From e4a2cc02dc743cd4a010fe83d86a29ef0bea5c84 Mon Sep 17 00:00:00 2001 From: MaTeMaTuK <maksym.vikarii@gmail.com> Date: Mon, 17 May 2021 22:27:46 +0300 Subject: [PATCH] tooltip is not a part of foreignObject anymore --- example/package-lock.json | 42 +++---- package.json | 2 +- src/components/gantt/gantt.module.css | 1 + src/components/gantt/gantt.tsx | 44 +++++-- src/components/gantt/task-gantt-content.tsx | 30 +---- src/components/gantt/task-gantt.tsx | 18 +-- src/components/other/tooltip.module.css | 17 ++- src/components/other/tooltip.tsx | 128 ++++++++++---------- 8 files changed, 142 insertions(+), 140 deletions(-) diff --git a/example/package-lock.json b/example/package-lock.json index e461fe2e1..c96c3e18a 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -104,9 +104,9 @@ } }, "@types/node": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.1.tgz", - "integrity": "sha512-TMkXt0Ck1y0KKsGr9gJtWGjttxlZnnvDtphxUOSd0bfaR6Q1jle+sPvrzNR1urqYTWMinoKvjKfXUGsumaO1PA==" + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.3.0.tgz", + "integrity": "sha512-8/bnjSZD86ZfpBsDlCIkNXIvm+h6wi9g7IqL+kmFkQ+Wvu3JrasgLElfiPgoo8V8vVfnEi0QVS12gbl94h9YsQ==" }, "@types/testing-library__jest-dom": { "version": "5.9.5", @@ -179,9 +179,9 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "core-js-pure": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.11.1.tgz", - "integrity": "sha512-2JukQi8HgAOCD5CSimxWWXVrUBoA9Br796uIA5Z06bIjt7PBBI19ircFaAxplgE1mJf3x2BY6MkT/HWA/UryPg==" + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.12.1.tgz", + "integrity": "sha512-1cch+qads4JnDSWsvc7d6nzlKAippwjUlf6vykkTLW53VSV+NkE6muGBToAjEA8pG90cSfcud3JgVmW2ds5TaQ==" }, "css": { "version": "3.0.0", @@ -395,9 +395,9 @@ } }, "@testing-library/dom": { - "version": "7.30.4", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.30.4.tgz", - "integrity": "sha512-GObDVMaI4ARrZEXaRy4moolNAxWPKvEYNV/fa6Uc2eAzR/t4otS6A7EhrntPBIQLeehL9DbVhscvvv7gd6hWqA==", + "version": "7.31.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.31.0.tgz", + "integrity": "sha512-0X7ACg4YvTRDFMIuTOEj6B4NpN7i3F/4j5igOcTI5NC5J+N4TribNdErCHOZF1LBWhhcyfwxelVwvoYNMUXTOA==", "requires": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -436,9 +436,9 @@ } }, "@types/node": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.1.tgz", - "integrity": "sha512-TMkXt0Ck1y0KKsGr9gJtWGjttxlZnnvDtphxUOSd0bfaR6Q1jle+sPvrzNR1urqYTWMinoKvjKfXUGsumaO1PA==" + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.3.0.tgz", + "integrity": "sha512-8/bnjSZD86ZfpBsDlCIkNXIvm+h6wi9g7IqL+kmFkQ+Wvu3JrasgLElfiPgoo8V8vVfnEi0QVS12gbl94h9YsQ==" }, "@types/yargs": { "version": "15.0.13", @@ -534,9 +534,9 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "core-js-pure": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.11.1.tgz", - "integrity": "sha512-2JukQi8HgAOCD5CSimxWWXVrUBoA9Br796uIA5Z06bIjt7PBBI19ircFaAxplgE1mJf3x2BY6MkT/HWA/UryPg==" + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.12.1.tgz", + "integrity": "sha512-1cch+qads4JnDSWsvc7d6nzlKAippwjUlf6vykkTLW53VSV+NkE6muGBToAjEA8pG90cSfcud3JgVmW2ds5TaQ==" }, "dom-accessibility-api": { "version": "0.5.4", @@ -681,9 +681,9 @@ } }, "@types/node": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.1.tgz", - "integrity": "sha512-TMkXt0Ck1y0KKsGr9gJtWGjttxlZnnvDtphxUOSd0bfaR6Q1jle+sPvrzNR1urqYTWMinoKvjKfXUGsumaO1PA==" + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.3.0.tgz", + "integrity": "sha512-8/bnjSZD86ZfpBsDlCIkNXIvm+h6wi9g7IqL+kmFkQ+Wvu3JrasgLElfiPgoo8V8vVfnEi0QVS12gbl94h9YsQ==" }, "@types/yargs": { "version": "15.0.13", @@ -828,9 +828,9 @@ "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" }, "@types/react": { - "version": "17.0.4", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.4.tgz", - "integrity": "sha512-onz2BqScSFMoTRdJUZUDD/7xrusM8hBA2Fktk2qgaTYPCgPvWnDEgkrOs8hhPUf2jfcIXkJ5yK6VfYormJS3Jw==", + "version": "17.0.5", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.5.tgz", + "integrity": "sha512-bj4biDB9ZJmGAYTWSKJly6bMr4BLUiBrx9ujiJEoP9XIDY9CTaPGxE5QWN/1WjpPLzYF7/jRNnV2nNxNe970sw==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", diff --git a/package.json b/package.json index 04efde969..0fd75b3c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gantt-task-react", - "version": "0.3.1", + "version": "0.3.2", "description": "Interactive Gantt Chart for React with TypeScript.", "author": "MaTeMaTuK <maksym.vikarii@gmail.com>", "homepage": "https://github.com/MaTeMaTuK/gantt-task-react", diff --git a/src/components/gantt/gantt.module.css b/src/components/gantt/gantt.module.css index 0531ffc1d..8169a19ce 100644 --- a/src/components/gantt/gantt.module.css +++ b/src/components/gantt/gantt.module.css @@ -17,4 +17,5 @@ margin: 0; list-style: none; outline: none; + position: relative; } diff --git a/src/components/gantt/gantt.tsx b/src/components/gantt/gantt.tsx index d5865f9ff..affe8bafb 100644 --- a/src/components/gantt/gantt.tsx +++ b/src/components/gantt/gantt.tsx @@ -6,7 +6,7 @@ import { CalendarProps } from "../calendar/calendar"; 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 { StandardTooltipContent, Tooltip } from "../other/tooltip"; import { VerticalScroll } from "../other/vertical-scroll"; import { TaskListProps, TaskList } from "../task-list/task-list"; import { TaskGantt } from "./task-gantt"; @@ -56,7 +56,6 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({ }) => { 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) }; @@ -64,6 +63,8 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({ const [taskHeight, setTaskHeight] = useState((rowHeight * barFill) / 100); const [taskListWidth, setTaskListWidth] = useState(0); + const [svgContainerWidth, setSvgContainerWidth] = useState(0); + const [svgContainerHeight, setSvgContainerHeight] = useState(ganttHeight); const [barTasks, setBarTasks] = useState<BarTask[]>([]); const [ganttEvent, setGanttEvent] = useState<GanttEvent>({ action: "", @@ -76,7 +77,6 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({ const [scrollX, setScrollX] = useState(0); const [ignoreScrollEvent, setIgnoreScrollEvent] = useState(false); - const svgHeight = rowHeight * barTasks.length; const svgWidth = dateSetup.dates.length * columnWidth; const ganttFullHeight = barTasks.length * rowHeight; @@ -170,10 +170,27 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({ }, [rowHeight, barFill, taskHeight]); useEffect(() => { + if (!listCellWidth) { + setTaskListWidth(0); + } if (taskListRef.current) { setTaskListWidth(taskListRef.current.offsetWidth); } - }, [taskListRef]); + }, [taskListRef, listCellWidth]); + + useEffect(() => { + if (wrapperRef.current) { + setSvgContainerWidth(wrapperRef.current.offsetWidth - taskListWidth); + } + }, [wrapperRef, taskListWidth]); + + useEffect(() => { + if (ganttHeight) { + setSvgContainerHeight(ganttHeight + headerHeight); + } else { + setSvgContainerHeight(tasks.length * rowHeight + headerHeight); + } + }, [ganttHeight, tasks]); // scroll events useEffect(() => { @@ -326,7 +343,6 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({ fontFamily, fontSize, arrowIndent, - svgHeight, svgWidth, setGanttEvent, setFailedTask, @@ -335,7 +351,6 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({ onProgressChange, onDoubleClick, onDelete, - TooltipContent, }; const tableProps: TaskListProps = { @@ -371,8 +386,23 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({ ganttHeight={ganttHeight} scrollY={scrollY} scrollX={scrollX} - verticalGanttContainerRef={verticalGanttContainerRef} /> + {ganttEvent.changedTask && ( + <Tooltip + arrowIndent={arrowIndent} + rowHeight={rowHeight} + svgContainerHeight={svgContainerHeight} + svgContainerWidth={svgContainerWidth} + fontFamily={fontFamily} + fontSize={fontSize} + scrollX={scrollX} + scrollY={scrollY} + task={ganttEvent.changedTask} + headerHeight={headerHeight} + taskListWidth={taskListWidth} + TooltipContent={TooltipContent} + /> + )} <VerticalScroll ganttFullHeight={ganttFullHeight} ganttHeight={ganttHeight} diff --git a/src/components/gantt/task-gantt-content.tsx b/src/components/gantt/task-gantt-content.tsx index 83527ffe8..f316b8ed8 100644 --- a/src/components/gantt/task-gantt-content.tsx +++ b/src/components/gantt/task-gantt-content.tsx @@ -1,9 +1,8 @@ import React, { useEffect, useState } from "react"; -import { Task, EventOption } from "../../types/public-types"; +import { EventOption } from "../../types/public-types"; import { BarTask } from "../../types/bar-task"; import { Arrow } from "../other/arrow"; import { handleTaskBySVGMouseEvent } from "../../helpers/bar-helper"; -import { Tooltip } from "../other/tooltip"; import { isKeyboardEvent } from "../../helpers/other-helper"; import { TaskItem } from "../task-item/task-item"; import { @@ -21,12 +20,7 @@ export type TaskGanttContentProps = { columnWidth: number; timeStep: number; svg?: React.RefObject<SVGSVGElement>; - svgHeight: number; svgWidth: number; - displayXStartEndpoint?: { - start: number; - end: number; - }; taskHeight: number; arrowColor: string; arrowIndent: number; @@ -35,11 +29,6 @@ export type TaskGanttContentProps = { setGanttEvent: (value: GanttEvent) => void; setFailedTask: (value: BarTask | null) => void; setSelectedTask: (taskId: string) => void; - TooltipContent: React.FC<{ - task: Task; - fontSize: string; - fontFamily: string; - }>; } & EventOption; export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({ @@ -51,8 +40,6 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({ columnWidth, timeStep, svg, - svgHeight, - displayXStartEndpoint, taskHeight, arrowColor, arrowIndent, @@ -65,7 +52,6 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({ onProgressChange, onDoubleClick, onDelete, - TooltipContent, }) => { const point = svg?.current?.createSVGPoint(); const [xStep, setXStep] = useState(0); @@ -292,20 +278,6 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({ ); })} </g> - <g className="toolTip"> - {ganttEvent.changedTask && displayXStartEndpoint && ( - <Tooltip - arrowIndent={arrowIndent} - rowHeight={rowHeight} - svgHeight={svgHeight} - displayXStartEndpoint={displayXStartEndpoint} - task={ganttEvent.changedTask} - fontFamily={fontFamily} - fontSize={fontSize} - TooltipContent={TooltipContent} - /> - )} - </g> </g> ); }; diff --git a/src/components/gantt/task-gantt.tsx b/src/components/gantt/task-gantt.tsx index 64f694370..7f9bc57c0 100644 --- a/src/components/gantt/task-gantt.tsx +++ b/src/components/gantt/task-gantt.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useEffect, useState } from "react"; +import React, { useRef, useEffect } from "react"; import { GridProps, Grid } from "../grid/grid"; import { CalendarProps, Calendar } from "../calendar/calendar"; import { TaskGanttContentProps, TaskGanttContent } from "./task-gantt-content"; @@ -11,7 +11,6 @@ export type TaskGanttProps = { ganttHeight: number; scrollY: number; scrollX: number; - verticalGanttContainerRef: React.RefObject<HTMLDivElement>; }; export const TaskGantt: React.FC<TaskGanttProps> = ({ gridProps, @@ -20,15 +19,11 @@ export const TaskGantt: React.FC<TaskGanttProps> = ({ ganttHeight, scrollY, scrollX, - verticalGanttContainerRef, }) => { const ganttSVGRef = useRef<SVGSVGElement>(null); const horizontalContainerRef = useRef<HTMLDivElement>(null); - const [displayXStartEndpoint, setDisplayXStartEndpoint] = useState({ - start: 0, - end: 0, - }); - const newBarProps = { ...barProps, svg: ganttSVGRef, displayXStartEndpoint }; + const verticalGanttContainerRef = useRef<HTMLDivElement>(null); + const newBarProps = { ...barProps, svg: ganttSVGRef }; useEffect(() => { if (horizontalContainerRef.current) { @@ -39,13 +34,8 @@ export const TaskGantt: React.FC<TaskGanttProps> = ({ useEffect(() => { if (verticalGanttContainerRef.current) { verticalGanttContainerRef.current.scrollLeft = scrollX; - setDisplayXStartEndpoint({ - start: scrollX, - end: verticalGanttContainerRef.current.clientWidth + scrollX, - }); } - // verticalContainerRef.current?.clientWidth need for resize window tracking - }, [scrollX, verticalGanttContainerRef.current?.clientWidth]); + }, [scrollX]); return ( <div diff --git a/src/components/other/tooltip.module.css b/src/components/other/tooltip.module.css index 000356971..d5793ef14 100644 --- a/src/components/other/tooltip.module.css +++ b/src/components/other/tooltip.module.css @@ -11,5 +11,20 @@ } .tooltipDetailsContainer { - display: table; + position: absolute; + display: flex; + flex-shrink: 0; + pointer-events: none; + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.tooltipDetailsContainerHidden { + visibility: hidden; + position: absolute; + display: flex; + pointer-events: none; } diff --git a/src/components/other/tooltip.tsx b/src/components/other/tooltip.tsx index 4d40a3753..95ba12494 100644 --- a/src/components/other/tooltip.tsx +++ b/src/components/other/tooltip.tsx @@ -6,11 +6,12 @@ import styles from "./tooltip.module.css"; export type TooltipProps = { task: BarTask; arrowIndent: number; - svgHeight: number; - displayXStartEndpoint: { - start: number; - end: number; - }; + svgContainerHeight: number; + svgContainerWidth: number; + headerHeight: number; + taskListWidth: number; + scrollX: number; + scrollY: number; rowHeight: number; fontSize: string; fontFamily: string; @@ -23,83 +24,76 @@ export type TooltipProps = { export const Tooltip: React.FC<TooltipProps> = ({ task, rowHeight, - svgHeight, - displayXStartEndpoint, + svgContainerHeight, + svgContainerWidth, + scrollX, + scrollY, arrowIndent, fontSize, fontFamily, + headerHeight, + taskListWidth, TooltipContent, }) => { const tooltipRef = useRef<HTMLDivElement | null>(null); - const [toolWidth, setToolWidth] = useState(1000); - const [toolHeight, setToolHeight] = useState(1000); - const [relatedY, setRelatedY] = useState(task.index * rowHeight); - const [relatedX, setRelatedX] = useState(displayXStartEndpoint.end); + const [relatedY, setRelatedY] = useState(0); + const [relatedX, setRelatedX] = useState(0); useEffect(() => { if (tooltipRef.current) { + let newRelatedX = + task.x2 + arrowIndent + arrowIndent * 0.5 + taskListWidth - scrollX; + let newRelatedY = task.index * rowHeight - scrollY + headerHeight; + const tooltipHeight = tooltipRef.current.offsetHeight * 1.1; - let tooltipY = task.index * rowHeight; - const newWidth = tooltipRef.current.scrollWidth * 1.1; - let newRelatedX = task.x2 + arrowIndent + arrowIndent * 0.5; - if (newWidth + newRelatedX > displayXStartEndpoint.end) { - newRelatedX = task.x1 - arrowIndent - arrowIndent * 0.5 - newWidth; - } - const tooltipLowerPoint = tooltipHeight + tooltipY; + const tooltipWidth = tooltipRef.current.offsetWidth * 1.1; - if ( - newRelatedX < displayXStartEndpoint.start && - tooltipLowerPoint > svgHeight - ) { - tooltipY -= tooltipHeight; - newRelatedX = (task.x1 + task.x2 - newWidth) * 0.5; - if (newRelatedX + newWidth > displayXStartEndpoint.end) { - newRelatedX = displayXStartEndpoint.end - newWidth; - } - if ( - newRelatedX + newWidth > displayXStartEndpoint.end || - newRelatedX - newWidth < displayXStartEndpoint.start - ) { - newRelatedX = displayXStartEndpoint.end - newWidth; - } - } else if ( - newRelatedX < displayXStartEndpoint.start && - tooltipLowerPoint < svgHeight - ) { - tooltipY += rowHeight; - newRelatedX = (task.x1 + task.x2 - newWidth) * 0.5; - if ( - newRelatedX + newWidth > displayXStartEndpoint.end || - newRelatedX - newWidth < displayXStartEndpoint.start - ) { - newRelatedX = displayXStartEndpoint.end - newWidth; - } - } else if (tooltipLowerPoint > svgHeight) { - tooltipY = svgHeight - tooltipHeight; - } + const tooltipLowerPoint = tooltipHeight + newRelatedY - scrollY; + const tooltipLeftmostPoint = tooltipWidth + newRelatedX; + const fullChartWidth = taskListWidth + svgContainerWidth; - setRelatedY(tooltipY); - setToolWidth(newWidth); - setRelatedX(newRelatedX); - if (tooltipHeight !== 1000) { - setToolHeight(tooltipHeight); + if (tooltipLeftmostPoint > fullChartWidth) { + newRelatedX = + task.x1 + + taskListWidth - + arrowIndent - + arrowIndent * 0.5 - + scrollX - + tooltipWidth; } + if (newRelatedX < taskListWidth) { + newRelatedX = svgContainerWidth + taskListWidth - tooltipWidth; + newRelatedY += rowHeight; + } else if (tooltipLowerPoint > svgContainerHeight - scrollY) { + newRelatedY = svgContainerHeight - tooltipHeight; + } + setRelatedY(newRelatedY); + setRelatedX(newRelatedX); } - }, [tooltipRef, task, arrowIndent, displayXStartEndpoint]); + }, [ + tooltipRef.current, + task, + arrowIndent, + scrollX, + scrollY, + headerHeight, + taskListWidth, + rowHeight, + svgContainerHeight, + svgContainerWidth, + ]); + return ( - <foreignObject - x={relatedX} - y={relatedY} - width={toolWidth} - height={toolHeight} + <div + ref={tooltipRef} + className={ + relatedX + ? styles.tooltipDetailsContainer + : styles.tooltipDetailsContainerHidden + } + style={{ left: relatedX, top: relatedY }} > - <div ref={tooltipRef} className={styles.tooltipDetailsContainer}> - <TooltipContent - task={task} - fontSize={fontSize} - fontFamily={fontFamily} - /> - </div> - </foreignObject> + <TooltipContent task={task} fontSize={fontSize} fontFamily={fontFamily} /> + </div> ); };