gantt-task-react-v 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17486,6 +17486,308 @@ const useHorizontalScrollbars = () => {
17486
17486
  scrollToRightStep
17487
17487
  ];
17488
17488
  };
17489
+ const usePerformanceMonitor = (enabled = true) => {
17490
+ const metricsRef = useRef([]);
17491
+ const benchmarksRef = useRef(/* @__PURE__ */ new Map());
17492
+ const frameTimesRef = useRef([]);
17493
+ const lastFrameTimeRef = useRef(performance.now());
17494
+ const updateFPS = useCallback(() => {
17495
+ if (!enabled)
17496
+ return;
17497
+ const now = performance.now();
17498
+ const deltaTime = now - lastFrameTimeRef.current;
17499
+ lastFrameTimeRef.current = now;
17500
+ frameTimesRef.current.push(deltaTime);
17501
+ if (frameTimesRef.current.length > 60) {
17502
+ frameTimesRef.current.shift();
17503
+ }
17504
+ }, [enabled]);
17505
+ const getFPS = useCallback(() => {
17506
+ if (frameTimesRef.current.length === 0)
17507
+ return 0;
17508
+ const avgFrameTime = frameTimesRef.current.reduce((sum, time) => sum + time, 0) / frameTimesRef.current.length;
17509
+ return Math.round(1e3 / avgFrameTime);
17510
+ }, []);
17511
+ const startMeasurement = useCallback(
17512
+ (name, metadata) => {
17513
+ if (!enabled)
17514
+ return;
17515
+ performance.mark(`${name}-start`);
17516
+ benchmarksRef.current.set(name, {
17517
+ name,
17518
+ startTime: performance.now(),
17519
+ metadata
17520
+ });
17521
+ },
17522
+ [enabled]
17523
+ );
17524
+ const endMeasurement = useCallback(
17525
+ (name) => {
17526
+ if (!enabled)
17527
+ return 0;
17528
+ const benchmark = benchmarksRef.current.get(name);
17529
+ if (!benchmark) {
17530
+ console.warn(`No benchmark found for ${name}`);
17531
+ return 0;
17532
+ }
17533
+ const endTime = performance.now();
17534
+ const duration = endTime - benchmark.startTime;
17535
+ performance.mark(`${name}-end`);
17536
+ performance.measure(name, `${name}-start`, `${name}-end`);
17537
+ benchmark.endTime = endTime;
17538
+ benchmark.duration = duration;
17539
+ return duration;
17540
+ },
17541
+ [enabled]
17542
+ );
17543
+ const measureFunction = useCallback(
17544
+ (name, fn, metadata) => {
17545
+ return (...args) => {
17546
+ if (!enabled)
17547
+ return fn(...args);
17548
+ startMeasurement(name, metadata);
17549
+ try {
17550
+ const result = fn(...args);
17551
+ return result;
17552
+ } finally {
17553
+ endMeasurement(name);
17554
+ }
17555
+ };
17556
+ },
17557
+ [enabled, startMeasurement, endMeasurement]
17558
+ );
17559
+ const measureAsyncFunction = useCallback(
17560
+ (name, fn, metadata) => {
17561
+ return async (...args) => {
17562
+ if (!enabled)
17563
+ return fn(...args);
17564
+ startMeasurement(name, metadata);
17565
+ try {
17566
+ const result = await fn(...args);
17567
+ return result;
17568
+ } finally {
17569
+ endMeasurement(name);
17570
+ }
17571
+ };
17572
+ },
17573
+ [enabled, startMeasurement, endMeasurement]
17574
+ );
17575
+ const getPerformanceReport = useCallback(() => {
17576
+ const entries = performance.getEntriesByType(
17577
+ "measure"
17578
+ );
17579
+ const report = {};
17580
+ entries.forEach((entry) => {
17581
+ if (!report[entry.name]) {
17582
+ report[entry.name] = {
17583
+ count: 0,
17584
+ totalTime: 0,
17585
+ avgTime: 0,
17586
+ maxTime: 0,
17587
+ minTime: Infinity
17588
+ };
17589
+ }
17590
+ const stat = report[entry.name];
17591
+ stat.count++;
17592
+ stat.totalTime += entry.duration;
17593
+ stat.maxTime = Math.max(stat.maxTime, entry.duration);
17594
+ stat.minTime = Math.min(stat.minTime, entry.duration);
17595
+ stat.avgTime = stat.totalTime / stat.count;
17596
+ });
17597
+ return {
17598
+ measurements: report,
17599
+ fps: getFPS(),
17600
+ currentMetrics: metricsRef.current[metricsRef.current.length - 1],
17601
+ allMetrics: [...metricsRef.current]
17602
+ };
17603
+ }, [getFPS]);
17604
+ const addMetrics = useCallback(
17605
+ (metrics) => {
17606
+ if (!enabled)
17607
+ return;
17608
+ const fullMetrics = {
17609
+ ...metrics,
17610
+ timestamp: Date.now()
17611
+ };
17612
+ metricsRef.current.push(fullMetrics);
17613
+ if (metricsRef.current.length > 100) {
17614
+ metricsRef.current.shift();
17615
+ }
17616
+ },
17617
+ [enabled]
17618
+ );
17619
+ const clearMeasurements = useCallback(() => {
17620
+ performance.clearMarks();
17621
+ performance.clearMeasures();
17622
+ benchmarksRef.current.clear();
17623
+ metricsRef.current.length = 0;
17624
+ frameTimesRef.current.length = 0;
17625
+ }, []);
17626
+ const getMemoryUsage = useCallback(() => {
17627
+ if ("memory" in performance) {
17628
+ const memory = performance.memory;
17629
+ return memory ? memory.usedJSHeapSize : 0;
17630
+ }
17631
+ return 0;
17632
+ }, []);
17633
+ return {
17634
+ startMeasurement,
17635
+ endMeasurement,
17636
+ measureFunction,
17637
+ measureAsyncFunction,
17638
+ getPerformanceReport,
17639
+ addMetrics,
17640
+ clearMeasurements,
17641
+ updateFPS,
17642
+ getFPS,
17643
+ getMemoryUsage,
17644
+ enabled
17645
+ };
17646
+ };
17647
+ const useAdaptivePerformance = (thresholds) => {
17648
+ const { getFPS, getPerformanceReport } = usePerformanceMonitor();
17649
+ const performanceLevelRef = useRef("high");
17650
+ const updatePerformanceLevel = useCallback(() => {
17651
+ var _a;
17652
+ const fps = getFPS();
17653
+ const report = getPerformanceReport();
17654
+ const renderTime = ((_a = report.measurements["render"]) == null ? void 0 : _a.avgTime) || 0;
17655
+ if (fps < thresholds.fpsThreshold * 0.5 || renderTime > thresholds.renderTimeThreshold * 2) {
17656
+ performanceLevelRef.current = "low";
17657
+ } else if (fps < thresholds.fpsThreshold || renderTime > thresholds.renderTimeThreshold) {
17658
+ performanceLevelRef.current = "medium";
17659
+ } else {
17660
+ performanceLevelRef.current = "high";
17661
+ }
17662
+ return performanceLevelRef.current;
17663
+ }, [getFPS, getPerformanceReport, thresholds]);
17664
+ const getQualitySettings = useCallback(() => {
17665
+ const level = performanceLevelRef.current;
17666
+ switch (level) {
17667
+ case "low":
17668
+ return {
17669
+ enableAnimations: false,
17670
+ renderQuality: "low",
17671
+ maxVisibleTasks: 100,
17672
+ updateFrequency: 500
17673
+ // ms
17674
+ };
17675
+ case "medium":
17676
+ return {
17677
+ enableAnimations: true,
17678
+ renderQuality: "medium",
17679
+ maxVisibleTasks: 500,
17680
+ updateFrequency: 100
17681
+ };
17682
+ case "high":
17683
+ default:
17684
+ return {
17685
+ enableAnimations: true,
17686
+ renderQuality: "high",
17687
+ maxVisibleTasks: 1e3,
17688
+ updateFrequency: 16
17689
+ // 60fps
17690
+ };
17691
+ }
17692
+ }, []);
17693
+ return {
17694
+ performanceLevel: performanceLevelRef.current,
17695
+ updatePerformanceLevel,
17696
+ getQualitySettings
17697
+ };
17698
+ };
17699
+ const useIncrementalCoordinates = (visibleTasks, visibleTasksMirror, taskToRowIndexMap, startDate, viewMode, rtl, fullRowHeight, taskHeight, taskYOffset, distances, svgWidth, renderedRowIndexes, renderedColumnIndexes) => {
17700
+ const cacheRef = useRef({
17701
+ coordinates: /* @__PURE__ */ new Map(),
17702
+ lastUpdateParams: null
17703
+ });
17704
+ return useMemo(() => {
17705
+ const cache = cacheRef.current;
17706
+ const currentParams = {
17707
+ startDate,
17708
+ viewMode,
17709
+ rtl,
17710
+ fullRowHeight,
17711
+ taskHeight,
17712
+ taskYOffset,
17713
+ svgWidth,
17714
+ distances
17715
+ };
17716
+ const needsFullRecalc = !cache.lastUpdateParams || cache.lastUpdateParams.startDate.getTime() !== startDate.getTime() || cache.lastUpdateParams.viewMode !== viewMode || cache.lastUpdateParams.rtl !== rtl || cache.lastUpdateParams.fullRowHeight !== fullRowHeight || cache.lastUpdateParams.taskHeight !== taskHeight || cache.lastUpdateParams.taskYOffset !== taskYOffset || cache.lastUpdateParams.svgWidth !== svgWidth || JSON.stringify(cache.lastUpdateParams.distances) !== JSON.stringify(distances);
17717
+ if (needsFullRecalc) {
17718
+ cache.coordinates.clear();
17719
+ }
17720
+ const tasksToCalculate = /* @__PURE__ */ new Set();
17721
+ if (renderedRowIndexes && renderedColumnIndexes) {
17722
+ const [startRow, endRow] = renderedRowIndexes;
17723
+ const rowBuffer = Math.max(5, Math.ceil((endRow - startRow) * 0.5));
17724
+ const bufferedStartRow = Math.max(0, startRow - rowBuffer);
17725
+ const bufferedEndRow = endRow + rowBuffer;
17726
+ visibleTasks.forEach((task, index2) => {
17727
+ if (task.type === "empty" || !visibleTasksMirror[task.id])
17728
+ return;
17729
+ if (index2 >= bufferedStartRow && index2 <= bufferedEndRow) {
17730
+ tasksToCalculate.add(task.id);
17731
+ }
17732
+ });
17733
+ } else {
17734
+ visibleTasks.forEach((task) => {
17735
+ if (task.type !== "empty" && visibleTasksMirror[task.id]) {
17736
+ tasksToCalculate.add(task.id);
17737
+ }
17738
+ });
17739
+ }
17740
+ if (!needsFullRecalc) {
17741
+ for (const [taskId] of cache.coordinates) {
17742
+ if (!tasksToCalculate.has(taskId)) {
17743
+ cache.coordinates.delete(taskId);
17744
+ }
17745
+ }
17746
+ }
17747
+ const res = /* @__PURE__ */ new Map();
17748
+ visibleTasks.forEach((task) => {
17749
+ if (task.type === "empty" || !tasksToCalculate.has(task.id))
17750
+ return;
17751
+ const { id, comparisonLevel = 1 } = task;
17752
+ const cacheKey = `${id}_${comparisonLevel}`;
17753
+ let taskCoordinates = cache.coordinates.get(cacheKey);
17754
+ if (!taskCoordinates || needsFullRecalc) {
17755
+ taskCoordinates = countTaskCoordinates(
17756
+ task,
17757
+ taskToRowIndexMap,
17758
+ startDate,
17759
+ viewMode,
17760
+ rtl,
17761
+ fullRowHeight,
17762
+ taskHeight,
17763
+ taskYOffset,
17764
+ distances,
17765
+ svgWidth
17766
+ );
17767
+ cache.coordinates.set(cacheKey, taskCoordinates);
17768
+ }
17769
+ const resByLevel = res.get(comparisonLevel) || /* @__PURE__ */ new Map();
17770
+ resByLevel.set(id, taskCoordinates);
17771
+ res.set(comparisonLevel, resByLevel);
17772
+ });
17773
+ cache.lastUpdateParams = currentParams;
17774
+ return res;
17775
+ }, [
17776
+ visibleTasks,
17777
+ visibleTasksMirror,
17778
+ taskToRowIndexMap,
17779
+ startDate,
17780
+ viewMode,
17781
+ rtl,
17782
+ fullRowHeight,
17783
+ taskHeight,
17784
+ taskYOffset,
17785
+ distances,
17786
+ svgWidth,
17787
+ renderedRowIndexes,
17788
+ renderedColumnIndexes
17789
+ ]);
17790
+ };
17489
17791
  const fillMinAndMaxChildsMap = (resOnLevel, task, childTasksOnLevel) => {
17490
17792
  const childs = childTasksOnLevel.get(task.id);
17491
17793
  if (!childs || childs.length === 0) {
@@ -19105,6 +19407,11 @@ const Gantt = (props) => {
19105
19407
  () => getChildsAndRoots(sortedTasks, null),
19106
19408
  [sortedTasks]
19107
19409
  );
19410
+ const adaptiveSettings = useAdaptivePerformance({
19411
+ fpsThreshold: 30,
19412
+ renderTimeThreshold: 16
19413
+ // 60fps = 16ms per frame
19414
+ });
19108
19415
  const minAndMaxChildsMap = useMemo(
19109
19416
  () => getMinAndMaxChildsMap(rootTasksMap, childTasksMap),
19110
19417
  [rootTasksMap, childTasksMap]
@@ -19278,7 +19585,7 @@ const Gantt = (props) => {
19278
19585
  svgWidth
19279
19586
  ]
19280
19587
  );
19281
- const mapTaskToCoordinates = useMemo(
19588
+ const originalMapTaskToCoordinates = useMemo(
19282
19589
  () => getMapTaskToCoordinates(
19283
19590
  sortedTasks,
19284
19591
  visibleTasksMirror,
@@ -19306,6 +19613,24 @@ const Gantt = (props) => {
19306
19613
  visibleTasksMirror
19307
19614
  ]
19308
19615
  );
19616
+ const optimizedMapTaskToCoordinates = useIncrementalCoordinates(
19617
+ visibleTasks,
19618
+ visibleTasksMirror,
19619
+ taskToRowIndexMap,
19620
+ startDate,
19621
+ viewMode,
19622
+ rtl,
19623
+ fullRowHeight,
19624
+ taskHeight,
19625
+ taskYOffset,
19626
+ distances,
19627
+ svgWidth,
19628
+ null,
19629
+ // renderedRowIndexes - can be integrated later with virtualization
19630
+ null
19631
+ // renderedColumnIndexes - can be integrated later with virtualization
19632
+ );
19633
+ const mapTaskToCoordinates = adaptiveSettings.performanceLevel === "high" ? optimizedMapTaskToCoordinates : originalMapTaskToCoordinates;
19309
19634
  const scrollToTask = useCallback(
19310
19635
  (task) => {
19311
19636
  const { x1 } = getTaskCoordinates(task, mapTaskToCoordinates);
@@ -17503,6 +17503,308 @@
17503
17503
  scrollToRightStep
17504
17504
  ];
17505
17505
  };
17506
+ const usePerformanceMonitor = (enabled = true) => {
17507
+ const metricsRef = React.useRef([]);
17508
+ const benchmarksRef = React.useRef(/* @__PURE__ */ new Map());
17509
+ const frameTimesRef = React.useRef([]);
17510
+ const lastFrameTimeRef = React.useRef(performance.now());
17511
+ const updateFPS = React.useCallback(() => {
17512
+ if (!enabled)
17513
+ return;
17514
+ const now = performance.now();
17515
+ const deltaTime = now - lastFrameTimeRef.current;
17516
+ lastFrameTimeRef.current = now;
17517
+ frameTimesRef.current.push(deltaTime);
17518
+ if (frameTimesRef.current.length > 60) {
17519
+ frameTimesRef.current.shift();
17520
+ }
17521
+ }, [enabled]);
17522
+ const getFPS = React.useCallback(() => {
17523
+ if (frameTimesRef.current.length === 0)
17524
+ return 0;
17525
+ const avgFrameTime = frameTimesRef.current.reduce((sum, time) => sum + time, 0) / frameTimesRef.current.length;
17526
+ return Math.round(1e3 / avgFrameTime);
17527
+ }, []);
17528
+ const startMeasurement = React.useCallback(
17529
+ (name, metadata) => {
17530
+ if (!enabled)
17531
+ return;
17532
+ performance.mark(`${name}-start`);
17533
+ benchmarksRef.current.set(name, {
17534
+ name,
17535
+ startTime: performance.now(),
17536
+ metadata
17537
+ });
17538
+ },
17539
+ [enabled]
17540
+ );
17541
+ const endMeasurement = React.useCallback(
17542
+ (name) => {
17543
+ if (!enabled)
17544
+ return 0;
17545
+ const benchmark = benchmarksRef.current.get(name);
17546
+ if (!benchmark) {
17547
+ console.warn(`No benchmark found for ${name}`);
17548
+ return 0;
17549
+ }
17550
+ const endTime = performance.now();
17551
+ const duration = endTime - benchmark.startTime;
17552
+ performance.mark(`${name}-end`);
17553
+ performance.measure(name, `${name}-start`, `${name}-end`);
17554
+ benchmark.endTime = endTime;
17555
+ benchmark.duration = duration;
17556
+ return duration;
17557
+ },
17558
+ [enabled]
17559
+ );
17560
+ const measureFunction = React.useCallback(
17561
+ (name, fn, metadata) => {
17562
+ return (...args) => {
17563
+ if (!enabled)
17564
+ return fn(...args);
17565
+ startMeasurement(name, metadata);
17566
+ try {
17567
+ const result = fn(...args);
17568
+ return result;
17569
+ } finally {
17570
+ endMeasurement(name);
17571
+ }
17572
+ };
17573
+ },
17574
+ [enabled, startMeasurement, endMeasurement]
17575
+ );
17576
+ const measureAsyncFunction = React.useCallback(
17577
+ (name, fn, metadata) => {
17578
+ return async (...args) => {
17579
+ if (!enabled)
17580
+ return fn(...args);
17581
+ startMeasurement(name, metadata);
17582
+ try {
17583
+ const result = await fn(...args);
17584
+ return result;
17585
+ } finally {
17586
+ endMeasurement(name);
17587
+ }
17588
+ };
17589
+ },
17590
+ [enabled, startMeasurement, endMeasurement]
17591
+ );
17592
+ const getPerformanceReport = React.useCallback(() => {
17593
+ const entries = performance.getEntriesByType(
17594
+ "measure"
17595
+ );
17596
+ const report = {};
17597
+ entries.forEach((entry) => {
17598
+ if (!report[entry.name]) {
17599
+ report[entry.name] = {
17600
+ count: 0,
17601
+ totalTime: 0,
17602
+ avgTime: 0,
17603
+ maxTime: 0,
17604
+ minTime: Infinity
17605
+ };
17606
+ }
17607
+ const stat = report[entry.name];
17608
+ stat.count++;
17609
+ stat.totalTime += entry.duration;
17610
+ stat.maxTime = Math.max(stat.maxTime, entry.duration);
17611
+ stat.minTime = Math.min(stat.minTime, entry.duration);
17612
+ stat.avgTime = stat.totalTime / stat.count;
17613
+ });
17614
+ return {
17615
+ measurements: report,
17616
+ fps: getFPS(),
17617
+ currentMetrics: metricsRef.current[metricsRef.current.length - 1],
17618
+ allMetrics: [...metricsRef.current]
17619
+ };
17620
+ }, [getFPS]);
17621
+ const addMetrics = React.useCallback(
17622
+ (metrics) => {
17623
+ if (!enabled)
17624
+ return;
17625
+ const fullMetrics = {
17626
+ ...metrics,
17627
+ timestamp: Date.now()
17628
+ };
17629
+ metricsRef.current.push(fullMetrics);
17630
+ if (metricsRef.current.length > 100) {
17631
+ metricsRef.current.shift();
17632
+ }
17633
+ },
17634
+ [enabled]
17635
+ );
17636
+ const clearMeasurements = React.useCallback(() => {
17637
+ performance.clearMarks();
17638
+ performance.clearMeasures();
17639
+ benchmarksRef.current.clear();
17640
+ metricsRef.current.length = 0;
17641
+ frameTimesRef.current.length = 0;
17642
+ }, []);
17643
+ const getMemoryUsage = React.useCallback(() => {
17644
+ if ("memory" in performance) {
17645
+ const memory = performance.memory;
17646
+ return memory ? memory.usedJSHeapSize : 0;
17647
+ }
17648
+ return 0;
17649
+ }, []);
17650
+ return {
17651
+ startMeasurement,
17652
+ endMeasurement,
17653
+ measureFunction,
17654
+ measureAsyncFunction,
17655
+ getPerformanceReport,
17656
+ addMetrics,
17657
+ clearMeasurements,
17658
+ updateFPS,
17659
+ getFPS,
17660
+ getMemoryUsage,
17661
+ enabled
17662
+ };
17663
+ };
17664
+ const useAdaptivePerformance = (thresholds) => {
17665
+ const { getFPS, getPerformanceReport } = usePerformanceMonitor();
17666
+ const performanceLevelRef = React.useRef("high");
17667
+ const updatePerformanceLevel = React.useCallback(() => {
17668
+ var _a;
17669
+ const fps = getFPS();
17670
+ const report = getPerformanceReport();
17671
+ const renderTime = ((_a = report.measurements["render"]) == null ? void 0 : _a.avgTime) || 0;
17672
+ if (fps < thresholds.fpsThreshold * 0.5 || renderTime > thresholds.renderTimeThreshold * 2) {
17673
+ performanceLevelRef.current = "low";
17674
+ } else if (fps < thresholds.fpsThreshold || renderTime > thresholds.renderTimeThreshold) {
17675
+ performanceLevelRef.current = "medium";
17676
+ } else {
17677
+ performanceLevelRef.current = "high";
17678
+ }
17679
+ return performanceLevelRef.current;
17680
+ }, [getFPS, getPerformanceReport, thresholds]);
17681
+ const getQualitySettings = React.useCallback(() => {
17682
+ const level = performanceLevelRef.current;
17683
+ switch (level) {
17684
+ case "low":
17685
+ return {
17686
+ enableAnimations: false,
17687
+ renderQuality: "low",
17688
+ maxVisibleTasks: 100,
17689
+ updateFrequency: 500
17690
+ // ms
17691
+ };
17692
+ case "medium":
17693
+ return {
17694
+ enableAnimations: true,
17695
+ renderQuality: "medium",
17696
+ maxVisibleTasks: 500,
17697
+ updateFrequency: 100
17698
+ };
17699
+ case "high":
17700
+ default:
17701
+ return {
17702
+ enableAnimations: true,
17703
+ renderQuality: "high",
17704
+ maxVisibleTasks: 1e3,
17705
+ updateFrequency: 16
17706
+ // 60fps
17707
+ };
17708
+ }
17709
+ }, []);
17710
+ return {
17711
+ performanceLevel: performanceLevelRef.current,
17712
+ updatePerformanceLevel,
17713
+ getQualitySettings
17714
+ };
17715
+ };
17716
+ const useIncrementalCoordinates = (visibleTasks, visibleTasksMirror, taskToRowIndexMap, startDate, viewMode, rtl, fullRowHeight, taskHeight, taskYOffset, distances, svgWidth, renderedRowIndexes, renderedColumnIndexes) => {
17717
+ const cacheRef = React.useRef({
17718
+ coordinates: /* @__PURE__ */ new Map(),
17719
+ lastUpdateParams: null
17720
+ });
17721
+ return React.useMemo(() => {
17722
+ const cache = cacheRef.current;
17723
+ const currentParams = {
17724
+ startDate,
17725
+ viewMode,
17726
+ rtl,
17727
+ fullRowHeight,
17728
+ taskHeight,
17729
+ taskYOffset,
17730
+ svgWidth,
17731
+ distances
17732
+ };
17733
+ const needsFullRecalc = !cache.lastUpdateParams || cache.lastUpdateParams.startDate.getTime() !== startDate.getTime() || cache.lastUpdateParams.viewMode !== viewMode || cache.lastUpdateParams.rtl !== rtl || cache.lastUpdateParams.fullRowHeight !== fullRowHeight || cache.lastUpdateParams.taskHeight !== taskHeight || cache.lastUpdateParams.taskYOffset !== taskYOffset || cache.lastUpdateParams.svgWidth !== svgWidth || JSON.stringify(cache.lastUpdateParams.distances) !== JSON.stringify(distances);
17734
+ if (needsFullRecalc) {
17735
+ cache.coordinates.clear();
17736
+ }
17737
+ const tasksToCalculate = /* @__PURE__ */ new Set();
17738
+ if (renderedRowIndexes && renderedColumnIndexes) {
17739
+ const [startRow, endRow] = renderedRowIndexes;
17740
+ const rowBuffer = Math.max(5, Math.ceil((endRow - startRow) * 0.5));
17741
+ const bufferedStartRow = Math.max(0, startRow - rowBuffer);
17742
+ const bufferedEndRow = endRow + rowBuffer;
17743
+ visibleTasks.forEach((task, index2) => {
17744
+ if (task.type === "empty" || !visibleTasksMirror[task.id])
17745
+ return;
17746
+ if (index2 >= bufferedStartRow && index2 <= bufferedEndRow) {
17747
+ tasksToCalculate.add(task.id);
17748
+ }
17749
+ });
17750
+ } else {
17751
+ visibleTasks.forEach((task) => {
17752
+ if (task.type !== "empty" && visibleTasksMirror[task.id]) {
17753
+ tasksToCalculate.add(task.id);
17754
+ }
17755
+ });
17756
+ }
17757
+ if (!needsFullRecalc) {
17758
+ for (const [taskId] of cache.coordinates) {
17759
+ if (!tasksToCalculate.has(taskId)) {
17760
+ cache.coordinates.delete(taskId);
17761
+ }
17762
+ }
17763
+ }
17764
+ const res = /* @__PURE__ */ new Map();
17765
+ visibleTasks.forEach((task) => {
17766
+ if (task.type === "empty" || !tasksToCalculate.has(task.id))
17767
+ return;
17768
+ const { id, comparisonLevel = 1 } = task;
17769
+ const cacheKey = `${id}_${comparisonLevel}`;
17770
+ let taskCoordinates = cache.coordinates.get(cacheKey);
17771
+ if (!taskCoordinates || needsFullRecalc) {
17772
+ taskCoordinates = countTaskCoordinates(
17773
+ task,
17774
+ taskToRowIndexMap,
17775
+ startDate,
17776
+ viewMode,
17777
+ rtl,
17778
+ fullRowHeight,
17779
+ taskHeight,
17780
+ taskYOffset,
17781
+ distances,
17782
+ svgWidth
17783
+ );
17784
+ cache.coordinates.set(cacheKey, taskCoordinates);
17785
+ }
17786
+ const resByLevel = res.get(comparisonLevel) || /* @__PURE__ */ new Map();
17787
+ resByLevel.set(id, taskCoordinates);
17788
+ res.set(comparisonLevel, resByLevel);
17789
+ });
17790
+ cache.lastUpdateParams = currentParams;
17791
+ return res;
17792
+ }, [
17793
+ visibleTasks,
17794
+ visibleTasksMirror,
17795
+ taskToRowIndexMap,
17796
+ startDate,
17797
+ viewMode,
17798
+ rtl,
17799
+ fullRowHeight,
17800
+ taskHeight,
17801
+ taskYOffset,
17802
+ distances,
17803
+ svgWidth,
17804
+ renderedRowIndexes,
17805
+ renderedColumnIndexes
17806
+ ]);
17807
+ };
17506
17808
  const fillMinAndMaxChildsMap = (resOnLevel, task, childTasksOnLevel) => {
17507
17809
  const childs = childTasksOnLevel.get(task.id);
17508
17810
  if (!childs || childs.length === 0) {
@@ -19122,6 +19424,11 @@
19122
19424
  () => getChildsAndRoots(sortedTasks, null),
19123
19425
  [sortedTasks]
19124
19426
  );
19427
+ const adaptiveSettings = useAdaptivePerformance({
19428
+ fpsThreshold: 30,
19429
+ renderTimeThreshold: 16
19430
+ // 60fps = 16ms per frame
19431
+ });
19125
19432
  const minAndMaxChildsMap = React.useMemo(
19126
19433
  () => getMinAndMaxChildsMap(rootTasksMap, childTasksMap),
19127
19434
  [rootTasksMap, childTasksMap]
@@ -19295,7 +19602,7 @@
19295
19602
  svgWidth
19296
19603
  ]
19297
19604
  );
19298
- const mapTaskToCoordinates = React.useMemo(
19605
+ const originalMapTaskToCoordinates = React.useMemo(
19299
19606
  () => getMapTaskToCoordinates(
19300
19607
  sortedTasks,
19301
19608
  visibleTasksMirror,
@@ -19323,6 +19630,24 @@
19323
19630
  visibleTasksMirror
19324
19631
  ]
19325
19632
  );
19633
+ const optimizedMapTaskToCoordinates = useIncrementalCoordinates(
19634
+ visibleTasks,
19635
+ visibleTasksMirror,
19636
+ taskToRowIndexMap,
19637
+ startDate,
19638
+ viewMode,
19639
+ rtl,
19640
+ fullRowHeight,
19641
+ taskHeight,
19642
+ taskYOffset,
19643
+ distances,
19644
+ svgWidth,
19645
+ null,
19646
+ // renderedRowIndexes - can be integrated later with virtualization
19647
+ null
19648
+ // renderedColumnIndexes - can be integrated later with virtualization
19649
+ );
19650
+ const mapTaskToCoordinates = adaptiveSettings.performanceLevel === "high" ? optimizedMapTaskToCoordinates : originalMapTaskToCoordinates;
19326
19651
  const scrollToTask = React.useCallback(
19327
19652
  (task) => {
19328
19653
  const { x1 } = getTaskCoordinates(task, mapTaskToCoordinates);
@@ -0,0 +1,17 @@
1
+ import { Distances, MapTaskToCoordinates, TaskCoordinates, RenderTask, TaskToRowIndexMap, ViewMode } from "../types";
2
+ import type { OptimizedListParams } from "./use-optimized-list";
3
+ /**
4
+ * Optimized hook for calculating task coordinates incrementally
5
+ * Only recalculates coordinates for visible tasks and caches results
6
+ */
7
+ export declare const useIncrementalCoordinates: (visibleTasks: readonly RenderTask[], visibleTasksMirror: Readonly<Record<string, true>>, taskToRowIndexMap: TaskToRowIndexMap, startDate: Date, viewMode: ViewMode, rtl: boolean, fullRowHeight: number, taskHeight: number, taskYOffset: number, distances: Distances, svgWidth: number, renderedRowIndexes: OptimizedListParams | null, renderedColumnIndexes: OptimizedListParams | null) => MapTaskToCoordinates;
8
+ /**
9
+ * Hook for getting coordinates of tasks in a specific range only
10
+ * Useful for virtual scrolling scenarios
11
+ */
12
+ export declare const useRangedCoordinates: (tasks: readonly RenderTask[], taskToRowIndexMap: TaskToRowIndexMap, startDate: Date, viewMode: ViewMode, rtl: boolean, fullRowHeight: number, taskHeight: number, taskYOffset: number, distances: Distances, svgWidth: number, startIndex: number, endIndex: number, comparisonLevel?: number) => Map<string, TaskCoordinates>;
13
+ /**
14
+ * Hook for smart coordinate invalidation
15
+ * Tracks which tasks need coordinate recalculation based on their dependencies
16
+ */
17
+ export declare const useCoordinateInvalidation: (tasks: readonly RenderTask[], changedTaskIds: Set<string>) => Set<string>;
@@ -0,0 +1,122 @@
1
+ import { TaskCoordinates } from "../types";
2
+ /**
3
+ * Generic object pool for reusing objects to reduce garbage collection
4
+ */
5
+ declare class ObjectPool<T> {
6
+ private pool;
7
+ private createFn;
8
+ private resetFn?;
9
+ private maxSize;
10
+ constructor(createFn: () => T, resetFn?: (obj: T) => void, maxSize?: number);
11
+ acquire(): T;
12
+ release(obj: T): void;
13
+ clear(): void;
14
+ get size(): number;
15
+ }
16
+ /**
17
+ * Task coordinates object pool for reducing memory allocations
18
+ */
19
+ export declare const useTaskCoordinatesPool: (maxSize?: number) => {
20
+ acquire: () => TaskCoordinates;
21
+ release: (coords: TaskCoordinates) => void;
22
+ clear: () => void;
23
+ pool: ObjectPool<TaskCoordinates>;
24
+ };
25
+ /**
26
+ * Array pool for reusing arrays to reduce allocations
27
+ */
28
+ export declare const useArrayPool: <T>(maxSize?: number) => {
29
+ acquire: () => T[];
30
+ release: (arr: T[]) => void;
31
+ clear: () => void;
32
+ pool: ObjectPool<T[]>;
33
+ };
34
+ /**
35
+ * Map pool for reusing Map objects
36
+ */
37
+ export declare const useMapPool: <K, V>(maxSize?: number) => {
38
+ acquire: () => Map<K, V>;
39
+ release: (map: Map<K, V>) => void;
40
+ clear: () => void;
41
+ pool: ObjectPool<Map<K, V>>;
42
+ };
43
+ /**
44
+ * Set pool for reusing Set objects
45
+ */
46
+ export declare const useSetPool: <T>(maxSize?: number) => {
47
+ acquire: () => Set<T>;
48
+ release: (set: Set<T>) => void;
49
+ clear: () => void;
50
+ pool: ObjectPool<Set<T>>;
51
+ };
52
+ /**
53
+ * Memory manager that coordinates multiple object pools
54
+ */
55
+ export declare const useMemoryManager: () => {
56
+ coordinatesPool: {
57
+ acquire: () => TaskCoordinates;
58
+ release: (coords: TaskCoordinates) => void;
59
+ clear: () => void;
60
+ pool: ObjectPool<TaskCoordinates>;
61
+ };
62
+ arrayPool: {
63
+ acquire: () => unknown[];
64
+ release: (arr: unknown[]) => void;
65
+ clear: () => void;
66
+ pool: ObjectPool<unknown[]>;
67
+ };
68
+ mapPool: {
69
+ acquire: () => Map<unknown, unknown>;
70
+ release: (map: Map<unknown, unknown>) => void;
71
+ clear: () => void;
72
+ pool: ObjectPool<Map<unknown, unknown>>;
73
+ };
74
+ setPool: {
75
+ acquire: () => Set<unknown>;
76
+ release: (set: Set<unknown>) => void;
77
+ clear: () => void;
78
+ pool: ObjectPool<Set<unknown>>;
79
+ };
80
+ memoryStats: {
81
+ coordinatesPoolSize: number;
82
+ arrayPoolSize: number;
83
+ mapPoolSize: number;
84
+ setPoolSize: number;
85
+ };
86
+ clearAllPools: () => void;
87
+ forceGarbageCollection: () => void;
88
+ };
89
+ /**
90
+ * Memory-efficient batch processor that reuses objects
91
+ */
92
+ export declare const useBatchProcessor: <T, R>(batchSize?: number) => {
93
+ processBatch: (items: T[], processor: (batch: T[]) => Promise<R[]> | R[], onProgress?: (processed: number, total: number) => void) => Promise<R[]>;
94
+ };
95
+ /**
96
+ * Weak reference manager for preventing memory leaks
97
+ */
98
+ export declare const useWeakReferenceManager: () => {
99
+ addWeakRef: (obj: object) => WeakRef<object>;
100
+ cleanupDeadRefs: () => number;
101
+ getAliveRefCount: () => number;
102
+ totalRefs: number;
103
+ };
104
+ /**
105
+ * Memory monitoring hook for tracking memory usage
106
+ */
107
+ export declare const useMemoryMonitor: (intervalMs?: number) => {
108
+ memoryInfo: {
109
+ usedJSHeapSize: number;
110
+ totalJSHeapSize: number;
111
+ jsHeapSizeLimit: number;
112
+ };
113
+ updateMemoryInfo: () => void;
114
+ startMonitoring: () => () => void;
115
+ getMemoryPressure: () => number;
116
+ };
117
+ declare global {
118
+ interface Window {
119
+ gc?: () => void;
120
+ }
121
+ }
122
+ export {};
@@ -0,0 +1,24 @@
1
+ import { RenderTask, TaskMapByLevel, ChildByLevelMap, RootMapByLevel } from "../types";
2
+ export interface OptimizedDataStructures {
3
+ tasksByLevel: Map<number, RenderTask[]>;
4
+ tasksMap: TaskMapByLevel;
5
+ childTasksMap: ChildByLevelMap;
6
+ rootTasksMap: RootMapByLevel;
7
+ taskIndicesByLevel: Map<number, Map<string, number>>;
8
+ visibleTasksAtLevel: Map<number, RenderTask[]>;
9
+ taskParentMap: Map<string, string | null>;
10
+ taskDepthMap: Map<string, number>;
11
+ }
12
+ /**
13
+ * Pre-computes optimized data structures for faster lookups
14
+ * Reduces O(n) operations to O(1) for common queries
15
+ */
16
+ export declare const useOptimizedDataStructures: (tasks: readonly RenderTask[]) => OptimizedDataStructures;
17
+ /**
18
+ * Hook for getting tasks at specific level with O(1) access
19
+ */
20
+ export declare const useTasksAtLevel: (optimizedData: OptimizedDataStructures, level: number) => RenderTask[];
21
+ /**
22
+ * Hook for getting visible tasks in a specific range for virtualization
23
+ */
24
+ export declare const useVisibleTasksInRange: (optimizedData: OptimizedDataStructures, level: number, startIndex: number, endIndex: number) => RenderTask[];
@@ -0,0 +1,117 @@
1
+ import React from "react";
2
+ import { RenderTask, TaskCoordinates } from "../types";
3
+ export interface SVGBatch {
4
+ tasks: RenderTask[];
5
+ dependencies: string[];
6
+ selectedTasks: string[];
7
+ holidays: string[];
8
+ }
9
+ export interface SVGRenderContext {
10
+ additionalLeftSpace: number;
11
+ fullRowHeight: number;
12
+ taskHeight: number;
13
+ svgWidth: number;
14
+ svgHeight: number;
15
+ }
16
+ /**
17
+ * Hook for batched SVG rendering to reduce DOM operations
18
+ */
19
+ export declare const useBatchedSVGRenderer: () => {
20
+ clearBatch: () => void;
21
+ addTaskToBatch: (task: RenderTask) => void;
22
+ addDependencyToBatch: (dependency: string) => void;
23
+ addSelectedTaskToBatch: (selectedTask: string) => void;
24
+ addHolidayToBatch: (holiday: string) => void;
25
+ getBatchData: () => {
26
+ tasks: RenderTask[];
27
+ dependencies: string[];
28
+ selectedTasks: string[];
29
+ holidays: string[];
30
+ };
31
+ currentBatch: SVGBatch;
32
+ };
33
+ /**
34
+ * Hook for SVG element pooling to reuse DOM elements
35
+ */
36
+ export declare const useSVGElementPool: () => {
37
+ acquireRect: () => SVGRectElement;
38
+ releaseRect: (rect: SVGRectElement) => void;
39
+ acquirePath: () => SVGPathElement;
40
+ releasePath: (path: SVGPathElement) => void;
41
+ acquireText: () => SVGTextElement;
42
+ releaseText: (text: SVGTextElement) => void;
43
+ acquireGroup: () => SVGGElement;
44
+ releaseGroup: (group: SVGGElement) => void;
45
+ clearPools: () => void;
46
+ };
47
+ /**
48
+ * Hook for virtual SVG rendering - only renders visible elements
49
+ */
50
+ export declare const useVirtualSVGRenderer: (allTasks: RenderTask[], getTaskCoordinates: (task: RenderTask) => TaskCoordinates, viewportBounds: {
51
+ minX: number;
52
+ maxX: number;
53
+ minY: number;
54
+ maxY: number;
55
+ }) => {
56
+ visibleTasks: RenderTask[];
57
+ getVisibleTasksInRange: (startIndex: number, endIndex: number) => RenderTask[];
58
+ totalVisibleCount: number;
59
+ };
60
+ /**
61
+ * Hook for optimized SVG coordinate transformations
62
+ */
63
+ export declare const useSVGCoordinateTransforms: () => {
64
+ getTransform: (x: number, y: number) => string;
65
+ getScaleTransform: (scaleX: number, scaleY: number) => string;
66
+ clearTransformCache: () => void;
67
+ };
68
+ /**
69
+ * Hook for Level-of-Detail (LOD) rendering
70
+ * Reduces rendering complexity based on zoom level
71
+ */
72
+ export declare const useLODRenderer: (zoomLevel: number, taskCount: number) => {
73
+ showLabels: boolean;
74
+ showProgress: boolean;
75
+ showDependencies: boolean;
76
+ taskDetail: "minimal";
77
+ maxVisibleTasks: number;
78
+ } | {
79
+ showLabels: boolean;
80
+ showProgress: boolean;
81
+ showDependencies: boolean;
82
+ taskDetail: "reduced";
83
+ maxVisibleTasks: number;
84
+ } | {
85
+ showLabels: boolean;
86
+ showProgress: boolean;
87
+ showDependencies: boolean;
88
+ taskDetail: "full";
89
+ maxVisibleTasks: number;
90
+ };
91
+ /**
92
+ * Hook for efficient SVG path generation
93
+ */
94
+ export declare const useSVGPathGenerator: () => {
95
+ generateTaskPath: (x: number, y: number, width: number, height: number, cornerRadius?: number) => string;
96
+ generateProgressPath: (x: number, y: number, width: number, height: number, progressPercent: number) => string;
97
+ generateArrowPath: (fromX: number, fromY: number, toX: number, toY: number, arrowSize?: number) => string;
98
+ clearPathCache: () => void;
99
+ };
100
+ /**
101
+ * Hook for SVG performance optimization
102
+ */
103
+ export declare const useSVGPerformanceOptimizer: () => {
104
+ optimizeRenderList: (elements: React.ReactElement[]) => React.ReactElement<any, string | React.JSXElementConstructor<any>>[];
105
+ batchSVGUpdates: (updates: Array<() => void>) => void;
106
+ shouldSkipRender: (bounds: {
107
+ x: number;
108
+ y: number;
109
+ width?: number;
110
+ height?: number;
111
+ }, viewportBounds: {
112
+ minX: number;
113
+ maxX: number;
114
+ minY: number;
115
+ maxY: number;
116
+ }) => boolean;
117
+ };
@@ -0,0 +1,106 @@
1
+ export interface PerformanceMetrics {
2
+ renderTime: number;
3
+ coordinateCalculationTime: number;
4
+ dependencyCalculationTime: number;
5
+ sortingTime: number;
6
+ totalTasks: number;
7
+ visibleTasks: number;
8
+ fps: number;
9
+ memoryUsage: number;
10
+ timestamp: number;
11
+ }
12
+ export interface PerformanceBenchmark {
13
+ name: string;
14
+ startTime: number;
15
+ endTime?: number;
16
+ duration?: number;
17
+ metadata?: Record<string, unknown>;
18
+ }
19
+ /**
20
+ * Performance monitor for tracking Gantt chart performance metrics
21
+ */
22
+ export declare const usePerformanceMonitor: (enabled?: boolean) => {
23
+ startMeasurement: (name: string, metadata?: Record<string, unknown>) => void;
24
+ endMeasurement: (name: string) => number;
25
+ measureFunction: <T extends unknown[], R>(name: string, fn: (...args: T) => R, metadata?: Record<string, unknown>) => (...args: T) => R;
26
+ measureAsyncFunction: <T_1 extends unknown[], R_1>(name: string, fn: (...args: T_1) => Promise<R_1>, metadata?: Record<string, unknown>) => (...args: T_1) => Promise<R_1>;
27
+ getPerformanceReport: () => {
28
+ measurements: Record<string, {
29
+ count: number;
30
+ totalTime: number;
31
+ avgTime: number;
32
+ maxTime: number;
33
+ minTime: number;
34
+ }>;
35
+ fps: number;
36
+ currentMetrics: PerformanceMetrics;
37
+ allMetrics: PerformanceMetrics[];
38
+ };
39
+ addMetrics: (metrics: Omit<PerformanceMetrics, "timestamp">) => void;
40
+ clearMeasurements: () => void;
41
+ updateFPS: () => void;
42
+ getFPS: () => number;
43
+ getMemoryUsage: () => number;
44
+ enabled: boolean;
45
+ };
46
+ /**
47
+ * Hook for measuring render performance
48
+ */
49
+ export declare const useRenderPerformance: (componentName: string) => {
50
+ measureRender: (fn: () => void) => () => void;
51
+ renderCount: number;
52
+ lastRenderTime: number;
53
+ };
54
+ /**
55
+ * Hook for performance budgeting
56
+ */
57
+ export declare const usePerformanceBudget: (budgets: Record<string, number>) => {
58
+ checkBudgets: () => {
59
+ operation: string;
60
+ actual: number;
61
+ budget: number;
62
+ timestamp: number;
63
+ }[];
64
+ getBudgetStatus: () => Record<string, {
65
+ budget: number;
66
+ actual: number;
67
+ status: "ok" | "warning" | "violation";
68
+ }>;
69
+ violations: {
70
+ operation: string;
71
+ actual: number;
72
+ budget: number;
73
+ timestamp: number;
74
+ }[];
75
+ };
76
+ /**
77
+ * Hook for performance-aware rendering
78
+ * Automatically reduces quality when performance is poor
79
+ */
80
+ export declare const useAdaptivePerformance: (thresholds: {
81
+ fpsThreshold: number;
82
+ renderTimeThreshold: number;
83
+ }) => {
84
+ performanceLevel: "medium" | "high" | "low";
85
+ updatePerformanceLevel: () => "medium" | "high" | "low";
86
+ getQualitySettings: () => {
87
+ enableAnimations: boolean;
88
+ renderQuality: string;
89
+ maxVisibleTasks: number;
90
+ updateFrequency: number;
91
+ };
92
+ };
93
+ /**
94
+ * Hook for throttling expensive operations based on performance
95
+ */
96
+ export declare const usePerformanceThrottling: () => {
97
+ throttle: <T extends unknown[], R>(key: string, fn: (...args: T) => R, baseDelay?: number) => (...args: T) => R | undefined;
98
+ };
99
+ /**
100
+ * Development-only performance debugging utilities
101
+ */
102
+ export declare const usePerformanceDebugger: () => {
103
+ logPerformanceReport: () => void;
104
+ exportPerformanceData: () => void;
105
+ clearMeasurements: () => void;
106
+ };
@@ -0,0 +1,63 @@
1
+ import { RenderTask } from "../types";
2
+ import type { OptimizedListParams } from "./use-optimized-list";
3
+ export interface ProgressiveLoadingConfig {
4
+ /** Number of tasks to load per chunk */
5
+ chunkSize: number;
6
+ /** Buffer size for preloading (in chunks) */
7
+ preloadBuffer: number;
8
+ /** Function to load a chunk of tasks */
9
+ loadTaskChunk: (startIndex: number, endIndex: number) => Promise<RenderTask[]>;
10
+ /** Total number of tasks available */
11
+ totalTaskCount: number;
12
+ /** Enable progressive loading */
13
+ enabled: boolean;
14
+ }
15
+ export interface ProgressiveLoadingState {
16
+ /** Currently loaded tasks */
17
+ loadedTasks: readonly RenderTask[];
18
+ /** Loading state */
19
+ isLoading: boolean;
20
+ /** Error state */
21
+ error: Error | null;
22
+ /** Loaded chunk ranges */
23
+ loadedRanges: Set<string>;
24
+ /** Currently loading chunks */
25
+ loadingChunks: Set<string>;
26
+ }
27
+ /**
28
+ * Hook for progressive loading of large task datasets
29
+ * Loads tasks in chunks based on viewport visibility
30
+ */
31
+ export declare const useProgressiveLoading: (initialTasks: readonly RenderTask[], config: Partial<ProgressiveLoadingConfig>, renderedIndexes: OptimizedListParams | null) => ProgressiveLoadingState & {
32
+ /** Force load a specific range */
33
+ loadRange: (startIndex: number, endIndex: number) => Promise<void>;
34
+ /** Preload next chunks based on scroll direction */
35
+ preloadNext: (direction: "up" | "down") => Promise<void>;
36
+ /** Clear loaded data and reset */
37
+ reset: () => void;
38
+ };
39
+ /**
40
+ * Hook for chunked task processing
41
+ * Processes large task arrays in smaller chunks to avoid blocking the main thread
42
+ */
43
+ export declare const useChunkedProcessing: <T, R>(data: T[], processor: (chunk: T[]) => R[], chunkSize?: number) => {
44
+ processedData: R[];
45
+ isProcessing: boolean;
46
+ progress: number;
47
+ };
48
+ /**
49
+ * Enhanced progressive loading with smart caching
50
+ */
51
+ export declare const useSmartProgressiveLoading: (config: Partial<ProgressiveLoadingConfig> & {
52
+ /** Cache expiry time in milliseconds */
53
+ cacheExpiry?: number;
54
+ /** Maximum cache size (number of chunks) */
55
+ maxCacheSize?: number;
56
+ }, renderedIndexes: OptimizedListParams | null) => ProgressiveLoadingState & {
57
+ /** Force load a specific range */
58
+ loadRange: (startIndex: number, endIndex: number) => Promise<void>;
59
+ /** Preload next chunks based on scroll direction */
60
+ preloadNext: (direction: "up" | "down") => Promise<void>;
61
+ /** Clear loaded data and reset */
62
+ reset: () => void;
63
+ };
@@ -0,0 +1,49 @@
1
+ import { DependencyMap, DependentMap, DependencyMargins, ExpandedDependency, ExpandedDependent, MapTaskToCoordinates, TaskMapByLevel, RenderTask, TaskCoordinates } from "../types";
2
+ import type { OptimizedListParams } from "./use-optimized-list";
3
+ interface SpatialBounds {
4
+ minX: number;
5
+ maxX: number;
6
+ minY: number;
7
+ maxY: number;
8
+ }
9
+ interface SpatialNode {
10
+ taskId: string;
11
+ bounds: SpatialBounds;
12
+ dependencies: ExpandedDependency[];
13
+ dependents: ExpandedDependent[];
14
+ }
15
+ /**
16
+ * Spatial hash map for efficient spatial queries
17
+ * Divides space into grid cells for O(1) spatial lookups
18
+ */
19
+ declare class SpatialHashMap {
20
+ private cellSize;
21
+ private grid;
22
+ private taskNodes;
23
+ constructor(cellSize?: number);
24
+ private getBoundsFromCoordinates;
25
+ insert(taskId: string, coordinates: TaskCoordinates, dependencies?: ExpandedDependency[], dependents?: ExpandedDependent[]): void;
26
+ query(bounds: SpatialBounds): SpatialNode[];
27
+ private boundsIntersect;
28
+ getNode(taskId: string): SpatialNode | undefined;
29
+ clear(): void;
30
+ remove(taskId: string): void;
31
+ }
32
+ /**
33
+ * Hook for optimized dependency calculations using spatial indexing
34
+ */
35
+ export declare const useSpatialDependencyMap: (tasks: readonly RenderTask[], visibleTasksMirror: Readonly<Record<string, true>>, tasksMap: TaskMapByLevel, mapTaskToCoordinates: MapTaskToCoordinates, fullRowHeight: number, isShowCriticalPath: boolean, renderedRowIndexes: OptimizedListParams | null, renderedColumnIndexes: OptimizedListParams | null) => [DependencyMap, DependentMap, DependencyMargins, SpatialHashMap];
36
+ /**
37
+ * Hook for querying dependencies in a specific spatial region
38
+ */
39
+ export declare const useSpatialDependencyQuery: (spatialMap: SpatialHashMap, queryBounds: SpatialBounds) => SpatialNode[];
40
+ /**
41
+ * Hook for optimized dependency updates
42
+ * Only recalculates dependencies for tasks that actually changed
43
+ */
44
+ export declare const useIncrementalDependencyUpdate: (spatialMap: SpatialHashMap, changedTaskIds: Set<string>, allTasks: readonly RenderTask[]) => (newTaskCoordinates: Map<string, TaskCoordinates>) => void;
45
+ /**
46
+ * Utility function to create viewport bounds from rendered indexes
47
+ */
48
+ export declare const createViewportBounds: (renderedRowIndexes: OptimizedListParams | null, renderedColumnIndexes: OptimizedListParams | null, fullRowHeight: number, columnWidth: number) => SpatialBounds | null;
49
+ export {};
@@ -0,0 +1,50 @@
1
+ import { RenderTask, ViewMode, Distances, TaskCoordinates } from "../types";
2
+ export interface WorkerMessage {
3
+ id: string;
4
+ type: "calculate-coordinates" | "process-dependencies" | "sort-tasks" | "filter-tasks";
5
+ data: unknown;
6
+ }
7
+ export interface WorkerResponse {
8
+ id: string;
9
+ type: string;
10
+ result?: unknown;
11
+ error?: string;
12
+ }
13
+ export interface CoordinateCalculationData {
14
+ tasks: RenderTask[];
15
+ startDate: number;
16
+ viewMode: ViewMode;
17
+ rtl: boolean;
18
+ fullRowHeight: number;
19
+ taskHeight: number;
20
+ taskYOffset: number;
21
+ distances: Distances;
22
+ svgWidth: number;
23
+ }
24
+ /**
25
+ * Hook for managing Web Worker communications for heavy calculations
26
+ */
27
+ export declare const useGanttWebWorker: () => {
28
+ isWorkerAvailable: boolean;
29
+ sendMessage: (message: Omit<WorkerMessage, "id">, timeoutMs?: number) => Promise<unknown>;
30
+ };
31
+ /**
32
+ * Hook for calculating task coordinates using Web Worker
33
+ */
34
+ export declare const useWorkerCoordinateCalculation: (tasks: readonly RenderTask[], startDate: Date, viewMode: ViewMode, rtl: boolean, fullRowHeight: number, taskHeight: number, taskYOffset: number, distances: Distances, svgWidth: number) => {
35
+ coordinates: Map<string, TaskCoordinates>;
36
+ isCalculating: boolean;
37
+ recalculate: () => Promise<Map<string, TaskCoordinates>>;
38
+ };
39
+ /**
40
+ * Hook for sorting tasks using Web Worker
41
+ */
42
+ export declare const useWorkerTaskSorting: () => {
43
+ sortTasks: (tasks: RenderTask[]) => Promise<RenderTask[]>;
44
+ };
45
+ /**
46
+ * Hook for filtering tasks using Web Worker
47
+ */
48
+ export declare const useWorkerTaskFiltering: () => {
49
+ filterTasks: (tasks: RenderTask[], filterFn: string) => Promise<RenderTask[]>;
50
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gantt-task-react-v",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Interactive Gantt Chart for React with TypeScript.",
5
5
  "author": "aguilanbon",
6
6
  "homepage": "https://github.com/aguilanbon/gantt-task-react-v",
@@ -43,8 +43,9 @@
43
43
  "@floating-ui/dom": "1.6.3",
44
44
  "@floating-ui/react": "0.26.11",
45
45
  "date-fns": "3.6.0",
46
- "i18next": "^24.0.5",
47
- "react-i18next": "^15.1.3"
46
+ "i18next": "^25.5.2",
47
+ "react-i18next": "^15.7.3",
48
+ "react-window": "^2.1.1"
48
49
  },
49
50
  "peerDependencies": {
50
51
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc",
@@ -61,11 +62,11 @@
61
62
  "@storybook/addon-onboarding": "8.2.10",
62
63
  "@storybook/blocks": "8.2.10",
63
64
  "@storybook/cli": "8.2.10",
65
+ "@storybook/core-server": "8.2.10",
66
+ "@storybook/jest": "0.2.3",
64
67
  "@storybook/react": "8.2.10",
65
68
  "@storybook/react-vite": "8.2.10",
66
69
  "@storybook/test": "8.2.10",
67
- "@storybook/core-server": "8.2.10",
68
- "@storybook/jest": "0.2.3",
69
70
  "@storybook/test-runner": "0.23.0",
70
71
  "@storybook/testing-library": "^0.2.2",
71
72
  "@testing-library/jest-dom": "^6.4.2",
@@ -75,6 +76,7 @@
75
76
  "@types/node": "^20.12.4",
76
77
  "@types/react": "^18.2.74",
77
78
  "@types/react-dom": "^18.2.24",
79
+ "@types/react-window": "^1.8.8",
78
80
  "@typescript-eslint/eslint-plugin": "^7.5.0",
79
81
  "@typescript-eslint/parser": "^7.5.0",
80
82
  "@vitejs/plugin-react": "4.2.1",