commit
605b7fcb33
7330
example/package-lock.json
generated
7330
example/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
5463
package-lock.json
generated
5463
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
22
package.json
22
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gantt-task-react",
|
"name": "gantt-task-react",
|
||||||
"version": "0.3.0",
|
"version": "0.3.1",
|
||||||
"description": "Interactive Gantt Chart for React with TypeScript.",
|
"description": "Interactive Gantt Chart for React with TypeScript.",
|
||||||
"author": "MaTeMaTuK <maksym.vikarii@gmail.com>",
|
"author": "MaTeMaTuK <maksym.vikarii@gmail.com>",
|
||||||
"homepage": "https://github.com/MaTeMaTuK/gantt-task-react",
|
"homepage": "https://github.com/MaTeMaTuK/gantt-task-react",
|
||||||
@ -39,15 +39,15 @@
|
|||||||
"react": "^16.0.0"
|
"react": "^16.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
"@testing-library/jest-dom": "^5.12.0",
|
||||||
"@testing-library/react": "^9.5.0",
|
"@testing-library/react": "^11.2.6",
|
||||||
"@testing-library/user-event": "^7.2.1",
|
"@testing-library/user-event": "^13.1.8",
|
||||||
"@types/jest": "^25.1.4",
|
"@types/jest": "^26.0.23",
|
||||||
"@types/node": "^12.20.4",
|
"@types/node": "^15.0.1",
|
||||||
"@types/react": "^16.14.4",
|
"@types/react": "^17.0.4",
|
||||||
"@types/react-dom": "^16.9.11",
|
"@types/react-dom": "^17.0.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.7.0",
|
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
||||||
"@typescript-eslint/parser": "^4.7.0",
|
"@typescript-eslint/parser": "^4.22.0",
|
||||||
"babel-eslint": "^10.0.3",
|
"babel-eslint": "^10.0.3",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"eslint-config-prettier": "^6.15.0",
|
"eslint-config-prettier": "^6.15.0",
|
||||||
@ -59,7 +59,7 @@
|
|||||||
"eslint-plugin-promise": "^4.3.1",
|
"eslint-plugin-promise": "^4.3.1",
|
||||||
"eslint-plugin-react": "^7.22.0",
|
"eslint-plugin-react": "^7.22.0",
|
||||||
"eslint-plugin-standard": "^4.1.0",
|
"eslint-plugin-standard": "^4.1.0",
|
||||||
"gh-pages": "^2.2.0",
|
"gh-pages": "^3.1.0",
|
||||||
"microbundle-crl": "^0.13.11",
|
"microbundle-crl": "^0.13.11",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prettier": "^2.2.1",
|
"prettier": "^2.2.1",
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
.ganttVerticalContainer {
|
.ganttVerticalContainer {
|
||||||
overflow-x: auto;
|
overflow: hidden;
|
||||||
overflow-y: hidden;
|
|
||||||
font-size: 0;
|
font-size: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -9,7 +8,7 @@
|
|||||||
.horizontalContainer {
|
.horizontalContainer {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow-y: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { TaskGanttContentProps } from "./task-gantt-content";
|
|||||||
import { TaskListHeaderDefault } from "../task-list/task-list-header";
|
import { TaskListHeaderDefault } from "../task-list/task-list-header";
|
||||||
import { TaskListTableDefault } from "../task-list/task-list-table";
|
import { TaskListTableDefault } from "../task-list/task-list-table";
|
||||||
import { StandardTooltipContent } from "../other/tooltip";
|
import { StandardTooltipContent } from "../other/tooltip";
|
||||||
import { Scroll } from "../other/scroll";
|
import { VerticalScroll } from "../other/vertical-scroll";
|
||||||
import { TaskListProps, TaskList } from "../task-list/task-list";
|
import { TaskListProps, TaskList } from "../task-list/task-list";
|
||||||
import { TaskGantt } from "./task-gantt";
|
import { TaskGantt } from "./task-gantt";
|
||||||
import { BarTask } from "../../types/bar-task";
|
import { BarTask } from "../../types/bar-task";
|
||||||
@ -15,6 +15,7 @@ import { convertToBarTasks } from "../../helpers/bar-helper";
|
|||||||
import { GanttEvent } from "../../types/gantt-task-actions";
|
import { GanttEvent } from "../../types/gantt-task-actions";
|
||||||
import { DateSetup } from "../../types/date-setup";
|
import { DateSetup } from "../../types/date-setup";
|
||||||
import styles from "./gantt.module.css";
|
import styles from "./gantt.module.css";
|
||||||
|
import { HorizontalScroll } from "../other/horizontal-scroll";
|
||||||
|
|
||||||
export const Gantt: React.FunctionComponent<GanttProps> = ({
|
export const Gantt: React.FunctionComponent<GanttProps> = ({
|
||||||
tasks,
|
tasks,
|
||||||
@ -54,12 +55,15 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({
|
|||||||
onSelect,
|
onSelect,
|
||||||
}) => {
|
}) => {
|
||||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||||
|
const taskListRef = useRef<HTMLDivElement>(null);
|
||||||
|
const verticalGanttContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const [dateSetup, setDateSetup] = useState<DateSetup>(() => {
|
const [dateSetup, setDateSetup] = useState<DateSetup>(() => {
|
||||||
const [startDate, endDate] = ganttDateRange(tasks, viewMode);
|
const [startDate, endDate] = ganttDateRange(tasks, viewMode);
|
||||||
return { viewMode, dates: seedDates(startDate, endDate, viewMode) };
|
return { viewMode, dates: seedDates(startDate, endDate, viewMode) };
|
||||||
});
|
});
|
||||||
|
|
||||||
const [taskHeight, setTaskHeight] = useState((rowHeight * barFill) / 100);
|
const [taskHeight, setTaskHeight] = useState((rowHeight * barFill) / 100);
|
||||||
|
const [taskListWidth, setTaskListWidth] = useState(0);
|
||||||
const [barTasks, setBarTasks] = useState<BarTask[]>([]);
|
const [barTasks, setBarTasks] = useState<BarTask[]>([]);
|
||||||
const [ganttEvent, setGanttEvent] = useState<GanttEvent>({
|
const [ganttEvent, setGanttEvent] = useState<GanttEvent>({
|
||||||
action: "",
|
action: "",
|
||||||
@ -165,27 +169,43 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({
|
|||||||
}
|
}
|
||||||
}, [rowHeight, barFill, taskHeight]);
|
}, [rowHeight, barFill, taskHeight]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (taskListRef.current) {
|
||||||
|
setTaskListWidth(taskListRef.current.offsetWidth);
|
||||||
|
}
|
||||||
|
}, [taskListRef]);
|
||||||
|
|
||||||
// scroll events
|
// scroll events
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleWheel = (event: WheelEvent) => {
|
const handleWheel = (event: WheelEvent) => {
|
||||||
event.preventDefault();
|
if (event.shiftKey || event.deltaX) {
|
||||||
const newScrollY = scrollY + event.deltaY;
|
const scrollMove = event.deltaX ? event.deltaX : event.deltaY;
|
||||||
if (newScrollY < 0) {
|
let newScrollX = scrollX + scrollMove;
|
||||||
setScrollY(0);
|
if (newScrollX < 0) {
|
||||||
} else if (newScrollY > ganttFullHeight - ganttHeight) {
|
newScrollX = 0;
|
||||||
setScrollY(ganttFullHeight - ganttHeight);
|
} else if (newScrollX > svgWidth) {
|
||||||
|
newScrollX = svgWidth;
|
||||||
|
}
|
||||||
|
setScrollX(newScrollX);
|
||||||
|
event.preventDefault();
|
||||||
} else {
|
} else {
|
||||||
setScrollY(newScrollY);
|
let newScrollY = scrollY + event.deltaY;
|
||||||
|
if (newScrollY < 0) {
|
||||||
|
newScrollY = 0;
|
||||||
|
} else if (newScrollY > ganttFullHeight - ganttHeight) {
|
||||||
|
newScrollY = ganttFullHeight - ganttHeight;
|
||||||
|
}
|
||||||
|
if (newScrollY !== scrollY) {
|
||||||
|
setScrollY(newScrollY);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setIgnoreScrollEvent(true);
|
setIgnoreScrollEvent(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// subscribe if scroll is necessary
|
// subscribe if scroll is necessary
|
||||||
if (
|
if (wrapperRef.current) {
|
||||||
wrapperRef.current &&
|
|
||||||
ganttHeight &&
|
|
||||||
ganttHeight < barTasks.length * rowHeight
|
|
||||||
) {
|
|
||||||
wrapperRef.current.addEventListener("wheel", handleWheel, {
|
wrapperRef.current.addEventListener("wheel", handleWheel, {
|
||||||
passive: false,
|
passive: false,
|
||||||
});
|
});
|
||||||
@ -195,7 +215,7 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({
|
|||||||
wrapperRef.current.removeEventListener("wheel", handleWheel);
|
wrapperRef.current.removeEventListener("wheel", handleWheel);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [wrapperRef.current, scrollY, ganttHeight, barTasks, rowHeight]);
|
}, [wrapperRef.current, scrollY, scrollX, ganttHeight, svgWidth]);
|
||||||
|
|
||||||
const handleScrollY = (event: SyntheticEvent<HTMLDivElement>) => {
|
const handleScrollY = (event: SyntheticEvent<HTMLDivElement>) => {
|
||||||
if (scrollY !== event.currentTarget.scrollTop && !ignoreScrollEvent) {
|
if (scrollY !== event.currentTarget.scrollTop && !ignoreScrollEvent) {
|
||||||
@ -330,34 +350,43 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({
|
|||||||
ganttHeight,
|
ganttHeight,
|
||||||
horizontalContainerClass: styles.horizontalContainer,
|
horizontalContainerClass: styles.horizontalContainer,
|
||||||
selectedTask,
|
selectedTask,
|
||||||
|
taskListRef,
|
||||||
setSelectedTask: handleSelectedTask,
|
setSelectedTask: handleSelectedTask,
|
||||||
TaskListHeader,
|
TaskListHeader,
|
||||||
TaskListTable,
|
TaskListTable,
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div
|
<div>
|
||||||
className={styles.wrapper}
|
<div
|
||||||
onKeyDown={handleKeyDown}
|
className={styles.wrapper}
|
||||||
tabIndex={0}
|
onKeyDown={handleKeyDown}
|
||||||
ref={wrapperRef}
|
tabIndex={0}
|
||||||
>
|
ref={wrapperRef}
|
||||||
{listCellWidth && <TaskList {...tableProps} />}
|
>
|
||||||
<TaskGantt
|
{listCellWidth && <TaskList {...tableProps} />}
|
||||||
gridProps={gridProps}
|
<TaskGantt
|
||||||
calendarProps={calendarProps}
|
gridProps={gridProps}
|
||||||
barProps={barProps}
|
calendarProps={calendarProps}
|
||||||
ganttHeight={ganttHeight}
|
barProps={barProps}
|
||||||
scrollY={scrollY}
|
ganttHeight={ganttHeight}
|
||||||
scrollX={scrollX}
|
scrollY={scrollY}
|
||||||
|
scrollX={scrollX}
|
||||||
|
verticalGanttContainerRef={verticalGanttContainerRef}
|
||||||
|
/>
|
||||||
|
<VerticalScroll
|
||||||
|
ganttFullHeight={ganttFullHeight}
|
||||||
|
ganttHeight={ganttHeight}
|
||||||
|
headerHeight={headerHeight}
|
||||||
|
scroll={scrollY}
|
||||||
|
onScroll={handleScrollY}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<HorizontalScroll
|
||||||
|
svgWidth={svgWidth}
|
||||||
|
taskListWidth={taskListWidth}
|
||||||
|
scroll={scrollX}
|
||||||
onScroll={handleScrollX}
|
onScroll={handleScrollX}
|
||||||
/>
|
/>
|
||||||
<Scroll
|
|
||||||
ganttFullHeight={ganttFullHeight}
|
|
||||||
ganttHeight={ganttHeight}
|
|
||||||
headerHeight={headerHeight}
|
|
||||||
scroll={scrollY}
|
|
||||||
onScroll={handleScrollY}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useRef, useEffect, SyntheticEvent, useState } from "react";
|
import React, { useRef, useEffect, useState } from "react";
|
||||||
import { GridProps, Grid } from "../grid/grid";
|
import { GridProps, Grid } from "../grid/grid";
|
||||||
import { CalendarProps, Calendar } from "../calendar/calendar";
|
import { CalendarProps, Calendar } from "../calendar/calendar";
|
||||||
import { TaskGanttContentProps, TaskGanttContent } from "./task-gantt-content";
|
import { TaskGanttContentProps, TaskGanttContent } from "./task-gantt-content";
|
||||||
@ -11,7 +11,7 @@ export type TaskGanttProps = {
|
|||||||
ganttHeight: number;
|
ganttHeight: number;
|
||||||
scrollY: number;
|
scrollY: number;
|
||||||
scrollX: number;
|
scrollX: number;
|
||||||
onScroll: (event: SyntheticEvent<HTMLDivElement>) => void;
|
verticalGanttContainerRef: React.RefObject<HTMLDivElement>;
|
||||||
};
|
};
|
||||||
export const TaskGantt: React.FC<TaskGanttProps> = ({
|
export const TaskGantt: React.FC<TaskGanttProps> = ({
|
||||||
gridProps,
|
gridProps,
|
||||||
@ -20,11 +20,10 @@ export const TaskGantt: React.FC<TaskGanttProps> = ({
|
|||||||
ganttHeight,
|
ganttHeight,
|
||||||
scrollY,
|
scrollY,
|
||||||
scrollX,
|
scrollX,
|
||||||
onScroll,
|
verticalGanttContainerRef,
|
||||||
}) => {
|
}) => {
|
||||||
const ganttSVGRef = useRef<SVGSVGElement>(null);
|
const ganttSVGRef = useRef<SVGSVGElement>(null);
|
||||||
const horizontalContainerRef = useRef<HTMLDivElement>(null);
|
const horizontalContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const verticalContainerRef = useRef<HTMLDivElement>(null);
|
|
||||||
const [displayXStartEndpoint, setDisplayXStartEndpoint] = useState({
|
const [displayXStartEndpoint, setDisplayXStartEndpoint] = useState({
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
@ -38,21 +37,20 @@ export const TaskGantt: React.FC<TaskGanttProps> = ({
|
|||||||
}, [scrollY]);
|
}, [scrollY]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (verticalContainerRef.current) {
|
if (verticalGanttContainerRef.current) {
|
||||||
verticalContainerRef.current.scrollLeft = scrollX;
|
verticalGanttContainerRef.current.scrollLeft = scrollX;
|
||||||
setDisplayXStartEndpoint({
|
setDisplayXStartEndpoint({
|
||||||
start: scrollX,
|
start: scrollX,
|
||||||
end: verticalContainerRef.current.clientWidth + scrollX,
|
end: verticalGanttContainerRef.current.clientWidth + scrollX,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// verticalContainerRef.current?.clientWidth need for resize window tracking
|
// verticalContainerRef.current?.clientWidth need for resize window tracking
|
||||||
}, [scrollX, verticalContainerRef.current?.clientWidth]);
|
}, [scrollX, verticalGanttContainerRef.current?.clientWidth]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={styles.ganttVerticalContainer}
|
className={styles.ganttVerticalContainer}
|
||||||
ref={verticalContainerRef}
|
ref={verticalGanttContainerRef}
|
||||||
onScroll={onScroll}
|
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|||||||
4
src/components/other/horizontal-scroll.module.css
Normal file
4
src/components/other/horizontal-scroll.module.css
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.scroll {
|
||||||
|
overflow: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
28
src/components/other/horizontal-scroll.tsx
Normal file
28
src/components/other/horizontal-scroll.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import React, { SyntheticEvent, useRef, useEffect } from "react";
|
||||||
|
import styles from "./horizontal-scroll.module.css";
|
||||||
|
|
||||||
|
export const HorizontalScroll: React.FC<{
|
||||||
|
scroll: number;
|
||||||
|
svgWidth: number;
|
||||||
|
taskListWidth: number;
|
||||||
|
onScroll: (event: SyntheticEvent<HTMLDivElement>) => void;
|
||||||
|
}> = ({ scroll, svgWidth, taskListWidth, onScroll }) => {
|
||||||
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (scrollRef.current) {
|
||||||
|
scrollRef.current.scrollLeft = scroll;
|
||||||
|
}
|
||||||
|
}, [scroll]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{ marginLeft: taskListWidth }}
|
||||||
|
className={styles.scroll}
|
||||||
|
onScroll={onScroll}
|
||||||
|
ref={scrollRef}
|
||||||
|
>
|
||||||
|
<div style={{ width: svgWidth, height: 1 }} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import React, { SyntheticEvent, useRef, useEffect } from "react";
|
import React, { SyntheticEvent, useRef, useEffect } from "react";
|
||||||
import styles from "./scroll.module.css";
|
import styles from "./vertical-scroll.module.css";
|
||||||
|
|
||||||
export const Scroll: React.FC<{
|
export const VerticalScroll: React.FC<{
|
||||||
scroll: number;
|
scroll: number;
|
||||||
ganttHeight: number;
|
ganttHeight: number;
|
||||||
ganttFullHeight: number;
|
ganttFullHeight: number;
|
||||||
@ -12,6 +12,7 @@ export type TaskListProps = {
|
|||||||
scrollY: number;
|
scrollY: number;
|
||||||
locale: string;
|
locale: string;
|
||||||
tasks: Task[];
|
tasks: Task[];
|
||||||
|
taskListRef: React.RefObject<HTMLDivElement>;
|
||||||
horizontalContainerClass?: string;
|
horizontalContainerClass?: string;
|
||||||
selectedTask: BarTask | undefined;
|
selectedTask: BarTask | undefined;
|
||||||
setSelectedTask: (task: string) => void;
|
setSelectedTask: (task: string) => void;
|
||||||
@ -45,6 +46,7 @@ export const TaskList: React.FC<TaskListProps> = ({
|
|||||||
setSelectedTask,
|
setSelectedTask,
|
||||||
locale,
|
locale,
|
||||||
ganttHeight,
|
ganttHeight,
|
||||||
|
taskListRef,
|
||||||
horizontalContainerClass,
|
horizontalContainerClass,
|
||||||
TaskListHeader,
|
TaskListHeader,
|
||||||
TaskListTable,
|
TaskListTable,
|
||||||
@ -75,7 +77,7 @@ export const TaskList: React.FC<TaskListProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div ref={taskListRef}>
|
||||||
<TaskListHeader {...headerProps} />
|
<TaskListHeader {...headerProps} />
|
||||||
<div
|
<div
|
||||||
ref={horizontalContainerRef}
|
ref={horizontalContainerRef}
|
||||||
|
|||||||
@ -156,7 +156,6 @@ const convertToBar = (
|
|||||||
barBackgroundColor: string,
|
barBackgroundColor: string,
|
||||||
barBackgroundSelectedColor: string
|
barBackgroundSelectedColor: string
|
||||||
): BarTask => {
|
): BarTask => {
|
||||||
debugger;
|
|
||||||
const x1 = taskXCoordinate(task.start, dates, dateDelta, columnWidth);
|
const x1 = taskXCoordinate(task.start, dates, dateDelta, columnWidth);
|
||||||
let x2 = taskXCoordinate(task.end, dates, dateDelta, columnWidth);
|
let x2 = taskXCoordinate(task.end, dates, dateDelta, columnWidth);
|
||||||
const y = taskYCoordinate(index, rowHeight, taskHeight);
|
const y = taskYCoordinate(index, rowHeight, taskHeight);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user