tooltip is not a part of foreignObject anymore

This commit is contained in:
MaTeMaTuK 2021-05-17 22:27:46 +03:00
parent d63fe60960
commit e4a2cc02dc
8 changed files with 142 additions and 140 deletions

View File

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

View File

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

View File

@ -17,4 +17,5 @@
margin: 0;
list-style: none;
outline: none;
position: relative;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
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);
if (tooltipHeight !== 1000) {
setToolHeight(tooltipHeight);
}
}
}, [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>
);
};