Merge pull request #6 from MaTeMaTuK/dev

vertical scroll fix
This commit is contained in:
MaTeMaTuK 2021-05-03 21:35:26 +03:00 committed by GitHub
commit 605b7fcb33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 4389 additions and 8589 deletions

7330
example/package-lock.json generated

File diff suppressed because it is too large Load Diff

5463
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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",

View File

@ -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 {

View File

@ -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>
); );
}; };

View File

@ -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"

View File

@ -0,0 +1,4 @@
.scroll {
overflow: auto;
max-width: 100%;
}

View 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>
);
};

View File

@ -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;

View File

@ -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}

View File

@ -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);