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