diff --git a/package-lock.json b/package-lock.json
index 05e497f..b35b7ef 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@svar-ui/react-gantt",
- "version": "2.3.3",
+ "version": "2.3.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@svar-ui/react-gantt",
- "version": "2.3.3",
+ "version": "2.3.4",
"license": "GPL-3.0",
"dependencies": {
"@svar-ui/gantt-data-provider": "2.3.0",
diff --git a/package.json b/package.json
index 62180c1..b7da54f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@svar-ui/react-gantt",
- "version": "2.3.3",
+ "version": "2.3.4",
"description": "A customizable, high-performance React Gantt chart component for building project timelines.",
"productTag": "gantt",
"type": "module",
@@ -50,7 +50,8 @@
"dist",
"types",
"package.json",
- "dist-full/index.css"
+ "dist-full/index.css",
+ "whatsnew.md"
],
"peerDependencies": {
"react": ">=18",
diff --git a/src/components/Layout.jsx b/src/components/Layout.jsx
index 3dab81a..7432e2a 100644
--- a/src/components/Layout.jsx
+++ b/src/components/Layout.jsx
@@ -7,7 +7,6 @@ import {
useContext,
} from 'react';
import { hotkeys } from '@svar-ui/grid-store';
-import TimeScales from './TimeScale.jsx';
import Grid from './grid/Grid.jsx';
import Chart from './chart/Chart.jsx';
import Resizer from './Resizer.jsx';
@@ -31,9 +30,7 @@ function Layout(props) {
const rScales = useStore(api, "_scales");
const rCellHeight = useStore(api, "cellHeight");
const rColumns = useStore(api, "columns");
- const scrollTop = useStore(api, "scrollTop");
const rScrollTask = useStore(api, "_scrollTask");
-
const [compactMode, setCompactMode] = useState(false);
let [gridWidth, setGridWidth] = useState(0);
const [ganttWidth, setGanttWidth] = useState(undefined);
@@ -131,20 +128,13 @@ function Layout(props) {
const ganttDivRef = useRef(null);
const pseudoRowsRef = useRef(null);
- const syncScroll = useCallback(() => {
- const el = ganttDivRef.current;
- if (el && scrollTop !== el.scrollTop) el.scrollTop = scrollTop;
- }, [scrollTop]);
-
- useEffect(() => {
- syncScroll();
- }, [scrollTop, syncScroll]);
-
const onScroll = useCallback(() => {
const el = ganttDivRef.current;
- api.exec('scroll-chart', {
- top: el ? el.scrollTop : 0,
- });
+ if (el) {
+ api.exec('scroll-chart', {
+ top: el.scrollTop,
+ });
+ }
}, [api]);
const latest = useRef({
@@ -193,6 +183,7 @@ function Layout(props) {
}
if (top !== null) {
api.exec('scroll-chart', { top: Math.max(top, 0) });
+ ganttDivRef.current.scrollTop = Math.max(top, 0);
}
}
},
@@ -200,7 +191,7 @@ function Layout(props) {
);
useEffect(() => {
- scrollToTask(rScrollTask);
+ scrollToTask(rScrollTask);
}, [rScrollTask]);
useEffect(() => {
@@ -229,11 +220,14 @@ function Layout(props) {
}, [pseudoRowsRef.current]);
const layoutRef = useRef(null);
+ const cleanupRef = useRef(null);
+
useEffect(() => {
+ if (cleanupRef.current) return;
const node = layoutRef.current;
if (!node) return;
- const cleanup = hotkeys(node, {
+ cleanupRef.current = hotkeys(node, {
keys: {
'ctrl+c': true,
'ctrl+v': true,
@@ -246,9 +240,11 @@ function Layout(props) {
},
});
-
- return cleanup.destroy;
- }, [api]);
+ return () => {
+ cleanupRef.current?.destroy();
+ cleanupRef.current = null;
+ };
+ }, []);
return (
@@ -289,8 +285,6 @@ function Layout(props) {
) : null}
-
-
{
if (!areaValue || !Array.isArray(rTasksValue)) return [];
@@ -66,21 +66,16 @@ function Bars(props) {
}, [hasFocus, selectedValue]);
useEffect(() => {
- if (!scrollTaskStore || typeof scrollTaskStore.subscribe !== 'function')
+ if (!scrollTaskStore)
return;
- const unsub = scrollTaskStore.subscribe((value) => {
- if (hasFocus && value) {
- const { id } = value;
- const node = containerRef.current?.querySelector(
- `.wx-bar[data-id='${id}']`,
- );
- if (node) node.focus();
- }
- });
- return () => {
- if (unsub) unsub();
- };
- }, [scrollTaskStore, hasFocus]);
+ if (hasFocus && scrollTaskStore) {
+ const { id } = scrollTaskStore;
+ const node = containerRef.current?.querySelector(
+ `.wx-bar[data-id='${id}']`,
+ );
+ if (node) node.focus({ preventScroll: true });
+ }
+ }, [scrollTaskStore]);
useEffect(() => {
const el = containerRef.current;
@@ -391,7 +386,7 @@ function Bars(props) {
setLinkFrom(null);
}
}, [linkFrom]);
-
+
const onClick = useCallback(
(e) => {
if (ignoreNextClickRef.current) {
@@ -496,7 +491,7 @@ function Bars(props) {
}}
>
{tasks.map((task) => {
- if (task.$skip) return null;
+ if (task.$skip && task.$skip_baseline) return null;
const barClass =
`wx-bar wx-${taskTypeCss(task.type)}` +
(touched && taskMove && task.id === taskMove.id ? ' wx-touch' : '') +
@@ -518,7 +513,7 @@ function Bars(props) {
: '');
return (
-
) : null}
+ }
{baselinesValue && !task.$skip_baseline ? (
{
- setScrollLeft(rScrollLeft);
- setScrollTop(rScrollTop);
- }, [rScrollLeft, rScrollTop]);
-
-
+ const extraRows = 1 + (scales?.rows?.length || 0);
const selectStyle = useMemo(() => {
const t = [];
if (selected && selected.length && cellHeight) {
@@ -58,10 +51,6 @@ function Chart(props) {
return t;
}, [selectedCounter, cellHeight]);
- useEffect(() => {
- dataRequest();
- }, [chartHeight]);
-
const chartGridHeight = useMemo(
() => Math.max(chartHeight || 0, fullHeight),
[chartHeight, fullHeight],
@@ -70,25 +59,21 @@ function Chart(props) {
useEffect(() => {
const el = chartRef.current;
if (!el) return;
- if (typeof scrollTop === 'number') el.scrollTop = scrollTop;
- if (typeof scrollLeft === 'number') el.scrollLeft = scrollLeft;
- if (typeof scrollTop === 'number' && scrollTop !== el.scrollTop)
- setScroll({ top: true });
- if (typeof scrollLeft === 'number' && scrollLeft !== el.scrollLeft)
- setScroll({ left: true });
- }, [scrollTop, scrollLeft]);
+
+ if (typeof rScrollTop === 'number') {
+ el.scrollTop = rScrollTop;
+ }
+ }, [rScrollTop]);
const onScroll = () => {
- const scroll = { left: true, top: true };
+ const scroll = { left: true };
setScroll(scroll);
- dataRequest();
};
function setScroll(scroll) {
const el = chartRef.current;
if (!el) return;
const pos = {};
- if (scroll.top) pos.top = el.scrollTop;
if (scroll.left) pos.left = el.scrollLeft;
api.exec('scroll-chart', pos);
}
@@ -108,6 +93,10 @@ function Chart(props) {
});
}
+ useEffect(() => {
+ dataRequest();
+ }, [chartHeight, rScrollTop, rScrollLeft]);
+
const showTask = useCallback(
(value) => {
if (!value) return;
@@ -119,14 +108,16 @@ function Chart(props) {
if (!el) return;
const { clientWidth } = el;
const task = api.getTask(id);
- if (task.$x + task.$w < (scrollLeft || 0)) {
- setScrollLeft(task.$x - (cellWidth || 0));
- } else if (task.$x >= clientWidth + (scrollLeft || 0)) {
+ if (task.$x + task.$w < el.scrollLeft) {
+ api.exec('scroll-chart', { left: task.$x - (cellWidth || 0) });
+ el.scrollLeft = task.$x - (cellWidth || 0)
+ } else if (task.$x >= clientWidth + el.scrollLeft) {
const width = clientWidth < task.$w ? cellWidth || 0 : task.$w;
- setScrollLeft(task.$x - clientWidth + width);
+ api.exec('scroll-chart', { left: task.$x - clientWidth + width });
+ el.scrollLeft = task.$x - clientWidth + width;
}
},
- [api, scrollLeft, cellWidth],
+ [api, cellWidth, rScrollLeft],
);
useEffect(() => {
@@ -165,10 +156,10 @@ function Chart(props) {
: null;
}, [scales, highlightTime]);
- function handleHotkey(ev) {
+ const handleHotkey = useCallback((ev) => {
ev.eventSource = 'chart';
api.exec('hotkey', ev);
- }
+ }, [api]);
useEffect(() => {
const el = chartRef.current;
@@ -182,10 +173,13 @@ function Chart(props) {
};
}, [chartRef.current]);
+ const cleanupRef = useRef(null);
+
useEffect(() => {
const el = chartRef.current;
if (!el) return;
- const cleanup = hotkeys(el, {
+ if (cleanupRef.current) return;
+ cleanupRef.current = hotkeys(el, {
keys: {
arrowup: true,
arrowdown: true,
@@ -193,10 +187,11 @@ function Chart(props) {
exec: (v) => handleHotkey(v),
});
return () => {
- if (typeof cleanup === 'function') cleanup();
+ cleanupRef.current?.destroy();
+ cleanupRef.current = null;
};
- }, [chartRef.current]);
-
+ }, []);
+
useEffect(() => {
const node = chartRef.current;
if (!node) return;
@@ -215,6 +210,7 @@ function Chart(props) {
ref={chartRef}
onScroll={onScroll}
>
+
{markers && markers.length ? (
- obj.$y ? (
-
- ) : null,
- )
+ obj.$y ? (
+
+ ) : null,
+ )
: null}
diff --git a/whatsnew.md b/whatsnew.md
new file mode 100644
index 0000000..fd0d2d8
--- /dev/null
+++ b/whatsnew.md
@@ -0,0 +1,101 @@
+## Version 2.3.4
+
+### Fixes
+
+- Performance regression during scrolling and zooming
+
+## Version 2.3.3
+
+### Fixes
+
+- Fullscreen hotkey property should be optional
+- Incorrect handling of themes
+- ContextMenu custom class name cannot be set
+
+## Version 2.3.2
+
+### Fixes
+
+- Tooltips for bars are not visible
+
+## Version 2.3
+
+### New features
+
+- TypeScript definitions
+- Ability to define "hour" duration unit
+- Ability to define "minute" length unit
+- Ability to create custom scale units
+- `autoScale` property to configure start/end scale behaviour
+- Standalone sidebar and modal Editor
+- Ability to validate fields in the Editor
+- Hotkeys for common actions and navigation
+- Multi-sorting for table columns via the sort-tasks action
+- Headermenu to hide table columns
+- Inline editors for table cells
+- Ability to expand/collapse table and chart via buttons
+
+### Breaking changes
+
+- The package migrated from wx-react-gantt to @svar-ui/react-gantt
+
+## Version 1.3.1
+
+### Fixes
+
+- Rendering issues
+
+## Version 1.3
+
+- Released under GPLv3
+
+## Version 1.1
+
+### New features
+
+- Summary tasks
+- Sorting Grid columns
+- Batch mode for RestDataProvider to handle mass operations
+
+### Updates
+
+- Common minCellwidth and maxCellWidth for zoom levels
+- Skipping meaningless actions in Context Menu and Toolbar
+
+### Fixes
+
+- Scroll in Grid is not smooth
+- Impossible to resize chart bars on the left more than for one cell
+- When a branch is removed, only links of the parent task are removed
+- Unstable move down behaviour: tasks are inserted in wrong positions
+- Resizing columns: horizontal scrollbar does not appear
+- Text of tasks is higher than a dragged bar during reordering
+- Last task is misplaced after reordering when there are few tasks
+- Reordering of tasks with child tasks is broken
+- Impossible to define cellWidth if default zoom is enabled
+- Data is removed from the task object after the "update-task" operation
+- Outdenting does not work for the 3rd-level tasks
+- Auto scale is calculated incorrectly if the last task is a milestone
+- Parent task is not always opened after adding a new task
+- Task start date is not set according to the top-level target task
+- Zooming in and out between levels does not work correctly
+
+## Version 1.0
+
+### Initial functionality
+
+- Fast behavior and clear API
+- Configurable Grid columns
+- Configurable Chart scales and cell sizes
+- Task types: "project", "task", "milestone" and custom
+- Baselines
+- Holiday and custom markers in the Chart area
+- Configurable Editor panel
+- Configurable Context Menu and Toolbar
+- Tooltips for tasks in the Chart area
+- Readonly mode
+- Fullscreen mode
+- Mouse-wheel zooming in the Chart area
+- Responsive behaviour of the Grid area
+- Localization of labels and dates
+- Ready-made DataProvider to integrate with server