gantt-lib 0.111.0 → 0.113.0

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.
package/dist/index.mjs CHANGED
@@ -3,7 +3,7 @@
3
3
  "use client";
4
4
 
5
5
  // src/components/GanttChart/GanttChart.tsx
6
- import { useMemo as useMemo12, useCallback as useCallback9, useRef as useRef10, useState as useState10, useEffect as useEffect9, useImperativeHandle, forwardRef } from "react";
6
+ import { useMemo as useMemo13, useCallback as useCallback10, useRef as useRef11, useState as useState11, useEffect as useEffect10, useImperativeHandle, forwardRef } from "react";
7
7
 
8
8
  // src/core/scheduling/dateMath.ts
9
9
  var DAY_MS = 24 * 60 * 60 * 1e3;
@@ -557,10 +557,10 @@ function buildTaskRangeFromStart(startDate, duration, businessDays = false, week
557
557
  end: parseDateOnly(addBusinessDays(normalizedStart, duration, weekendPredicate))
558
558
  };
559
559
  }
560
- const DAY_MS5 = 24 * 60 * 60 * 1e3;
560
+ const DAY_MS6 = 24 * 60 * 60 * 1e3;
561
561
  return {
562
562
  start: normalizedStart,
563
- end: new Date(normalizedStart.getTime() + (Math.max(1, duration) - 1) * DAY_MS5)
563
+ end: new Date(normalizedStart.getTime() + (Math.max(1, duration) - 1) * DAY_MS6)
564
564
  };
565
565
  }
566
566
  function buildTaskRangeFromEnd(endDate, duration, businessDays = false, weekendPredicate, snapDirection = -1) {
@@ -571,9 +571,9 @@ function buildTaskRangeFromEnd(endDate, duration, businessDays = false, weekendP
571
571
  end: normalizedEnd
572
572
  };
573
573
  }
574
- const DAY_MS5 = 24 * 60 * 60 * 1e3;
574
+ const DAY_MS6 = 24 * 60 * 60 * 1e3;
575
575
  return {
576
- start: new Date(normalizedEnd.getTime() - (Math.max(1, duration) - 1) * DAY_MS5),
576
+ start: new Date(normalizedEnd.getTime() - (Math.max(1, duration) - 1) * DAY_MS6),
577
577
  end: normalizedEnd
578
578
  };
579
579
  }
@@ -988,13 +988,13 @@ function computeParentProgress(parentId, tasks) {
988
988
  if (children.length === 0) {
989
989
  return 0;
990
990
  }
991
- const DAY_MS5 = 24 * 60 * 60 * 1e3;
991
+ const DAY_MS6 = 24 * 60 * 60 * 1e3;
992
992
  let totalWeight = 0;
993
993
  let weightedSum = 0;
994
994
  for (const child of children) {
995
995
  const start = new Date(child.startDate).getTime();
996
996
  const end = new Date(child.endDate).getTime();
997
- const duration = (end - start + DAY_MS5) / DAY_MS5;
997
+ const duration = (end - start + DAY_MS6) / DAY_MS6;
998
998
  const progress = child.progress ?? 0;
999
999
  totalWeight += duration;
1000
1000
  weightedSum += duration * progress;
@@ -10447,6 +10447,1001 @@ function TableMatrix({
10447
10447
  ] });
10448
10448
  }
10449
10449
 
10450
+ // src/components/PlanFactMatrix/PlanFactMatrix.tsx
10451
+ import React15, { useCallback as useCallback9, useEffect as useEffect9, useMemo as useMemo12, useRef as useRef10, useState as useState10 } from "react";
10452
+ import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
10453
+ var DAY_MS5 = 24 * 60 * 60 * 1e3;
10454
+ function joinClasses2(...values) {
10455
+ return values.filter(Boolean).join(" ");
10456
+ }
10457
+ function formatDateKey(date) {
10458
+ const year = date.getUTCFullYear();
10459
+ const month = String(date.getUTCMonth() + 1).padStart(2, "0");
10460
+ const day = String(date.getUTCDate()).padStart(2, "0");
10461
+ return `${year}-${month}-${day}`;
10462
+ }
10463
+ function escapeAttributeValue(value) {
10464
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
10465
+ }
10466
+ function parseNumberInput(value) {
10467
+ const trimmed = value.trim();
10468
+ if (trimmed === "") return void 0;
10469
+ const normalized = trimmed.replace(/\s+/g, "").replace(",", ".");
10470
+ const parsed = Number(normalized);
10471
+ if (!Number.isFinite(parsed) || parsed < 0) {
10472
+ return null;
10473
+ }
10474
+ return parsed;
10475
+ }
10476
+ function getDateOnlyMs(date) {
10477
+ return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
10478
+ }
10479
+ function getPlannedIndexRange(task, rangeStartMs, rangeLength) {
10480
+ if (rangeLength <= 0) return null;
10481
+ const start = parseUTCDate(task.startDate);
10482
+ const end = parseUTCDate(task.endDate);
10483
+ const startIndex = Math.ceil((getDateOnlyMs(start) - rangeStartMs) / DAY_MS5);
10484
+ const endIndex = Math.floor((getDateOnlyMs(end) - rangeStartMs) / DAY_MS5);
10485
+ const clampedStartIndex = Math.max(0, startIndex);
10486
+ const clampedEndIndex = Math.min(rangeLength - 1, endIndex);
10487
+ if (clampedStartIndex > clampedEndIndex) return null;
10488
+ return {
10489
+ startIndex: clampedStartIndex,
10490
+ endIndex: clampedEndIndex
10491
+ };
10492
+ }
10493
+ function isDateIndexWithinPlannedRange(dateIndex, range) {
10494
+ return !!range && range.startIndex <= dateIndex && dateIndex <= range.endIndex;
10495
+ }
10496
+ function formatValue(value) {
10497
+ if (value === void 0) return "";
10498
+ const absoluteValue = Math.abs(value);
10499
+ if (absoluteValue >= 1e6) {
10500
+ const compactValue = value / 1e6;
10501
+ const fractionDigits = absoluteValue >= 1e7 ? 0 : 1;
10502
+ return `${compactValue.toLocaleString("ru-RU", {
10503
+ minimumFractionDigits: 0,
10504
+ maximumFractionDigits: fractionDigits,
10505
+ useGrouping: false
10506
+ })}m`;
10507
+ }
10508
+ if (absoluteValue >= 1e4) {
10509
+ const compactValue = value / 1e3;
10510
+ const fractionDigits = absoluteValue >= 1e5 ? 0 : 1;
10511
+ return `${compactValue.toLocaleString("ru-RU", {
10512
+ minimumFractionDigits: 0,
10513
+ maximumFractionDigits: fractionDigits,
10514
+ useGrouping: false
10515
+ })}k`;
10516
+ }
10517
+ return Number.isInteger(value) ? String(value) : String(value).replace(".", ",");
10518
+ }
10519
+ function formatTooltipValue(value) {
10520
+ if (value === void 0) return "";
10521
+ return value.toLocaleString("ru-RU", { maximumFractionDigits: 20 });
10522
+ }
10523
+ function getSubrowIndex(taskIndex, kind) {
10524
+ return taskIndex * 2 + (kind === "plan" ? 0 : 1);
10525
+ }
10526
+ function findEditableTaskIndex(tasks, parentTaskIds, startIndex, step) {
10527
+ let index = startIndex;
10528
+ while (index >= 0 && index < tasks.length) {
10529
+ if (!parentTaskIds.has(tasks[index].id)) {
10530
+ return index;
10531
+ }
10532
+ index += step;
10533
+ }
10534
+ return null;
10535
+ }
10536
+ function PlanFactCellEditor({
10537
+ value,
10538
+ startValue,
10539
+ onCommit,
10540
+ onCommitRange,
10541
+ onCancel
10542
+ }) {
10543
+ const [draft, setDraft] = useState10(startValue ?? (value === void 0 ? "" : String(value)));
10544
+ const inputRef = useRef10(null);
10545
+ useEffect9(() => {
10546
+ inputRef.current?.focus();
10547
+ if (startValue === void 0) {
10548
+ inputRef.current?.select();
10549
+ } else {
10550
+ inputRef.current?.setSelectionRange(startValue.length, startValue.length);
10551
+ }
10552
+ }, [startValue]);
10553
+ const commit = useCallback9(() => {
10554
+ const parsed = parseNumberInput(draft);
10555
+ if (parsed === null) {
10556
+ onCancel();
10557
+ return;
10558
+ }
10559
+ onCommit(parsed);
10560
+ }, [draft, onCancel, onCommit]);
10561
+ return /* @__PURE__ */ jsx18(
10562
+ "input",
10563
+ {
10564
+ ref: inputRef,
10565
+ value: draft,
10566
+ className: "gantt-pf-editor",
10567
+ inputMode: "decimal",
10568
+ onChange: (event) => setDraft(event.target.value),
10569
+ onBlur: commit,
10570
+ onKeyDown: (event) => {
10571
+ if (event.key === "Escape") {
10572
+ event.preventDefault();
10573
+ onCancel();
10574
+ return;
10575
+ }
10576
+ if (event.key === "Enter") {
10577
+ event.preventDefault();
10578
+ if (event.ctrlKey || event.metaKey) {
10579
+ const parsed = parseNumberInput(draft);
10580
+ if (parsed === null) {
10581
+ onCancel();
10582
+ return;
10583
+ }
10584
+ onCommitRange?.(parsed);
10585
+ } else {
10586
+ commit();
10587
+ }
10588
+ }
10589
+ }
10590
+ }
10591
+ );
10592
+ }
10593
+ function getCellSignatureForTask(cell, taskId) {
10594
+ return cell?.taskId === taskId ? `${cell.dateIndex}:${cell.kind}` : "";
10595
+ }
10596
+ function getEditingCellSignatureForTask(cell, taskId) {
10597
+ return cell?.taskId === taskId ? `${cell.dateIndex}:${cell.kind}:${cell.startValue ?? ""}` : "";
10598
+ }
10599
+ function getRangeAnchorSignatureForTask(range, taskId) {
10600
+ return range?.anchor.taskId === taskId ? `${range.anchor.dateIndex}:${range.anchor.kind}` : "";
10601
+ }
10602
+ function doesRangeTouchRow(bounds, rowIndex) {
10603
+ if (!bounds) return false;
10604
+ const firstSubrowIndex = rowIndex * 2;
10605
+ const lastSubrowIndex = firstSubrowIndex + 1;
10606
+ return bounds.fromSubrowIndex <= lastSubrowIndex && bounds.toSubrowIndex >= firstSubrowIndex;
10607
+ }
10608
+ function areRangeBoundsEqual(left, right) {
10609
+ if (left === right) return true;
10610
+ if (!left || !right) return false;
10611
+ return left.fromDateIndex === right.fromDateIndex && left.toDateIndex === right.toDateIndex && left.fromSubrowIndex === right.fromSubrowIndex && left.toSubrowIndex === right.toSubrowIndex;
10612
+ }
10613
+ function PlanFactRowInner({
10614
+ task,
10615
+ rowIndex,
10616
+ dateRange,
10617
+ dateKeys,
10618
+ renderedDateIndices,
10619
+ rowHeight,
10620
+ subrowHeight,
10621
+ dayWidth,
10622
+ plannedRange,
10623
+ todayDateIndex,
10624
+ isParent,
10625
+ isHighlighted,
10626
+ selectedTaskId,
10627
+ activeCell,
10628
+ editingCell,
10629
+ selectedRange,
10630
+ renderedRangeBounds,
10631
+ didDragSelectRef,
10632
+ isSelectingRef,
10633
+ isFillDraggingRef,
10634
+ onTaskSelect,
10635
+ selectSingleCell,
10636
+ queueHoverCellUpdate,
10637
+ setActiveCell,
10638
+ setEditingCell,
10639
+ setFillRange,
10640
+ clearSelectedCells,
10641
+ commitCell,
10642
+ commitSelectedCells,
10643
+ moveActiveCell,
10644
+ extendSelectedRange,
10645
+ focusCell,
10646
+ showOverflowTooltip,
10647
+ hideOverflowTooltip
10648
+ }) {
10649
+ return /* @__PURE__ */ jsx18(
10650
+ "div",
10651
+ {
10652
+ "data-gantt-task-row-id": task.id,
10653
+ className: joinClasses2(
10654
+ "gantt-pf-row",
10655
+ isParent && "gantt-pf-row-parent",
10656
+ selectedTaskId === task.id && "gantt-pf-row-selected",
10657
+ isHighlighted && "gantt-pf-row-highlighted"
10658
+ ),
10659
+ style: {
10660
+ top: `${rowIndex * rowHeight}px`,
10661
+ height: `${rowHeight}px`,
10662
+ gridTemplateColumns: `repeat(${dateRange.length}, ${dayWidth}px)`,
10663
+ ["--gantt-pf-today-left"]: todayDateIndex !== void 0 && todayDateIndex >= 0 ? `${todayDateIndex * dayWidth}px` : void 0
10664
+ },
10665
+ onClick: () => onTaskSelect?.(task.id),
10666
+ children: renderedDateIndices.map((dateIndex) => {
10667
+ const dateKey = dateKeys[dateIndex];
10668
+ if (dateKey === void 0) return null;
10669
+ const planned = isDateIndexWithinPlannedRange(dateIndex, plannedRange);
10670
+ return ["plan", "fact"].map((kind) => {
10671
+ const subrowIndex = getSubrowIndex(rowIndex, kind);
10672
+ const isInRenderedRange = !!renderedRangeBounds && dateIndex >= renderedRangeBounds.fromDateIndex && dateIndex <= renderedRangeBounds.toDateIndex && subrowIndex >= renderedRangeBounds.fromSubrowIndex && subrowIndex <= renderedRangeBounds.toSubrowIndex;
10673
+ const planValue = task.planByDate?.[dateKey];
10674
+ const factValue = task.factByDate?.[dateKey];
10675
+ const value = kind === "plan" ? planValue : factValue;
10676
+ const factStatus = factValue === void 0 || planValue === void 0 ? null : factValue >= planValue ? "success" : "warning";
10677
+ const isActive = activeCell?.taskId === task.id && activeCell.dateIndex === dateIndex && activeCell.kind === kind;
10678
+ const isEditing = editingCell?.taskId === task.id && editingCell.dateIndex === dateIndex && editingCell.kind === kind;
10679
+ const currentCell = { taskId: task.id, dateIndex, kind };
10680
+ const isSelected = !isParent && isInRenderedRange;
10681
+ const showFillHandle = !isParent && !isEditing && isInRenderedRange && renderedRangeBounds !== null && dateIndex === renderedRangeBounds.toDateIndex && subrowIndex === renderedRangeBounds.toSubrowIndex;
10682
+ const isRangeAnchor = !showFillHandle && !isParent && selectedRange?.anchor.taskId === task.id && selectedRange.anchor.dateIndex === dateIndex && selectedRange.anchor.kind === kind;
10683
+ return /* @__PURE__ */ jsxs14(
10684
+ "div",
10685
+ {
10686
+ "data-plan-fact-task-id": task.id,
10687
+ "data-plan-fact-date-index": dateIndex,
10688
+ "data-plan-fact-kind": kind,
10689
+ className: joinClasses2(
10690
+ "gantt-pf-cell",
10691
+ `gantt-pf-cell-${kind}`,
10692
+ planned && kind === "plan" && "gantt-pf-cell-planned",
10693
+ value !== void 0 && "gantt-pf-cell-hasValue",
10694
+ kind === "fact" && factStatus === "success" && "gantt-pf-cell-factSuccess",
10695
+ kind === "fact" && factStatus === "warning" && "gantt-pf-cell-factWarning",
10696
+ isSelected && "gantt-pf-cell-selected",
10697
+ isInRenderedRange && renderedRangeBounds !== null && dateIndex === renderedRangeBounds.fromDateIndex && "gantt-pf-cell-rangeLeft",
10698
+ isInRenderedRange && renderedRangeBounds !== null && dateIndex === renderedRangeBounds.toDateIndex && "gantt-pf-cell-rangeRight",
10699
+ isInRenderedRange && renderedRangeBounds !== null && subrowIndex === renderedRangeBounds.fromSubrowIndex && "gantt-pf-cell-rangeTop",
10700
+ isInRenderedRange && renderedRangeBounds !== null && subrowIndex === renderedRangeBounds.toSubrowIndex && "gantt-pf-cell-rangeBottom",
10701
+ isRangeAnchor && "gantt-pf-cell-rangeAnchor",
10702
+ isActive && "gantt-pf-cell-active",
10703
+ isEditing && "gantt-pf-cell-editing",
10704
+ isParent && "gantt-pf-cell-readonly"
10705
+ ),
10706
+ style: {
10707
+ gridColumn: dateIndex + 1,
10708
+ gridRow: kind === "plan" ? 1 : 2,
10709
+ height: `${subrowHeight}px`
10710
+ },
10711
+ tabIndex: isParent ? -1 : 0,
10712
+ onMouseDown: (event) => {
10713
+ if (isParent) return;
10714
+ event.preventDefault();
10715
+ event.stopPropagation();
10716
+ didDragSelectRef.current = false;
10717
+ isSelectingRef.current = true;
10718
+ selectSingleCell(currentCell);
10719
+ onTaskSelect?.(task.id);
10720
+ event.currentTarget.focus();
10721
+ },
10722
+ onMouseEnter: () => {
10723
+ if (isParent) return;
10724
+ if (!isFillDraggingRef.current && !isSelectingRef.current) return;
10725
+ queueHoverCellUpdate(currentCell);
10726
+ },
10727
+ onFocus: () => {
10728
+ if (isParent) return;
10729
+ setActiveCell({ taskId: task.id, dateIndex, kind });
10730
+ },
10731
+ onClick: (event) => {
10732
+ event.stopPropagation();
10733
+ if (didDragSelectRef.current) {
10734
+ didDragSelectRef.current = false;
10735
+ return;
10736
+ }
10737
+ onTaskSelect?.(task.id);
10738
+ if (isParent) return;
10739
+ selectSingleCell(currentCell);
10740
+ event.currentTarget.focus();
10741
+ },
10742
+ onDoubleClick: (event) => {
10743
+ event.stopPropagation();
10744
+ if (isParent) return;
10745
+ setEditingCell({ taskId: task.id, dateIndex, kind });
10746
+ },
10747
+ onKeyDown: (event) => {
10748
+ if (isParent || isEditing) return;
10749
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight" || event.key === "ArrowUp" || event.key === "ArrowDown") {
10750
+ event.preventDefault();
10751
+ event.stopPropagation();
10752
+ const direction = event.key.replace("Arrow", "").toLowerCase();
10753
+ if (event.shiftKey) {
10754
+ extendSelectedRange(selectedRange?.focus ?? currentCell, direction);
10755
+ } else {
10756
+ moveActiveCell(currentCell, direction);
10757
+ }
10758
+ return;
10759
+ }
10760
+ if (event.key === "Enter" || event.key === "F2") {
10761
+ event.preventDefault();
10762
+ event.stopPropagation();
10763
+ setEditingCell(selectedRange?.anchor ?? currentCell);
10764
+ return;
10765
+ }
10766
+ if (event.key === "Backspace" || event.key === "Delete") {
10767
+ event.preventDefault();
10768
+ event.stopPropagation();
10769
+ clearSelectedCells();
10770
+ return;
10771
+ }
10772
+ if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
10773
+ event.preventDefault();
10774
+ event.stopPropagation();
10775
+ setEditingCell({ ...currentCell, startValue: event.key });
10776
+ }
10777
+ },
10778
+ children: [
10779
+ isEditing ? /* @__PURE__ */ jsx18(
10780
+ PlanFactCellEditor,
10781
+ {
10782
+ value,
10783
+ startValue: editingCell.startValue,
10784
+ onCommit: (nextValue) => {
10785
+ commitCell(task, dateIndex, kind, nextValue);
10786
+ setEditingCell(null);
10787
+ const nextActiveCell = { taskId: task.id, dateIndex, kind };
10788
+ selectSingleCell(nextActiveCell);
10789
+ focusCell(nextActiveCell);
10790
+ },
10791
+ onCommitRange: (nextValue) => {
10792
+ commitSelectedCells(nextValue);
10793
+ setEditingCell(null);
10794
+ const nextActiveCell = { taskId: task.id, dateIndex, kind };
10795
+ selectSingleCell(nextActiveCell);
10796
+ focusCell(nextActiveCell);
10797
+ },
10798
+ onCancel: () => {
10799
+ setEditingCell(null);
10800
+ const nextActiveCell = { taskId: task.id, dateIndex, kind };
10801
+ selectSingleCell(nextActiveCell);
10802
+ focusCell(nextActiveCell);
10803
+ }
10804
+ }
10805
+ ) : /* @__PURE__ */ jsx18(
10806
+ "span",
10807
+ {
10808
+ className: "gantt-pf-cellValue",
10809
+ onMouseEnter: (event) => {
10810
+ if (isParent || value === void 0) return;
10811
+ const compactValue = formatValue(value);
10812
+ const fullValue = formatTooltipValue(value);
10813
+ showOverflowTooltip(event.currentTarget, fullValue, compactValue !== fullValue);
10814
+ },
10815
+ onMouseLeave: hideOverflowTooltip,
10816
+ children: isParent ? "" : formatValue(value)
10817
+ }
10818
+ ),
10819
+ showFillHandle && /* @__PURE__ */ jsx18(
10820
+ "span",
10821
+ {
10822
+ className: "gantt-pf-fillHandle",
10823
+ "aria-hidden": "true",
10824
+ onMouseDown: (event) => {
10825
+ event.preventDefault();
10826
+ event.stopPropagation();
10827
+ isSelectingRef.current = false;
10828
+ isFillDraggingRef.current = true;
10829
+ setFillRange(selectedRange);
10830
+ }
10831
+ }
10832
+ )
10833
+ ]
10834
+ },
10835
+ `${task.id}:${dateKey}:${kind}`
10836
+ );
10837
+ });
10838
+ })
10839
+ }
10840
+ );
10841
+ }
10842
+ function arePlanFactRowsEqual(previous, next) {
10843
+ const previousRangeTouchesRow = doesRangeTouchRow(previous.renderedRangeBounds, previous.rowIndex);
10844
+ const nextRangeTouchesRow = doesRangeTouchRow(next.renderedRangeBounds, next.rowIndex);
10845
+ return previous.task === next.task && previous.rowIndex === next.rowIndex && previous.dateRange === next.dateRange && previous.dateKeys === next.dateKeys && previous.renderedDateIndices === next.renderedDateIndices && previous.rowHeight === next.rowHeight && previous.subrowHeight === next.subrowHeight && previous.dayWidth === next.dayWidth && previous.plannedRange === next.plannedRange && previous.todayDateIndex === next.todayDateIndex && previous.isParent === next.isParent && previous.isHighlighted === next.isHighlighted && previous.selectedTaskId === previous.task.id === (next.selectedTaskId === next.task.id) && getCellSignatureForTask(previous.activeCell, previous.task.id) === getCellSignatureForTask(next.activeCell, next.task.id) && getEditingCellSignatureForTask(previous.editingCell, previous.task.id) === getEditingCellSignatureForTask(next.editingCell, next.task.id) && getRangeAnchorSignatureForTask(previous.selectedRange, previous.task.id) === getRangeAnchorSignatureForTask(next.selectedRange, next.task.id) && previousRangeTouchesRow === nextRangeTouchesRow && (!nextRangeTouchesRow || areRangeBoundsEqual(previous.renderedRangeBounds, next.renderedRangeBounds));
10846
+ }
10847
+ var PlanFactRow = React15.memo(PlanFactRowInner, arePlanFactRowsEqual);
10848
+ function PlanFactMatrix({
10849
+ tasks,
10850
+ allTasks = tasks,
10851
+ dateRange,
10852
+ dayWidth,
10853
+ rowHeight,
10854
+ headerHeight,
10855
+ bodyMinHeight,
10856
+ selectedTaskId,
10857
+ onTaskSelect,
10858
+ onTasksChange,
10859
+ onCellCommit,
10860
+ highlightedTaskIds,
10861
+ filterMode = "highlight",
10862
+ visibleRowIndices,
10863
+ visibleDateIndices,
10864
+ todayDateIndex
10865
+ }) {
10866
+ const [activeCell, setActiveCell] = useState10(null);
10867
+ const [editingCell, setEditingCell] = useState10(null);
10868
+ const [selectedRange, setSelectedRange] = useState10(null);
10869
+ const [fillRange, setFillRange] = useState10(null);
10870
+ const [overflowTooltip, setOverflowTooltip] = useState10(null);
10871
+ const isSelectingRef = useRef10(false);
10872
+ const isFillDraggingRef = useRef10(false);
10873
+ const didDragSelectRef = useRef10(false);
10874
+ const pendingHoverCellRef = useRef10(null);
10875
+ const hoverFrameRef = useRef10(null);
10876
+ const rootRef = useRef10(null);
10877
+ const bodyRef = useRef10(null);
10878
+ const totalWidth = dateRange.length * dayWidth;
10879
+ const subrowHeight = rowHeight / 2;
10880
+ const renderedRowIndices = useMemo12(
10881
+ () => visibleRowIndices ?? tasks.map((_, index) => index),
10882
+ [tasks, visibleRowIndices]
10883
+ );
10884
+ const renderedDateIndices = useMemo12(
10885
+ () => visibleDateIndices ?? dateRange.map((_, index) => index),
10886
+ [dateRange, visibleDateIndices]
10887
+ );
10888
+ const parentTaskIds = useMemo12(() => {
10889
+ const ids = /* @__PURE__ */ new Set();
10890
+ for (const task of allTasks) {
10891
+ if (task.parentId) ids.add(task.parentId);
10892
+ }
10893
+ return ids;
10894
+ }, [allTasks]);
10895
+ const dateKeys = useMemo12(() => dateRange.map(formatDateKey), [dateRange]);
10896
+ const dateRangeStartMs = dateRange[0] ? getDateOnlyMs(dateRange[0]) : 0;
10897
+ const taskIndexById = useMemo12(() => {
10898
+ const indexById = /* @__PURE__ */ new Map();
10899
+ tasks.forEach((task, index) => indexById.set(task.id, index));
10900
+ return indexById;
10901
+ }, [tasks]);
10902
+ const taskById = useMemo12(
10903
+ () => new Map(tasks.map((task) => [task.id, task])),
10904
+ [tasks]
10905
+ );
10906
+ const monthSeparatorIndices = useMemo12(() => {
10907
+ const indices = [];
10908
+ for (let index = 1; index < dateRange.length; index += 1) {
10909
+ if (dateRange[index].getUTCDate() === 1) {
10910
+ indices.push(index);
10911
+ }
10912
+ }
10913
+ return indices;
10914
+ }, [dateRange]);
10915
+ const plannedRangeByTaskId = useMemo12(() => {
10916
+ const rangeByTaskId = /* @__PURE__ */ new Map();
10917
+ for (const task of tasks) {
10918
+ rangeByTaskId.set(task.id, getPlannedIndexRange(task, dateRangeStartMs, dateRange.length));
10919
+ }
10920
+ return rangeByTaskId;
10921
+ }, [dateRange.length, dateRangeStartMs, tasks]);
10922
+ const focusCell = useCallback9((cell) => {
10923
+ window.requestAnimationFrame(() => {
10924
+ const selector = [
10925
+ `[data-plan-fact-task-id="${escapeAttributeValue(cell.taskId)}"]`,
10926
+ `[data-plan-fact-date-index="${cell.dateIndex}"]`,
10927
+ `[data-plan-fact-kind="${cell.kind}"]`
10928
+ ].join("");
10929
+ bodyRef.current?.querySelector(selector)?.focus();
10930
+ });
10931
+ }, []);
10932
+ const clearSelection = useCallback9(() => {
10933
+ if (hoverFrameRef.current !== null) {
10934
+ window.cancelAnimationFrame(hoverFrameRef.current);
10935
+ hoverFrameRef.current = null;
10936
+ }
10937
+ pendingHoverCellRef.current = null;
10938
+ isSelectingRef.current = false;
10939
+ isFillDraggingRef.current = false;
10940
+ didDragSelectRef.current = false;
10941
+ setActiveCell(null);
10942
+ setEditingCell(null);
10943
+ setSelectedRange(null);
10944
+ setFillRange(null);
10945
+ setOverflowTooltip(null);
10946
+ }, []);
10947
+ const hideOverflowTooltip = useCallback9(() => {
10948
+ setOverflowTooltip(null);
10949
+ }, []);
10950
+ const flushPendingHoverCell = useCallback9(() => {
10951
+ hoverFrameRef.current = null;
10952
+ const currentCell = pendingHoverCellRef.current;
10953
+ if (!currentCell) return;
10954
+ if (isFillDraggingRef.current && selectedRange) {
10955
+ setFillRange({ anchor: selectedRange.anchor, focus: currentCell });
10956
+ setActiveCell(currentCell);
10957
+ onTaskSelect?.(currentCell.taskId);
10958
+ return;
10959
+ }
10960
+ if (!isSelectingRef.current) return;
10961
+ didDragSelectRef.current = true;
10962
+ setSelectedRange((currentRange) => ({
10963
+ anchor: currentRange?.anchor ?? currentCell,
10964
+ focus: currentCell
10965
+ }));
10966
+ onTaskSelect?.(currentCell.taskId);
10967
+ }, [onTaskSelect, selectedRange]);
10968
+ const queueHoverCellUpdate = useCallback9((cell) => {
10969
+ pendingHoverCellRef.current = cell;
10970
+ if (hoverFrameRef.current !== null) return;
10971
+ hoverFrameRef.current = window.requestAnimationFrame(flushPendingHoverCell);
10972
+ }, [flushPendingHoverCell]);
10973
+ const showOverflowTooltip = useCallback9((target, label, force = false) => {
10974
+ if (!rootRef.current || !label) return;
10975
+ if (!force && target.scrollWidth <= target.clientWidth) {
10976
+ setOverflowTooltip(null);
10977
+ return;
10978
+ }
10979
+ const rootRect = rootRef.current.getBoundingClientRect();
10980
+ const targetRect = target.getBoundingClientRect();
10981
+ setOverflowTooltip({
10982
+ label,
10983
+ left: targetRect.left - rootRect.left + targetRect.width / 2,
10984
+ top: targetRect.top - rootRect.top
10985
+ });
10986
+ }, []);
10987
+ const selectSingleCell = useCallback9((cell) => {
10988
+ setActiveCell(cell);
10989
+ setSelectedRange({ anchor: cell, focus: cell });
10990
+ setFillRange(null);
10991
+ }, []);
10992
+ const getRangeBounds = useCallback9((range) => {
10993
+ const anchorTaskIndex = taskIndexById.get(range.anchor.taskId);
10994
+ const focusTaskIndex = taskIndexById.get(range.focus.taskId);
10995
+ if (anchorTaskIndex === void 0 || focusTaskIndex === void 0) {
10996
+ return null;
10997
+ }
10998
+ return {
10999
+ fromDateIndex: Math.min(range.anchor.dateIndex, range.focus.dateIndex),
11000
+ toDateIndex: Math.max(range.anchor.dateIndex, range.focus.dateIndex),
11001
+ fromSubrowIndex: Math.min(
11002
+ getSubrowIndex(anchorTaskIndex, range.anchor.kind),
11003
+ getSubrowIndex(focusTaskIndex, range.focus.kind)
11004
+ ),
11005
+ toSubrowIndex: Math.max(
11006
+ getSubrowIndex(anchorTaskIndex, range.anchor.kind),
11007
+ getSubrowIndex(focusTaskIndex, range.focus.kind)
11008
+ )
11009
+ };
11010
+ }, [taskIndexById]);
11011
+ const renderedRange = fillRange ?? selectedRange;
11012
+ const renderedRangeBounds = useMemo12(
11013
+ () => renderedRange ? getRangeBounds(renderedRange) : null,
11014
+ [getRangeBounds, renderedRange]
11015
+ );
11016
+ const getCellFromPosition = useCallback9((subrowIndex, dateIndex) => {
11017
+ const task = tasks[Math.floor(subrowIndex / 2)];
11018
+ if (!task) return null;
11019
+ return {
11020
+ taskId: task.id,
11021
+ dateIndex,
11022
+ kind: subrowIndex % 2 === 0 ? "plan" : "fact"
11023
+ };
11024
+ }, [tasks]);
11025
+ const isCellInRange = useCallback9((cell, range) => {
11026
+ const bounds = getRangeBounds(range);
11027
+ if (!bounds) return false;
11028
+ const cellTaskIndex = taskIndexById.get(cell.taskId);
11029
+ if (cellTaskIndex === void 0) {
11030
+ return false;
11031
+ }
11032
+ const cellSubrowIndex = getSubrowIndex(cellTaskIndex, cell.kind);
11033
+ return cell.dateIndex >= bounds.fromDateIndex && cell.dateIndex <= bounds.toDateIndex && cellSubrowIndex >= bounds.fromSubrowIndex && cellSubrowIndex <= bounds.toSubrowIndex;
11034
+ }, [getRangeBounds, taskIndexById]);
11035
+ const commitCell = useCallback9((task, dateIndex, kind, value) => {
11036
+ const dateKey = dateKeys[dateIndex];
11037
+ const source = kind === "plan" ? task.planByDate : task.factByDate;
11038
+ const nextValues = { ...source ?? {} };
11039
+ if (value === void 0) {
11040
+ delete nextValues[dateKey];
11041
+ } else {
11042
+ nextValues[dateKey] = value;
11043
+ }
11044
+ const changedTask = {
11045
+ ...task,
11046
+ [kind === "plan" ? "planByDate" : "factByDate"]: nextValues
11047
+ };
11048
+ onTasksChange?.([changedTask]);
11049
+ onCellCommit?.({
11050
+ task,
11051
+ date: dateRange[dateIndex],
11052
+ dateKey,
11053
+ kind,
11054
+ value
11055
+ });
11056
+ }, [dateKeys, dateRange, onCellCommit, onTasksChange]);
11057
+ const commitRangeCells = useCallback9((bounds, value, mode) => {
11058
+ const changedTasksById = /* @__PURE__ */ new Map();
11059
+ for (let subrowIndex = bounds.fromSubrowIndex; subrowIndex <= bounds.toSubrowIndex; subrowIndex += 1) {
11060
+ const task = tasks[Math.floor(subrowIndex / 2)];
11061
+ if (!task || parentTaskIds.has(task.id)) continue;
11062
+ const kind = subrowIndex % 2 === 0 ? "plan" : "fact";
11063
+ const currentChangedTask = changedTasksById.get(task.id) ?? task;
11064
+ let nextPlanByDate = currentChangedTask.planByDate;
11065
+ let nextFactByDate = currentChangedTask.factByDate;
11066
+ let didChange = false;
11067
+ for (let dateIndex = bounds.fromDateIndex; dateIndex <= bounds.toDateIndex; dateIndex += 1) {
11068
+ const dateKey = dateKeys[dateIndex];
11069
+ if (dateKey === void 0) continue;
11070
+ const currentValues = kind === "plan" ? nextPlanByDate : nextFactByDate;
11071
+ const currentValue = currentValues?.[dateKey];
11072
+ const nextValue = mode === "clear" ? void 0 : value;
11073
+ if (currentValue === nextValue) continue;
11074
+ if (kind === "plan") {
11075
+ nextPlanByDate = { ...nextPlanByDate ?? {} };
11076
+ if (nextValue === void 0) {
11077
+ delete nextPlanByDate[dateKey];
11078
+ } else {
11079
+ nextPlanByDate[dateKey] = nextValue;
11080
+ }
11081
+ } else {
11082
+ nextFactByDate = { ...nextFactByDate ?? {} };
11083
+ if (nextValue === void 0) {
11084
+ delete nextFactByDate[dateKey];
11085
+ } else {
11086
+ nextFactByDate[dateKey] = nextValue;
11087
+ }
11088
+ }
11089
+ didChange = true;
11090
+ }
11091
+ if (didChange) {
11092
+ changedTasksById.set(task.id, {
11093
+ ...currentChangedTask,
11094
+ ...nextPlanByDate !== currentChangedTask.planByDate ? { planByDate: nextPlanByDate ?? {} } : {},
11095
+ ...nextFactByDate !== currentChangedTask.factByDate ? { factByDate: nextFactByDate ?? {} } : {}
11096
+ });
11097
+ }
11098
+ }
11099
+ const changedTasks = Array.from(changedTasksById.values());
11100
+ if (changedTasks.length > 0) {
11101
+ onTasksChange?.(changedTasks);
11102
+ }
11103
+ }, [dateKeys, onTasksChange, parentTaskIds, tasks]);
11104
+ const clearSelectedCells = useCallback9(() => {
11105
+ const activeRange = fillRange ?? selectedRange;
11106
+ if (!activeRange) {
11107
+ if (!activeCell) return;
11108
+ const task = taskById.get(activeCell.taskId);
11109
+ if (!task || parentTaskIds.has(task.id)) return;
11110
+ commitCell(task, activeCell.dateIndex, activeCell.kind, void 0);
11111
+ return;
11112
+ }
11113
+ const bounds = getRangeBounds(activeRange);
11114
+ if (bounds) {
11115
+ commitRangeCells(bounds, void 0, "clear");
11116
+ }
11117
+ }, [activeCell, commitCell, commitRangeCells, fillRange, getRangeBounds, parentTaskIds, selectedRange, taskById]);
11118
+ const commitSelectedCells = useCallback9((value) => {
11119
+ const activeRange = fillRange ?? selectedRange;
11120
+ if (!activeRange) {
11121
+ if (!activeCell) return;
11122
+ const task = taskById.get(activeCell.taskId);
11123
+ if (!task || parentTaskIds.has(task.id)) return;
11124
+ commitCell(task, activeCell.dateIndex, activeCell.kind, value);
11125
+ return;
11126
+ }
11127
+ const bounds = getRangeBounds(activeRange);
11128
+ if (bounds) {
11129
+ commitRangeCells(bounds, value, "set");
11130
+ }
11131
+ }, [activeCell, commitCell, commitRangeCells, fillRange, getRangeBounds, parentTaskIds, selectedRange, taskById]);
11132
+ const getCellValue = useCallback9((cell) => {
11133
+ const task = taskById.get(cell.taskId);
11134
+ if (!task) return void 0;
11135
+ const dateKey = dateKeys[cell.dateIndex];
11136
+ return cell.kind === "plan" ? task.planByDate?.[dateKey] : task.factByDate?.[dateKey];
11137
+ }, [dateKeys, taskById]);
11138
+ const applyFillRange = useCallback9((nextFillRange) => {
11139
+ const targetRange = nextFillRange ?? fillRange;
11140
+ if (!selectedRange || !targetRange) return;
11141
+ const sourceBounds = getRangeBounds(selectedRange);
11142
+ const targetBounds = getRangeBounds(targetRange);
11143
+ if (!sourceBounds || !targetBounds) return;
11144
+ const sourceDateSpan = sourceBounds.toDateIndex - sourceBounds.fromDateIndex + 1;
11145
+ const sourceSubrowSpan = sourceBounds.toSubrowIndex - sourceBounds.fromSubrowIndex + 1;
11146
+ const changedTasksById = /* @__PURE__ */ new Map();
11147
+ for (let subrowIndex = targetBounds.fromSubrowIndex; subrowIndex <= targetBounds.toSubrowIndex; subrowIndex += 1) {
11148
+ const targetCellForRow = getCellFromPosition(subrowIndex, targetBounds.fromDateIndex);
11149
+ if (!targetCellForRow || parentTaskIds.has(targetCellForRow.taskId)) continue;
11150
+ const originalTask = taskById.get(targetCellForRow.taskId);
11151
+ if (!originalTask) continue;
11152
+ let changedTask = changedTasksById.get(originalTask.id) ?? originalTask;
11153
+ let nextPlanByDate = changedTask.planByDate;
11154
+ let nextFactByDate = changedTask.factByDate;
11155
+ let didChange = false;
11156
+ for (let dateIndex = targetBounds.fromDateIndex; dateIndex <= targetBounds.toDateIndex; dateIndex += 1) {
11157
+ const targetCell = getCellFromPosition(subrowIndex, dateIndex);
11158
+ if (!targetCell || isCellInRange(targetCell, selectedRange)) continue;
11159
+ const sourceSubrowIndex = sourceBounds.fromSubrowIndex + ((subrowIndex - sourceBounds.fromSubrowIndex) % sourceSubrowSpan + sourceSubrowSpan) % sourceSubrowSpan;
11160
+ const sourceDateIndex = sourceBounds.fromDateIndex + ((dateIndex - sourceBounds.fromDateIndex) % sourceDateSpan + sourceDateSpan) % sourceDateSpan;
11161
+ const sourceCell = getCellFromPosition(sourceSubrowIndex, sourceDateIndex);
11162
+ if (!sourceCell) continue;
11163
+ const nextValue = getCellValue(sourceCell);
11164
+ const dateKey = dateKeys[dateIndex];
11165
+ const currentValues = targetCell.kind === "plan" ? nextPlanByDate : nextFactByDate;
11166
+ if (currentValues?.[dateKey] === nextValue) continue;
11167
+ if (targetCell.kind === "plan") {
11168
+ nextPlanByDate = { ...nextPlanByDate ?? {} };
11169
+ if (nextValue === void 0) {
11170
+ delete nextPlanByDate[dateKey];
11171
+ } else {
11172
+ nextPlanByDate[dateKey] = nextValue;
11173
+ }
11174
+ } else {
11175
+ nextFactByDate = { ...nextFactByDate ?? {} };
11176
+ if (nextValue === void 0) {
11177
+ delete nextFactByDate[dateKey];
11178
+ } else {
11179
+ nextFactByDate[dateKey] = nextValue;
11180
+ }
11181
+ }
11182
+ didChange = true;
11183
+ }
11184
+ if (didChange) {
11185
+ changedTasksById.set(originalTask.id, {
11186
+ ...changedTask,
11187
+ ...nextPlanByDate !== changedTask.planByDate ? { planByDate: nextPlanByDate ?? {} } : {},
11188
+ ...nextFactByDate !== changedTask.factByDate ? { factByDate: nextFactByDate ?? {} } : {}
11189
+ });
11190
+ }
11191
+ }
11192
+ const changedTasks = Array.from(changedTasksById.values());
11193
+ if (changedTasks.length > 0) {
11194
+ onTasksChange?.(changedTasks);
11195
+ }
11196
+ setSelectedRange(targetRange);
11197
+ setActiveCell(targetRange.anchor);
11198
+ setFillRange(null);
11199
+ }, [
11200
+ dateKeys,
11201
+ fillRange,
11202
+ getCellFromPosition,
11203
+ getCellValue,
11204
+ getRangeBounds,
11205
+ isCellInRange,
11206
+ onTasksChange,
11207
+ parentTaskIds,
11208
+ selectedRange,
11209
+ taskById
11210
+ ]);
11211
+ const moveActiveCell = useCallback9((cell, direction) => {
11212
+ const taskIndex = tasks.findIndex((task) => task.id === cell.taskId);
11213
+ if (taskIndex < 0) return;
11214
+ let nextCell = { ...cell };
11215
+ if (direction === "left") {
11216
+ nextCell.dateIndex = Math.max(0, cell.dateIndex - 1);
11217
+ } else if (direction === "right") {
11218
+ nextCell.dateIndex = Math.min(dateRange.length - 1, cell.dateIndex + 1);
11219
+ } else if (direction === "up") {
11220
+ if (cell.kind === "fact") {
11221
+ nextCell.kind = "plan";
11222
+ } else {
11223
+ const previousEditableTaskIndex = findEditableTaskIndex(tasks, parentTaskIds, taskIndex - 1, -1);
11224
+ if (previousEditableTaskIndex === null) return;
11225
+ const previousTask = tasks[previousEditableTaskIndex];
11226
+ nextCell = { taskId: previousTask.id, dateIndex: cell.dateIndex, kind: "fact" };
11227
+ }
11228
+ } else if (direction === "down") {
11229
+ if (cell.kind === "plan") {
11230
+ nextCell.kind = "fact";
11231
+ } else {
11232
+ const nextEditableTaskIndex = findEditableTaskIndex(tasks, parentTaskIds, taskIndex + 1, 1);
11233
+ if (nextEditableTaskIndex === null) return;
11234
+ const nextTask = tasks[nextEditableTaskIndex];
11235
+ nextCell = { taskId: nextTask.id, dateIndex: cell.dateIndex, kind: "plan" };
11236
+ }
11237
+ }
11238
+ setActiveCell(nextCell);
11239
+ setSelectedRange({ anchor: nextCell, focus: nextCell });
11240
+ onTaskSelect?.(nextCell.taskId);
11241
+ focusCell(nextCell);
11242
+ }, [dateRange.length, focusCell, onTaskSelect, parentTaskIds, tasks]);
11243
+ const extendSelectedRange = useCallback9((cell, direction) => {
11244
+ const taskIndex = tasks.findIndex((task) => task.id === cell.taskId);
11245
+ if (taskIndex < 0) return;
11246
+ let nextCell = { ...cell };
11247
+ if (direction === "left") {
11248
+ nextCell.dateIndex = Math.max(0, cell.dateIndex - 1);
11249
+ } else if (direction === "right") {
11250
+ nextCell.dateIndex = Math.min(dateRange.length - 1, cell.dateIndex + 1);
11251
+ } else if (direction === "up") {
11252
+ if (cell.kind === "fact") {
11253
+ nextCell.kind = "plan";
11254
+ } else {
11255
+ const previousEditableTaskIndex = findEditableTaskIndex(tasks, parentTaskIds, taskIndex - 1, -1);
11256
+ if (previousEditableTaskIndex === null) return;
11257
+ const previousTask = tasks[previousEditableTaskIndex];
11258
+ nextCell = { taskId: previousTask.id, dateIndex: cell.dateIndex, kind: "fact" };
11259
+ }
11260
+ } else if (direction === "down") {
11261
+ if (cell.kind === "plan") {
11262
+ nextCell.kind = "fact";
11263
+ } else {
11264
+ const nextEditableTaskIndex = findEditableTaskIndex(tasks, parentTaskIds, taskIndex + 1, 1);
11265
+ if (nextEditableTaskIndex === null) return;
11266
+ const nextTask = tasks[nextEditableTaskIndex];
11267
+ nextCell = { taskId: nextTask.id, dateIndex: cell.dateIndex, kind: "plan" };
11268
+ }
11269
+ }
11270
+ setActiveCell((currentActiveCell) => currentActiveCell ?? cell);
11271
+ setFillRange(null);
11272
+ setSelectedRange((currentRange) => ({
11273
+ anchor: currentRange?.anchor ?? cell,
11274
+ focus: nextCell
11275
+ }));
11276
+ onTaskSelect?.(nextCell.taskId);
11277
+ focusCell(selectedRange?.anchor ?? cell);
11278
+ }, [dateRange.length, focusCell, onTaskSelect, parentTaskIds, selectedRange, tasks]);
11279
+ useEffect9(() => {
11280
+ const endSelection = () => {
11281
+ let pendingFillRange = null;
11282
+ if (hoverFrameRef.current !== null) {
11283
+ window.cancelAnimationFrame(hoverFrameRef.current);
11284
+ hoverFrameRef.current = null;
11285
+ const pendingCell = pendingHoverCellRef.current;
11286
+ if (pendingCell && isFillDraggingRef.current && selectedRange) {
11287
+ pendingFillRange = { anchor: selectedRange.anchor, focus: pendingCell };
11288
+ }
11289
+ flushPendingHoverCell();
11290
+ }
11291
+ if (isFillDraggingRef.current) {
11292
+ isFillDraggingRef.current = false;
11293
+ applyFillRange(pendingFillRange);
11294
+ }
11295
+ isSelectingRef.current = false;
11296
+ };
11297
+ window.addEventListener("mouseup", endSelection);
11298
+ return () => {
11299
+ window.removeEventListener("mouseup", endSelection);
11300
+ };
11301
+ }, [applyFillRange, flushPendingHoverCell]);
11302
+ useEffect9(() => () => {
11303
+ if (hoverFrameRef.current !== null) {
11304
+ window.cancelAnimationFrame(hoverFrameRef.current);
11305
+ }
11306
+ }, []);
11307
+ useEffect9(() => {
11308
+ const handleKeyDown = (event) => {
11309
+ if (event.key !== "Escape") return;
11310
+ clearSelection();
11311
+ };
11312
+ const handleMouseDown = (event) => {
11313
+ const target = event.target;
11314
+ if (target instanceof Node && rootRef.current?.contains(target)) {
11315
+ return;
11316
+ }
11317
+ clearSelection();
11318
+ };
11319
+ window.addEventListener("keydown", handleKeyDown);
11320
+ document.addEventListener("mousedown", handleMouseDown);
11321
+ return () => {
11322
+ window.removeEventListener("keydown", handleKeyDown);
11323
+ document.removeEventListener("mousedown", handleMouseDown);
11324
+ };
11325
+ }, [clearSelection]);
11326
+ return /* @__PURE__ */ jsxs14(
11327
+ "div",
11328
+ {
11329
+ ref: rootRef,
11330
+ className: "gantt-pf-root",
11331
+ style: {
11332
+ width: `${totalWidth}px`,
11333
+ ["--gantt-pf-day-width"]: `${dayWidth}px`
11334
+ },
11335
+ children: [
11336
+ /* @__PURE__ */ jsxs14("div", { className: "gantt-pf-header", style: { width: `${totalWidth}px`, height: `${headerHeight}px` }, children: [
11337
+ /* @__PURE__ */ jsx18(
11338
+ TimeScaleHeader_default,
11339
+ {
11340
+ days: dateRange,
11341
+ dayWidth,
11342
+ headerHeight: headerHeight - 1,
11343
+ viewMode: "day"
11344
+ }
11345
+ ),
11346
+ todayDateIndex !== void 0 && todayDateIndex >= 0 && /* @__PURE__ */ jsx18(
11347
+ "span",
11348
+ {
11349
+ className: "gantt-pf-headerTodayLine",
11350
+ "aria-hidden": "true",
11351
+ style: {
11352
+ left: `${todayDateIndex * dayWidth}px`,
11353
+ top: `${Math.max(0, headerHeight / 2)}px`
11354
+ }
11355
+ }
11356
+ )
11357
+ ] }),
11358
+ /* @__PURE__ */ jsx18("div", { className: "gantt-pf-monthSeparatorLayer", "aria-hidden": "true", children: monthSeparatorIndices.map((dateIndex) => /* @__PURE__ */ jsx18(
11359
+ "span",
11360
+ {
11361
+ className: "gantt-pf-monthSeparator",
11362
+ style: {
11363
+ left: `${Math.round(dateIndex * dayWidth)}px`,
11364
+ top: `${Math.max(0, headerHeight / 2)}px`
11365
+ }
11366
+ },
11367
+ `month-separator-${dateIndex}`
11368
+ )) }),
11369
+ /* @__PURE__ */ jsx18(
11370
+ "div",
11371
+ {
11372
+ ref: bodyRef,
11373
+ className: "gantt-pf-body",
11374
+ style: {
11375
+ height: `${tasks.length * rowHeight}px`,
11376
+ minHeight: bodyMinHeight,
11377
+ width: `${totalWidth}px`
11378
+ },
11379
+ children: renderedRowIndices.map((rowIndex) => {
11380
+ const task = tasks[rowIndex];
11381
+ if (!task) return null;
11382
+ const isParent = parentTaskIds.has(task.id);
11383
+ const isHighlighted = filterMode === "highlight" && !!highlightedTaskIds?.has(task.id);
11384
+ return /* @__PURE__ */ jsx18(
11385
+ PlanFactRow,
11386
+ {
11387
+ task,
11388
+ rowIndex,
11389
+ dateRange,
11390
+ dateKeys,
11391
+ renderedDateIndices,
11392
+ rowHeight,
11393
+ subrowHeight,
11394
+ dayWidth,
11395
+ plannedRange: plannedRangeByTaskId.get(task.id) ?? null,
11396
+ todayDateIndex,
11397
+ isParent,
11398
+ isHighlighted,
11399
+ selectedTaskId,
11400
+ activeCell,
11401
+ editingCell,
11402
+ selectedRange,
11403
+ renderedRangeBounds,
11404
+ didDragSelectRef,
11405
+ isSelectingRef,
11406
+ isFillDraggingRef,
11407
+ onTaskSelect,
11408
+ selectSingleCell,
11409
+ queueHoverCellUpdate,
11410
+ setActiveCell,
11411
+ setEditingCell,
11412
+ setFillRange,
11413
+ clearSelectedCells,
11414
+ commitCell,
11415
+ commitSelectedCells,
11416
+ moveActiveCell,
11417
+ extendSelectedRange,
11418
+ focusCell,
11419
+ showOverflowTooltip,
11420
+ hideOverflowTooltip
11421
+ },
11422
+ task.id
11423
+ );
11424
+ })
11425
+ }
11426
+ ),
11427
+ overflowTooltip && /* @__PURE__ */ jsx18(
11428
+ "div",
11429
+ {
11430
+ className: "gantt-pf-overflowTooltip",
11431
+ role: "tooltip",
11432
+ "aria-hidden": "true",
11433
+ style: {
11434
+ left: `${overflowTooltip.left}px`,
11435
+ top: `${overflowTooltip.top}px`
11436
+ },
11437
+ children: overflowTooltip.label
11438
+ }
11439
+ )
11440
+ ]
11441
+ }
11442
+ );
11443
+ }
11444
+
10450
11445
  // src/components/GanttChart/print.ts
10451
11446
  function getPrintDocumentTitle({
10452
11447
  header,
@@ -10855,9 +11850,60 @@ function createTaskPreviewPositionStore() {
10855
11850
  }
10856
11851
 
10857
11852
  // src/components/GanttChart/GanttChart.tsx
10858
- import { Fragment as Fragment4, jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
11853
+ import { Fragment as Fragment4, jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
10859
11854
  var SCROLL_TO_ROW_CONTEXT_ROWS = 2;
10860
11855
  var TASK_ROW_OVERSCAN = 8;
11856
+ var PLAN_FACT_COLUMN_OVERSCAN = 24;
11857
+ var PLAN_FACT_COLUMN_WINDOW_STEP = 14;
11858
+ function getFullMonthDays(tasks) {
11859
+ if (!tasks || tasks.length === 0) {
11860
+ return getMultiMonthDays(tasks);
11861
+ }
11862
+ let minDate = null;
11863
+ let maxDate = null;
11864
+ for (const task of tasks) {
11865
+ const start = parseUTCDate(task.startDate);
11866
+ const end = parseUTCDate(task.endDate);
11867
+ if (!minDate || start.getTime() < minDate.getTime()) {
11868
+ minDate = start;
11869
+ }
11870
+ if (!maxDate || end.getTime() > maxDate.getTime()) {
11871
+ maxDate = end;
11872
+ }
11873
+ }
11874
+ if (!minDate || !maxDate) {
11875
+ return getMultiMonthDays(tasks);
11876
+ }
11877
+ const startOfMonth2 = new Date(Date.UTC(minDate.getUTCFullYear(), minDate.getUTCMonth(), 1));
11878
+ const endOfMonth = new Date(Date.UTC(maxDate.getUTCFullYear(), maxDate.getUTCMonth() + 1, 0));
11879
+ const days = [];
11880
+ const current = new Date(startOfMonth2);
11881
+ while (current.getTime() <= endOfMonth.getTime()) {
11882
+ days.push(new Date(Date.UTC(
11883
+ current.getUTCFullYear(),
11884
+ current.getUTCMonth(),
11885
+ current.getUTCDate()
11886
+ )));
11887
+ current.setUTCDate(current.getUTCDate() + 1);
11888
+ }
11889
+ return days;
11890
+ }
11891
+ function getPlanFactRangeTasks(tasks) {
11892
+ const rangeTasks = [];
11893
+ for (const task of tasks) {
11894
+ rangeTasks.push({ startDate: task.startDate, endDate: task.endDate });
11895
+ for (const dateKey of Object.keys(task.planByDate ?? {})) {
11896
+ rangeTasks.push({ startDate: dateKey, endDate: dateKey });
11897
+ }
11898
+ for (const dateKey of Object.keys(task.factByDate ?? {})) {
11899
+ rangeTasks.push({ startDate: dateKey, endDate: dateKey });
11900
+ }
11901
+ }
11902
+ return rangeTasks;
11903
+ }
11904
+ function clampScrollValue(value, max) {
11905
+ return Math.min(max, Math.max(0, value));
11906
+ }
10861
11907
  function arePositionMapsEqual(left, right) {
10862
11908
  if (left.size !== right.size) return false;
10863
11909
  for (const [taskId, leftPosition] of left) {
@@ -10894,9 +11940,9 @@ function arePreviewTaskMapsEqual(left, right) {
10894
11940
  }
10895
11941
  function GanttChartInner(props, ref) {
10896
11942
  if (props.mode === "resource-planner") {
10897
- return /* @__PURE__ */ jsx18(ResourceTimelineChart, { ...props });
11943
+ return /* @__PURE__ */ jsx19(ResourceTimelineChart, { ...props });
10898
11944
  }
10899
- return /* @__PURE__ */ jsx18(
11945
+ return /* @__PURE__ */ jsx19(
10900
11946
  TaskGanttChart,
10901
11947
  {
10902
11948
  ...props,
@@ -10906,6 +11952,7 @@ function GanttChartInner(props, ref) {
10906
11952
  }
10907
11953
  function TaskGanttChartInner(props, ref) {
10908
11954
  const isTableMatrixMode = props.mode === "table-matrix";
11955
+ const isPlanFactMode = props.mode === "plan-fact";
10909
11956
  const {
10910
11957
  tasks,
10911
11958
  rowHeight = 40,
@@ -10955,47 +12002,52 @@ function TaskGanttChartInner(props, ref) {
10955
12002
  onTaskDateChangeModeChange: externalOnTaskDateChangeModeChange
10956
12003
  } = props;
10957
12004
  const dayWidth = !isTableMatrixMode ? props.dayWidth ?? 40 : 40;
10958
- const viewMode = !isTableMatrixMode ? props.viewMode ?? "day" : "day";
10959
- const customDays = !isTableMatrixMode ? props.customDays : void 0;
10960
- const isWeekend3 = !isTableMatrixMode ? props.isWeekend : void 0;
10961
- const businessDays = !isTableMatrixMode ? props.businessDays ?? true : true;
12005
+ const viewMode = !isTableMatrixMode && !isPlanFactMode ? props.viewMode ?? "day" : "day";
12006
+ const customDays = !isTableMatrixMode && !isPlanFactMode ? props.customDays : void 0;
12007
+ const isWeekend3 = !isTableMatrixMode && !isPlanFactMode ? props.isWeekend : void 0;
12008
+ const businessDays = !isTableMatrixMode && !isPlanFactMode ? props.businessDays ?? true : true;
10962
12009
  const matrixColumns = isTableMatrixMode ? props.matrixColumns : [];
10963
12010
  const matrixColumnGroups = isTableMatrixMode ? props.matrixColumnGroups : void 0;
10964
12011
  const onMatrixCellClick = isTableMatrixMode ? props.onMatrixCellClick : void 0;
10965
12012
  const matrixDateOverlay = isTableMatrixMode ? props.matrixDateOverlay : void 0;
10966
- const containerRef = useRef10(null);
10967
- const scrollContainerRef = useRef10(null);
10968
- const scrollContentRef = useRef10(null);
10969
- const clearSelectedTaskTimeoutRef = useRef10(null);
10970
- const hasAutoScrolledToTodayRef = useRef10(false);
10971
- const previewPositionStoreRef = useRef10(null);
10972
- const renderedTaskIdsRef = useRef10(/* @__PURE__ */ new Set());
12013
+ const onPlanFactCellCommit = isPlanFactMode ? props.onPlanFactCellCommit : void 0;
12014
+ const containerRef = useRef11(null);
12015
+ const scrollContainerRef = useRef11(null);
12016
+ const scrollContentRef = useRef11(null);
12017
+ const clearSelectedTaskTimeoutRef = useRef11(null);
12018
+ const hasAutoScrolledToTodayRef = useRef11(false);
12019
+ const previewPositionStoreRef = useRef11(null);
12020
+ const renderedTaskIdsRef = useRef11(/* @__PURE__ */ new Set());
10973
12021
  if (previewPositionStoreRef.current === null) {
10974
12022
  previewPositionStoreRef.current = createTaskPreviewPositionStore();
10975
12023
  }
10976
12024
  const previewPositionStore = previewPositionStoreRef.current;
10977
- const [selectedTaskId, setSelectedTaskId] = useState10(null);
10978
- const [taskListHasRightShadow, setTaskListHasRightShadow] = useState10(false);
10979
- const [internalTaskDateChangeMode, setInternalTaskDateChangeMode] = useState10("preserve-duration");
10980
- const [scrollViewport, setScrollViewport] = useState10({ scrollTop: 0, viewportHeight: 0 });
10981
- const [selectedChip, setSelectedChip] = useState10(null);
10982
- const [activeTimelineTooltip, setActiveTimelineTooltip] = useState10(null);
10983
- const [internalCollapsedParentIds, setInternalCollapsedParentIds] = useState10(/* @__PURE__ */ new Set());
12025
+ const [selectedTaskId, setSelectedTaskId] = useState11(null);
12026
+ const [taskListHasRightShadow, setTaskListHasRightShadow] = useState11(false);
12027
+ const [internalTaskDateChangeMode, setInternalTaskDateChangeMode] = useState11("preserve-duration");
12028
+ const [scrollViewport, setScrollViewport] = useState11({ scrollTop: 0, viewportHeight: 0 });
12029
+ const [planFactDateWindow, setPlanFactDateWindow] = useState11(null);
12030
+ const [selectedChip, setSelectedChip] = useState11(null);
12031
+ const [activeTimelineTooltip, setActiveTimelineTooltip] = useState11(null);
12032
+ const [internalCollapsedParentIds, setInternalCollapsedParentIds] = useState11(/* @__PURE__ */ new Set());
10984
12033
  const collapsedParentIds = externalCollapsedParentIds ?? internalCollapsedParentIds;
10985
- const [editingTaskId, setEditingTaskId] = useState10(null);
12034
+ const [editingTaskId, setEditingTaskId] = useState11(null);
10986
12035
  const taskDateChangeMode = externalTaskDateChangeMode ?? internalTaskDateChangeMode;
10987
12036
  const handleTaskDateChangeMode = externalOnTaskDateChangeModeChange ?? setInternalTaskDateChangeMode;
10988
- const resolvedRowContentLines = Math.max(1, Math.floor(rowContentLines));
10989
- const effectiveRowHeight = useMemo12(
12037
+ const resolvedRowContentLines = isPlanFactMode ? Math.max(2, Math.floor(rowContentLines)) : Math.max(1, Math.floor(rowContentLines));
12038
+ const effectiveRowHeight = useMemo13(
10990
12039
  () => Math.max(rowHeight, 10 + resolvedRowContentLines * 18),
10991
12040
  [resolvedRowContentLines, rowHeight]
10992
12041
  );
10993
- const normalizedTasks = useMemo12(() => normalizeHierarchyTasks(tasks), [tasks]);
10994
- const isCustomWeekend = useMemo12(
12042
+ const normalizedTasks = useMemo13(() => normalizeHierarchyTasks(tasks), [tasks]);
12043
+ const isCustomWeekend = useMemo13(
10995
12044
  () => createCustomDayPredicate({ customDays, isWeekend: isWeekend3 }),
10996
12045
  [customDays, isWeekend3]
10997
12046
  );
10998
- const dateRangeTasks = useMemo12(() => {
12047
+ const dateRangeTasks = useMemo13(() => {
12048
+ if (isPlanFactMode) {
12049
+ return getPlanFactRangeTasks(normalizedTasks);
12050
+ }
10999
12051
  if (!showBaseline) {
11000
12052
  return normalizedTasks;
11001
12053
  }
@@ -11004,22 +12056,25 @@ function TaskGanttChartInner(props, ref) {
11004
12056
  startDate: task.baselineStartDate && parseUTCDate(task.baselineStartDate).getTime() < parseUTCDate(task.startDate).getTime() ? task.baselineStartDate : task.startDate,
11005
12057
  endDate: task.baselineEndDate && parseUTCDate(task.baselineEndDate).getTime() > parseUTCDate(task.endDate).getTime() ? task.baselineEndDate : task.endDate
11006
12058
  }));
11007
- }, [normalizedTasks, showBaseline]);
11008
- const dateRange = useMemo12(() => getMultiMonthDays(dateRangeTasks), [dateRangeTasks]);
11009
- const [validationResult, setValidationResult] = useState10(null);
11010
- const [cascadeOverrides, setCascadeOverrides] = useState10(/* @__PURE__ */ new Map());
11011
- const gridWidth = useMemo12(
12059
+ }, [isPlanFactMode, normalizedTasks, showBaseline]);
12060
+ const dateRange = useMemo13(
12061
+ () => isPlanFactMode ? getFullMonthDays(dateRangeTasks) : getMultiMonthDays(dateRangeTasks),
12062
+ [dateRangeTasks, isPlanFactMode]
12063
+ );
12064
+ const [validationResult, setValidationResult] = useState11(null);
12065
+ const [cascadeOverrides, setCascadeOverrides] = useState11(/* @__PURE__ */ new Map());
12066
+ const gridWidth = useMemo13(
11012
12067
  () => Math.round(dateRange.length * dayWidth),
11013
12068
  [dateRange.length, dayWidth]
11014
12069
  );
11015
- const matrixWidth = useMemo12(
12070
+ const matrixWidth = useMemo13(
11016
12071
  () => matrixColumns.reduce((sum, column) => {
11017
12072
  if (typeof column.width !== "number") return void 0;
11018
12073
  return sum !== void 0 ? sum + column.width : void 0;
11019
12074
  }, 0),
11020
12075
  [matrixColumns]
11021
12076
  );
11022
- const visibleTasks = useMemo12(() => {
12077
+ const visibleTasks = useMemo13(() => {
11023
12078
  const parentMap = new Map(normalizedTasks.map((t) => [t.id, t.parentId]));
11024
12079
  function isAnyAncestorCollapsed(parentId) {
11025
12080
  let current = parentId;
@@ -11035,11 +12090,11 @@ function TaskGanttChartInner(props, ref) {
11035
12090
  }
11036
12091
  return tasks2;
11037
12092
  }, [normalizedTasks, collapsedParentIds, filterMode, taskFilter]);
11038
- const matchedTaskIds = useMemo12(() => {
12093
+ const matchedTaskIds = useMemo13(() => {
11039
12094
  if (!taskFilter) return /* @__PURE__ */ new Set();
11040
12095
  return new Set(visibleTasks.filter(taskFilter).map((task) => task.id));
11041
12096
  }, [visibleTasks, taskFilter]);
11042
- const taskListHighlightedTaskIds = useMemo12(() => {
12097
+ const taskListHighlightedTaskIds = useMemo13(() => {
11043
12098
  if (filterMode === "hide") {
11044
12099
  return /* @__PURE__ */ new Set();
11045
12100
  }
@@ -11050,33 +12105,34 @@ function TaskGanttChartInner(props, ref) {
11050
12105
  matchedTaskIds.forEach((taskId) => mergedHighlightedTaskIds.add(taskId));
11051
12106
  return mergedHighlightedTaskIds;
11052
12107
  }, [filterMode, highlightedTaskIds, matchedTaskIds]);
11053
- const totalGridHeight = useMemo12(
12108
+ const totalGridHeight = useMemo13(
11054
12109
  () => visibleTasks.length * effectiveRowHeight,
11055
12110
  [effectiveRowHeight, visibleTasks.length]
11056
12111
  );
11057
12112
  const timelineHeaderHeight = headerHeight + 1;
11058
- const tableBodyMinHeight = useMemo12(() => {
11059
- if (!isTableMatrixMode || containerHeight === void 0) {
12113
+ const tableBodyMinHeight = useMemo13(() => {
12114
+ if (!isTableMatrixMode && !isPlanFactMode || containerHeight === void 0) {
11060
12115
  return void 0;
11061
12116
  }
11062
12117
  if (typeof containerHeight === "number") {
11063
12118
  return Math.max(0, containerHeight - timelineHeaderHeight);
11064
12119
  }
11065
12120
  return `calc(${containerHeight} - ${timelineHeaderHeight}px)`;
11066
- }, [containerHeight, isTableMatrixMode, timelineHeaderHeight]);
11067
- const monthStart = useMemo12(() => {
12121
+ }, [containerHeight, isPlanFactMode, isTableMatrixMode, timelineHeaderHeight]);
12122
+ const monthStart = useMemo13(() => {
11068
12123
  if (dateRange.length === 0) {
11069
12124
  return new Date(Date.UTC((/* @__PURE__ */ new Date()).getUTCFullYear(), (/* @__PURE__ */ new Date()).getUTCMonth(), 1));
11070
12125
  }
11071
12126
  const firstDay = dateRange[0];
11072
12127
  return new Date(Date.UTC(firstDay.getUTCFullYear(), firstDay.getUTCMonth(), 1));
11073
12128
  }, [dateRange]);
11074
- const todayInRange = useMemo12(() => {
12129
+ const todayIndex = useMemo13(() => {
11075
12130
  const now = /* @__PURE__ */ new Date();
11076
12131
  const today = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
11077
- return dateRange.some((day) => day.getTime() === today.getTime());
12132
+ return dateRange.findIndex((day) => day.getTime() === today.getTime());
11078
12133
  }, [dateRange]);
11079
- const visibleTimelineMarkers = useMemo12(() => {
12134
+ const todayInRange = todayIndex !== -1;
12135
+ const visibleTimelineMarkers = useMemo13(() => {
11080
12136
  if (isTableMatrixMode || !timelineMarkers || timelineMarkers.length === 0 || dateRange.length === 0) {
11081
12137
  return [];
11082
12138
  }
@@ -11087,22 +12143,22 @@ function TaskGanttChartInner(props, ref) {
11087
12143
  return markerDate >= rangeStartMs && markerDate <= rangeEndMs;
11088
12144
  });
11089
12145
  }, [dateRange, isTableMatrixMode, timelineMarkers]);
11090
- useEffect9(() => {
12146
+ useEffect10(() => {
11091
12147
  if (isTableMatrixMode) return;
11092
12148
  if (hasAutoScrolledToTodayRef.current) return;
11093
12149
  const container = scrollContainerRef.current;
11094
12150
  if (!container || dateRange.length === 0) return;
11095
12151
  const now = /* @__PURE__ */ new Date();
11096
12152
  const today = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
11097
- const todayIndex = dateRange.findIndex((day) => day.getTime() === today.getTime());
11098
- if (todayIndex === -1) return;
11099
- const todayOffset = todayIndex * dayWidth;
12153
+ const todayIndex2 = dateRange.findIndex((day) => day.getTime() === today.getTime());
12154
+ if (todayIndex2 === -1) return;
12155
+ const todayOffset = todayIndex2 * dayWidth;
11100
12156
  const containerWidth = container.clientWidth;
11101
12157
  const scrollLeft = Math.round(todayOffset + dayWidth / 2 - containerWidth * 0.3);
11102
12158
  container.scrollLeft = Math.max(0, scrollLeft);
11103
12159
  hasAutoScrolledToTodayRef.current = true;
11104
12160
  }, [dateRange, dayWidth, isTableMatrixMode]);
11105
- useEffect9(() => {
12161
+ useEffect10(() => {
11106
12162
  const container = scrollContainerRef.current;
11107
12163
  if (!container) return;
11108
12164
  let frameId = null;
@@ -11110,13 +12166,33 @@ function TaskGanttChartInner(props, ref) {
11110
12166
  frameId = null;
11111
12167
  const nextHasRightShadow = container.scrollLeft > 0;
11112
12168
  const nextViewportHeight = Math.max(0, container.clientHeight - timelineHeaderHeight);
12169
+ const nextViewportWidth = Math.max(0, container.clientWidth - (showTaskList ? taskListWidth : 0));
11113
12170
  const nextScrollTop = container.scrollTop;
12171
+ const nextScrollLeft = container.scrollLeft;
12172
+ const nextChartScrollLeft = Math.max(0, nextScrollLeft);
11114
12173
  setTaskListHasRightShadow(
11115
12174
  (previous) => previous === nextHasRightShadow ? previous : nextHasRightShadow
11116
12175
  );
11117
12176
  setScrollViewport(
11118
12177
  (previous) => previous.scrollTop === nextScrollTop && previous.viewportHeight === nextViewportHeight ? previous : { scrollTop: nextScrollTop, viewportHeight: nextViewportHeight }
11119
12178
  );
12179
+ setPlanFactDateWindow((previous) => {
12180
+ if (!isPlanFactMode || dateRange.length === 0 || nextViewportWidth <= 0) {
12181
+ return previous === null ? previous : null;
12182
+ }
12183
+ const firstVisibleColumn = Math.max(0, Math.floor(nextChartScrollLeft / dayWidth));
12184
+ const visibleColumnCount = Math.max(1, Math.ceil(nextViewportWidth / dayWidth));
12185
+ const lastVisibleColumn = Math.min(dateRange.length - 1, firstVisibleColumn + visibleColumnCount - 1);
12186
+ const rangeStart = Math.max(
12187
+ 0,
12188
+ Math.floor(Math.max(0, firstVisibleColumn - PLAN_FACT_COLUMN_OVERSCAN) / PLAN_FACT_COLUMN_WINDOW_STEP) * PLAN_FACT_COLUMN_WINDOW_STEP
12189
+ );
12190
+ const rangeEnd = Math.min(
12191
+ dateRange.length - 1,
12192
+ Math.ceil((lastVisibleColumn + PLAN_FACT_COLUMN_OVERSCAN + 1) / PLAN_FACT_COLUMN_WINDOW_STEP) * PLAN_FACT_COLUMN_WINDOW_STEP - 1
12193
+ );
12194
+ return previous?.start === rangeStart && previous.end === rangeEnd ? previous : { start: rangeStart, end: rangeEnd };
12195
+ });
11120
12196
  };
11121
12197
  const scheduleUpdate = () => {
11122
12198
  if (frameId !== null) return;
@@ -11137,21 +12213,21 @@ function TaskGanttChartInner(props, ref) {
11137
12213
  container.removeEventListener("scroll", scheduleUpdate);
11138
12214
  window.removeEventListener("resize", scheduleUpdate);
11139
12215
  };
11140
- }, [timelineHeaderHeight]);
11141
- const scrollToToday = useCallback9(() => {
12216
+ }, [dateRange.length, dayWidth, isPlanFactMode, showTaskList, taskListWidth, timelineHeaderHeight]);
12217
+ const scrollToToday = useCallback10(() => {
11142
12218
  if (isTableMatrixMode) return;
11143
12219
  const container = scrollContainerRef.current;
11144
12220
  if (!container || dateRange.length === 0) return;
11145
12221
  const now = /* @__PURE__ */ new Date();
11146
12222
  const today = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
11147
- const todayIndex = dateRange.findIndex((day) => day.getTime() === today.getTime());
11148
- if (todayIndex === -1) return;
11149
- const todayOffset = todayIndex * dayWidth;
12223
+ const todayIndex2 = dateRange.findIndex((day) => day.getTime() === today.getTime());
12224
+ if (todayIndex2 === -1) return;
12225
+ const todayOffset = todayIndex2 * dayWidth;
11150
12226
  const containerWidth = container.clientWidth;
11151
12227
  const scrollLeft = Math.round(todayOffset + dayWidth / 2 - containerWidth * 0.3);
11152
12228
  container.scrollTo({ left: Math.max(0, scrollLeft), behavior: "smooth" });
11153
12229
  }, [dateRange, dayWidth, isTableMatrixMode]);
11154
- const scrollToTask = useCallback9((taskId) => {
12230
+ const scrollToTask = useCallback10((taskId) => {
11155
12231
  if (isTableMatrixMode) {
11156
12232
  const container2 = scrollContainerRef.current;
11157
12233
  if (!container2) return;
@@ -11178,7 +12254,7 @@ function TaskGanttChartInner(props, ref) {
11178
12254
  const scrollLeft = Math.round(taskOffset - dayWidth * 2);
11179
12255
  container.scrollTo({ left: Math.max(0, scrollLeft), behavior: "smooth" });
11180
12256
  }, [dateRange, dayWidth, effectiveRowHeight, isTableMatrixMode, tasks, visibleTasks]);
11181
- const scrollToRow = useCallback9((taskId, options = {}) => {
12257
+ const scrollToRow = useCallback10((taskId, options = {}) => {
11182
12258
  const container = scrollContainerRef.current;
11183
12259
  if (!container) return;
11184
12260
  const task = tasks.find((t) => t.id === taskId);
@@ -11207,21 +12283,21 @@ function TaskGanttChartInner(props, ref) {
11207
12283
  }
11208
12284
  container.scrollTo({ top: scrollTop, behavior });
11209
12285
  }, [effectiveRowHeight, tasks, visibleTasks]);
11210
- const [dragGuideLines, setDragGuideLines] = useState10(null);
11211
- const [draggedTaskOverride, setDraggedTaskOverride] = useState10(null);
11212
- const [previewTasksById, setPreviewTasksById] = useState10(/* @__PURE__ */ new Map());
11213
- useEffect9(() => {
12286
+ const [dragGuideLines, setDragGuideLines] = useState11(null);
12287
+ const [draggedTaskOverride, setDraggedTaskOverride] = useState11(null);
12288
+ const [previewTasksById, setPreviewTasksById] = useState11(/* @__PURE__ */ new Map());
12289
+ useEffect10(() => {
11214
12290
  const result = validateDependencies(tasks);
11215
12291
  setValidationResult(result);
11216
12292
  onValidateDependencies?.(result);
11217
12293
  }, [tasks, onValidateDependencies]);
11218
- useEffect9(() => () => {
12294
+ useEffect10(() => () => {
11219
12295
  if (clearSelectedTaskTimeoutRef.current !== null) {
11220
12296
  window.clearTimeout(clearSelectedTaskTimeoutRef.current);
11221
12297
  }
11222
12298
  previewPositionStore.clear();
11223
12299
  }, [previewPositionStore]);
11224
- const handleTaskChange = useCallback9((updatedTasks) => {
12300
+ const handleTaskChange = useCallback10((updatedTasks) => {
11225
12301
  const updatedTask = updatedTasks[0];
11226
12302
  if (!updatedTask) return;
11227
12303
  const originalTask = tasks.find((t) => t.id === updatedTask.id);
@@ -11267,7 +12343,7 @@ function TaskGanttChartInner(props, ref) {
11267
12343
  const cascadedTasks = disableConstraints ? [updatedTask] : universalCascade(updatedTask, newStart, newEnd, sourceTasks, businessDays, isCustomWeekend);
11268
12344
  onTasksChange?.(cascadedTasks);
11269
12345
  }, [tasks, onTasksChange, disableConstraints, editingTaskId, businessDays, isCustomWeekend]);
11270
- const handleDelete = useCallback9((taskId) => {
12346
+ const handleDelete = useCallback10((taskId) => {
11271
12347
  const toDelete = /* @__PURE__ */ new Set([taskId]);
11272
12348
  function collectDescendants(parentId) {
11273
12349
  const children = getChildren(parentId, tasks);
@@ -11292,10 +12368,10 @@ function TaskGanttChartInner(props, ref) {
11292
12368
  }
11293
12369
  toDelete.forEach((id) => onDelete?.(id));
11294
12370
  }, [tasks, onTasksChange, onDelete]);
11295
- const handleInsertAfter = useCallback9((taskId, newTask) => {
12371
+ const handleInsertAfter = useCallback10((taskId, newTask) => {
11296
12372
  onInsertAfter?.(taskId, newTask);
11297
12373
  }, [onInsertAfter]);
11298
- const handleReorder = useCallback9((reorderedTasks, movedTaskId, inferredParentId) => {
12374
+ const handleReorder = useCallback10((reorderedTasks, movedTaskId, inferredParentId) => {
11299
12375
  let updated = reorderedTasks;
11300
12376
  if (movedTaskId) {
11301
12377
  updated = updated.map((t) => {
@@ -11312,7 +12388,7 @@ function TaskGanttChartInner(props, ref) {
11312
12388
  }
11313
12389
  onTasksChange?.(normalized);
11314
12390
  }, [onTasksChange, onReorder]);
11315
- const dependencyOverrides = useMemo12(() => {
12391
+ const dependencyOverrides = useMemo13(() => {
11316
12392
  const map = new Map(cascadeOverrides);
11317
12393
  if (draggedTaskOverride) {
11318
12394
  map.set(draggedTaskOverride.taskId, {
@@ -11322,7 +12398,7 @@ function TaskGanttChartInner(props, ref) {
11322
12398
  }
11323
12399
  return map;
11324
12400
  }, [cascadeOverrides, draggedTaskOverride]);
11325
- const handleCascadeProgress = useCallback9((overrides, previewTasks = []) => {
12401
+ const handleCascadeProgress = useCallback10((overrides, previewTasks = []) => {
11326
12402
  previewPositionStore.setPositions(overrides);
11327
12403
  const renderedTaskIds = renderedTaskIdsRef.current;
11328
12404
  const renderedOverrides = /* @__PURE__ */ new Map();
@@ -11337,26 +12413,26 @@ function TaskGanttChartInner(props, ref) {
11337
12413
  return arePreviewTaskMapsEqual(current, next) ? current : next;
11338
12414
  });
11339
12415
  }, [previewPositionStore]);
11340
- const previewNormalizedTasks = useMemo12(() => {
12416
+ const previewNormalizedTasks = useMemo13(() => {
11341
12417
  if (previewTasksById.size === 0) return normalizedTasks;
11342
12418
  return normalizedTasks.map((task) => previewTasksById.get(task.id) ?? task);
11343
12419
  }, [normalizedTasks, previewTasksById]);
11344
- const previewVisibleTasks = useMemo12(() => {
12420
+ const previewVisibleTasks = useMemo13(() => {
11345
12421
  if (previewTasksById.size === 0) return visibleTasks;
11346
12422
  return visibleTasks.map((task) => previewTasksById.get(task.id) ?? task);
11347
12423
  }, [visibleTasks, previewTasksById]);
11348
- const visibleTaskIndexMap = useMemo12(
12424
+ const visibleTaskIndexMap = useMemo13(
11349
12425
  () => new Map(visibleTasks.map((task, index) => [task.id, index])),
11350
12426
  [visibleTasks]
11351
12427
  );
11352
- const forcedRenderedTaskIds = useMemo12(() => {
12428
+ const forcedRenderedTaskIds = useMemo13(() => {
11353
12429
  const ids = /* @__PURE__ */ new Set();
11354
12430
  if (draggedTaskOverride) {
11355
12431
  ids.add(draggedTaskOverride.taskId);
11356
12432
  }
11357
12433
  return ids;
11358
12434
  }, [draggedTaskOverride]);
11359
- const visibleTaskWindowIndices = useMemo12(() => {
12435
+ const visibleTaskWindowIndices = useMemo13(() => {
11360
12436
  const totalTasks = visibleTasks.length;
11361
12437
  if (totalTasks === 0) {
11362
12438
  return [];
@@ -11382,35 +12458,44 @@ function TaskGanttChartInner(props, ref) {
11382
12458
  }
11383
12459
  return Array.from(indices).sort((left, right) => left - right);
11384
12460
  }, [effectiveRowHeight, forcedRenderedTaskIds, scrollViewport, visibleTaskIndexMap, visibleTasks.length]);
11385
- const renderedChartTasks = useMemo12(
12461
+ const visiblePlanFactDateIndices = useMemo13(() => {
12462
+ if (!isPlanFactMode || dateRange.length === 0 || !planFactDateWindow) {
12463
+ return void 0;
12464
+ }
12465
+ return Array.from(
12466
+ { length: planFactDateWindow.end - planFactDateWindow.start + 1 },
12467
+ (_, index) => planFactDateWindow.start + index
12468
+ );
12469
+ }, [dateRange.length, isPlanFactMode, planFactDateWindow]);
12470
+ const renderedChartTasks = useMemo13(
11386
12471
  () => visibleTaskWindowIndices.map((index) => {
11387
12472
  const task = previewVisibleTasks[index];
11388
12473
  return task ? { index, task } : null;
11389
12474
  }).filter((entry) => entry !== null),
11390
12475
  [previewVisibleTasks, visibleTaskWindowIndices]
11391
12476
  );
11392
- const renderedDependencyTasks = useMemo12(
12477
+ const renderedDependencyTasks = useMemo13(
11393
12478
  () => renderedChartTasks.map(({ task }) => task),
11394
12479
  [renderedChartTasks]
11395
12480
  );
11396
- renderedTaskIdsRef.current = useMemo12(
12481
+ renderedTaskIdsRef.current = useMemo13(
11397
12482
  () => new Set(renderedChartTasks.map(({ task }) => task.id)),
11398
12483
  [renderedChartTasks]
11399
12484
  );
11400
- const handleCascade = useCallback9((cascadedTasks) => {
12485
+ const handleCascade = useCallback10((cascadedTasks) => {
11401
12486
  onTasksChange?.(cascadedTasks);
11402
12487
  }, [tasks, onTasksChange]);
11403
- const handleTaskSelect = useCallback9((taskId) => {
12488
+ const handleTaskSelect = useCallback10((taskId) => {
11404
12489
  setSelectedTaskId(taskId);
11405
12490
  }, []);
11406
- const hoveredRowElementsRef = useRef10([]);
11407
- const clearHoveredRows = useCallback9(() => {
12491
+ const hoveredRowElementsRef = useRef11([]);
12492
+ const clearHoveredRows = useCallback10(() => {
11408
12493
  for (const element of hoveredRowElementsRef.current) {
11409
- element.classList.remove("gantt-tl-row-hovered", "gantt-tr-row-hovered", "gantt-mx-row-hovered");
12494
+ element.classList.remove("gantt-tl-row-hovered", "gantt-tr-row-hovered", "gantt-mx-row-hovered", "gantt-pf-row-hovered");
11410
12495
  }
11411
12496
  hoveredRowElementsRef.current = [];
11412
12497
  }, []);
11413
- const applyHoveredRows = useCallback9((taskId) => {
12498
+ const applyHoveredRows = useCallback10((taskId) => {
11414
12499
  const root = scrollContentRef.current;
11415
12500
  if (!root) return;
11416
12501
  clearHoveredRows();
@@ -11427,10 +12512,13 @@ function TaskGanttChartInner(props, ref) {
11427
12512
  if (element.classList.contains("gantt-mx-row")) {
11428
12513
  element.classList.add("gantt-mx-row-hovered");
11429
12514
  }
12515
+ if (element.classList.contains("gantt-pf-row")) {
12516
+ element.classList.add("gantt-pf-row-hovered");
12517
+ }
11430
12518
  }
11431
12519
  hoveredRowElementsRef.current = nextHoveredRows;
11432
12520
  }, [clearHoveredRows]);
11433
- const handleSharedRowHover = useCallback9((event) => {
12521
+ const handleSharedRowHover = useCallback10((event) => {
11434
12522
  const target = event.target;
11435
12523
  const row = target.closest("[data-gantt-task-row-id]");
11436
12524
  const taskId = row?.dataset.ganttTaskRowId;
@@ -11440,7 +12528,7 @@ function TaskGanttChartInner(props, ref) {
11440
12528
  }
11441
12529
  applyHoveredRows(taskId);
11442
12530
  }, [applyHoveredRows]);
11443
- const handleToggleCollapse = externalOnToggleCollapse ?? useCallback9((parentId) => {
12531
+ const handleToggleCollapse = externalOnToggleCollapse ?? useCallback10((parentId) => {
11444
12532
  setInternalCollapsedParentIds((prev) => {
11445
12533
  const next = new Set(prev);
11446
12534
  if (next.has(parentId)) {
@@ -11451,20 +12539,20 @@ function TaskGanttChartInner(props, ref) {
11451
12539
  return next;
11452
12540
  });
11453
12541
  }, []);
11454
- const allParentIds = useMemo12(() => {
12542
+ const allParentIds = useMemo13(() => {
11455
12543
  return new Set(
11456
12544
  normalizedTasks.filter((t) => isTaskParent(t.id, normalizedTasks)).map((t) => t.id)
11457
12545
  );
11458
12546
  }, [normalizedTasks]);
11459
- const handleCollapseAll = useCallback9(() => {
12547
+ const handleCollapseAll = useCallback10(() => {
11460
12548
  if (externalCollapsedParentIds) return;
11461
12549
  setInternalCollapsedParentIds(allParentIds);
11462
12550
  }, [allParentIds, externalCollapsedParentIds]);
11463
- const handleExpandAll = useCallback9(() => {
12551
+ const handleExpandAll = useCallback10(() => {
11464
12552
  if (externalCollapsedParentIds) return;
11465
12553
  setInternalCollapsedParentIds(/* @__PURE__ */ new Set());
11466
12554
  }, [externalCollapsedParentIds]);
11467
- const exportToPdf = useCallback9(async (options) => {
12555
+ const exportToPdf = useCallback10(async (options) => {
11468
12556
  const sourceContainer = containerRef.current;
11469
12557
  const sourceContent = scrollContentRef.current;
11470
12558
  if (!sourceContainer || !sourceContent || typeof window === "undefined" || typeof document === "undefined") {
@@ -11523,7 +12611,7 @@ function TaskGanttChartInner(props, ref) {
11523
12611
  }
11524
12612
  return depth;
11525
12613
  }
11526
- const handlePromoteTask = useCallback9((taskId) => {
12614
+ const handlePromoteTask = useCallback10((taskId) => {
11527
12615
  if (onPromoteTask) {
11528
12616
  onPromoteTask(taskId);
11529
12617
  return;
@@ -11555,7 +12643,7 @@ function TaskGanttChartInner(props, ref) {
11555
12643
  ]);
11556
12644
  onTasksChange?.(reorderedTasks);
11557
12645
  }, [tasks, onTasksChange, onPromoteTask]);
11558
- const handleDemoteTask = useCallback9((taskId, newParentId) => {
12646
+ const handleDemoteTask = useCallback10((taskId, newParentId) => {
11559
12647
  if (onDemoteTask) {
11560
12648
  onDemoteTask(taskId, newParentId);
11561
12649
  return;
@@ -11596,7 +12684,7 @@ function TaskGanttChartInner(props, ref) {
11596
12684
  };
11597
12685
  onTasksChange?.([updatedDemotedTask]);
11598
12686
  }, [tasks, onTasksChange, onDemoteTask]);
11599
- const handleUngroupTask = useCallback9((taskId) => {
12687
+ const handleUngroupTask = useCallback10((taskId) => {
11600
12688
  if (onUngroupTask) {
11601
12689
  onUngroupTask(taskId);
11602
12690
  return;
@@ -11621,8 +12709,8 @@ function TaskGanttChartInner(props, ref) {
11621
12709
  onTasksChange?.(changedTasks);
11622
12710
  }
11623
12711
  }, [tasks, onTasksChange, onUngroupTask]);
11624
- const panStateRef = useRef10(null);
11625
- const handlePanStart = useCallback9((e) => {
12712
+ const panStateRef = useRef11(null);
12713
+ const handlePanStart = useCallback10((e) => {
11626
12714
  if (e.button !== 0) return;
11627
12715
  const target = e.target;
11628
12716
  if (target.closest("[data-taskbar]")) return;
@@ -11635,7 +12723,10 @@ function TaskGanttChartInner(props, ref) {
11635
12723
  startX: e.clientX,
11636
12724
  startY: e.clientY,
11637
12725
  scrollX: container.scrollLeft,
11638
- scrollY: container.scrollTop
12726
+ scrollY: container.scrollTop,
12727
+ currentX: e.clientX,
12728
+ currentY: e.clientY,
12729
+ frameId: null
11639
12730
  };
11640
12731
  if (document.activeElement instanceof HTMLElement) {
11641
12732
  document.activeElement.blur();
@@ -11643,17 +12734,38 @@ function TaskGanttChartInner(props, ref) {
11643
12734
  container.style.cursor = "grabbing";
11644
12735
  e.preventDefault();
11645
12736
  }, []);
11646
- useEffect9(() => {
11647
- const handlePanMove = (e) => {
12737
+ useEffect10(() => {
12738
+ const flushPanMove = () => {
11648
12739
  const pan = panStateRef.current;
11649
12740
  if (!pan?.active) return;
12741
+ pan.frameId = null;
11650
12742
  const container = scrollContainerRef.current;
11651
12743
  if (!container) return;
11652
- container.scrollLeft = pan.scrollX - (e.clientX - pan.startX);
11653
- container.scrollTop = pan.scrollY - (e.clientY - pan.startY);
12744
+ const maxScrollLeft = Math.max(0, container.scrollWidth - container.clientWidth);
12745
+ const maxScrollTop = Math.max(0, container.scrollHeight - container.clientHeight);
12746
+ const nextScrollLeft = clampScrollValue(pan.scrollX - (pan.currentX - pan.startX), maxScrollLeft);
12747
+ const nextScrollTop = clampScrollValue(pan.scrollY - (pan.currentY - pan.startY), maxScrollTop);
12748
+ if (Math.abs(container.scrollLeft - nextScrollLeft) > 0.5) {
12749
+ container.scrollLeft = nextScrollLeft;
12750
+ }
12751
+ if (Math.abs(container.scrollTop - nextScrollTop) > 0.5) {
12752
+ container.scrollTop = nextScrollTop;
12753
+ }
12754
+ };
12755
+ const handlePanMove = (e) => {
12756
+ const pan = panStateRef.current;
12757
+ if (!pan?.active) return;
12758
+ pan.currentX = e.clientX;
12759
+ pan.currentY = e.clientY;
12760
+ if (pan.frameId !== null) return;
12761
+ pan.frameId = window.requestAnimationFrame(flushPanMove);
11654
12762
  };
11655
12763
  const handlePanEnd = () => {
11656
- if (!panStateRef.current?.active) return;
12764
+ const pan = panStateRef.current;
12765
+ if (!pan?.active) return;
12766
+ if (pan.frameId !== null) {
12767
+ window.cancelAnimationFrame(pan.frameId);
12768
+ }
11657
12769
  panStateRef.current = null;
11658
12770
  const container = scrollContainerRef.current;
11659
12771
  if (container) container.style.cursor = "";
@@ -11665,19 +12777,19 @@ function TaskGanttChartInner(props, ref) {
11665
12777
  window.removeEventListener("mouseup", handlePanEnd);
11666
12778
  };
11667
12779
  }, []);
11668
- return /* @__PURE__ */ jsx18(
12780
+ return /* @__PURE__ */ jsx19(
11669
12781
  "div",
11670
12782
  {
11671
12783
  ref: containerRef,
11672
- className: isTableMatrixMode ? "gantt-container gantt-container-tableMatrix" : "gantt-container",
11673
- children: /* @__PURE__ */ jsx18(
12784
+ className: isTableMatrixMode ? "gantt-container gantt-container-tableMatrix" : isPlanFactMode ? "gantt-container gantt-container-planFact" : "gantt-container",
12785
+ children: /* @__PURE__ */ jsx19(
11674
12786
  "div",
11675
12787
  {
11676
12788
  ref: scrollContainerRef,
11677
12789
  className: "gantt-scrollContainer",
11678
12790
  style: { height: containerHeight ?? "auto", cursor: "grab" },
11679
12791
  onMouseDown: handlePanStart,
11680
- children: /* @__PURE__ */ jsxs14(
12792
+ children: /* @__PURE__ */ jsxs15(
11681
12793
  "div",
11682
12794
  {
11683
12795
  ref: scrollContentRef,
@@ -11685,7 +12797,7 @@ function TaskGanttChartInner(props, ref) {
11685
12797
  onMouseOver: handleSharedRowHover,
11686
12798
  onMouseLeave: clearHoveredRows,
11687
12799
  children: [
11688
- /* @__PURE__ */ jsx18(
12800
+ /* @__PURE__ */ jsx19(
11689
12801
  TaskList,
11690
12802
  {
11691
12803
  tasks: normalizedTasks,
@@ -11738,17 +12850,17 @@ function TaskGanttChartInner(props, ref) {
11738
12850
  visibleRowIndices: visibleTaskWindowIndices
11739
12851
  }
11740
12852
  ),
11741
- /* @__PURE__ */ jsx18(
12853
+ /* @__PURE__ */ jsx19(
11742
12854
  "div",
11743
12855
  {
11744
- className: isTableMatrixMode || showChart ? "gantt-chartSurface" : "gantt-chartSurface gantt-chart-hidden",
12856
+ className: isTableMatrixMode || isPlanFactMode || showChart ? "gantt-chartSurface" : "gantt-chartSurface gantt-chart-hidden",
11745
12857
  style: {
11746
- minWidth: isTableMatrixMode ? matrixWidth !== void 0 ? `${matrixWidth}px` : void 0 : `${gridWidth}px`,
11747
- width: isTableMatrixMode ? matrixWidth !== void 0 ? `${matrixWidth}px` : "max-content" : void 0,
11748
- flex: isTableMatrixMode ? "0 0 auto" : 1,
11749
- display: isTableMatrixMode || showChart ? void 0 : "none"
12858
+ minWidth: isTableMatrixMode ? matrixWidth !== void 0 ? `${matrixWidth}px` : void 0 : isPlanFactMode ? `${gridWidth}px` : `${gridWidth}px`,
12859
+ width: isTableMatrixMode ? matrixWidth !== void 0 ? `${matrixWidth}px` : "max-content" : isPlanFactMode ? `${gridWidth}px` : void 0,
12860
+ flex: isTableMatrixMode || isPlanFactMode ? "0 0 auto" : 1,
12861
+ display: isTableMatrixMode || isPlanFactMode || showChart ? void 0 : "none"
11750
12862
  },
11751
- children: isTableMatrixMode ? /* @__PURE__ */ jsx18(
12863
+ children: isTableMatrixMode ? /* @__PURE__ */ jsx19(
11752
12864
  TableMatrix,
11753
12865
  {
11754
12866
  tasks: visibleTasks,
@@ -11765,14 +12877,34 @@ function TaskGanttChartInner(props, ref) {
11765
12877
  highlightedTaskIds: taskListHighlightedTaskIds,
11766
12878
  filterMode
11767
12879
  }
11768
- ) : /* @__PURE__ */ jsxs14(Fragment4, { children: [
11769
- /* @__PURE__ */ jsxs14(
12880
+ ) : isPlanFactMode ? /* @__PURE__ */ jsx19(
12881
+ PlanFactMatrix,
12882
+ {
12883
+ tasks: visibleTasks,
12884
+ allTasks: normalizedTasks,
12885
+ dateRange,
12886
+ dayWidth,
12887
+ rowHeight: effectiveRowHeight,
12888
+ headerHeight: timelineHeaderHeight,
12889
+ bodyMinHeight: tableBodyMinHeight,
12890
+ selectedTaskId,
12891
+ onTaskSelect: handleTaskSelect,
12892
+ onTasksChange: handleTaskChange,
12893
+ onCellCommit: onPlanFactCellCommit,
12894
+ highlightedTaskIds: taskListHighlightedTaskIds,
12895
+ filterMode,
12896
+ visibleRowIndices: visibleTaskWindowIndices,
12897
+ visibleDateIndices: visiblePlanFactDateIndices,
12898
+ todayDateIndex: todayInRange ? todayIndex : void 0
12899
+ }
12900
+ ) : /* @__PURE__ */ jsxs15(Fragment4, { children: [
12901
+ /* @__PURE__ */ jsxs15(
11770
12902
  "div",
11771
12903
  {
11772
12904
  className: "gantt-stickyHeader",
11773
12905
  style: { width: `${gridWidth}px`, height: `${timelineHeaderHeight}px` },
11774
12906
  children: [
11775
- /* @__PURE__ */ jsx18(
12907
+ /* @__PURE__ */ jsx19(
11776
12908
  TimeScaleHeader_default,
11777
12909
  {
11778
12910
  days: dateRange,
@@ -11785,7 +12917,7 @@ function TaskGanttChartInner(props, ref) {
11785
12917
  onTimelineHoverEnd: () => setActiveTimelineTooltip(null)
11786
12918
  }
11787
12919
  ),
11788
- activeTimelineTooltip && /* @__PURE__ */ jsx18("div", { className: "gantt-timelineTooltipLayer", "aria-hidden": "true", children: /* @__PURE__ */ jsx18(
12920
+ activeTimelineTooltip && /* @__PURE__ */ jsx19("div", { className: "gantt-timelineTooltipLayer", "aria-hidden": "true", children: /* @__PURE__ */ jsx19(
11789
12921
  "div",
11790
12922
  {
11791
12923
  className: "gantt-timelineTooltip",
@@ -11799,7 +12931,7 @@ function TaskGanttChartInner(props, ref) {
11799
12931
  ]
11800
12932
  }
11801
12933
  ),
11802
- /* @__PURE__ */ jsxs14(
12934
+ /* @__PURE__ */ jsxs15(
11803
12935
  "div",
11804
12936
  {
11805
12937
  className: "gantt-taskArea",
@@ -11809,7 +12941,7 @@ function TaskGanttChartInner(props, ref) {
11809
12941
  height: `${totalGridHeight}px`
11810
12942
  },
11811
12943
  children: [
11812
- /* @__PURE__ */ jsx18(
12944
+ /* @__PURE__ */ jsx19(
11813
12945
  GridBackground_default,
11814
12946
  {
11815
12947
  dateRange,
@@ -11819,7 +12951,7 @@ function TaskGanttChartInner(props, ref) {
11819
12951
  isCustomWeekend
11820
12952
  }
11821
12953
  ),
11822
- todayInRange && /* @__PURE__ */ jsx18(
12954
+ todayInRange && /* @__PURE__ */ jsx19(
11823
12955
  TodayIndicator_default,
11824
12956
  {
11825
12957
  monthStart,
@@ -11828,7 +12960,7 @@ function TaskGanttChartInner(props, ref) {
11828
12960
  onHoverEnd: () => setActiveTimelineTooltip(null)
11829
12961
  }
11830
12962
  ),
11831
- visibleTimelineMarkers.length > 0 && /* @__PURE__ */ jsx18(
12963
+ visibleTimelineMarkers.length > 0 && /* @__PURE__ */ jsx19(
11832
12964
  TimelineMarkers_default,
11833
12965
  {
11834
12966
  rangeStart: monthStart,
@@ -11839,7 +12971,7 @@ function TaskGanttChartInner(props, ref) {
11839
12971
  onHoverEnd: () => setActiveTimelineTooltip(null)
11840
12972
  }
11841
12973
  ),
11842
- /* @__PURE__ */ jsx18(
12974
+ /* @__PURE__ */ jsx19(
11843
12975
  DependencyLines_default,
11844
12976
  {
11845
12977
  tasks: renderedDependencyTasks,
@@ -11857,7 +12989,7 @@ function TaskGanttChartInner(props, ref) {
11857
12989
  weekendPredicate: isCustomWeekend
11858
12990
  }
11859
12991
  ),
11860
- dragGuideLines && /* @__PURE__ */ jsx18(
12992
+ dragGuideLines && /* @__PURE__ */ jsx19(
11861
12993
  DragGuideLines_default,
11862
12994
  {
11863
12995
  isDragging: dragGuideLines.isDragging,
@@ -11867,7 +12999,7 @@ function TaskGanttChartInner(props, ref) {
11867
12999
  totalHeight: totalGridHeight
11868
13000
  }
11869
13001
  ),
11870
- renderedChartTasks.map(({ task, index }) => /* @__PURE__ */ jsx18(
13002
+ renderedChartTasks.map(({ task, index }) => /* @__PURE__ */ jsx19(
11871
13003
  "div",
11872
13004
  {
11873
13005
  style: {
@@ -11877,7 +13009,7 @@ function TaskGanttChartInner(props, ref) {
11877
13009
  right: 0,
11878
13010
  height: `${effectiveRowHeight}px`
11879
13011
  },
11880
- children: /* @__PURE__ */ jsx18(
13012
+ children: /* @__PURE__ */ jsx19(
11881
13013
  TaskRow_default,
11882
13014
  {
11883
13015
  task,
@@ -11933,9 +13065,9 @@ var GanttChart = forwardRef(GanttChartInner);
11933
13065
  GanttChart.displayName = "GanttChart";
11934
13066
 
11935
13067
  // src/components/ui/Button.tsx
11936
- import React16 from "react";
11937
- import { jsx as jsx19 } from "react/jsx-runtime";
11938
- var Button = React16.forwardRef(
13068
+ import React17 from "react";
13069
+ import { jsx as jsx20 } from "react/jsx-runtime";
13070
+ var Button = React17.forwardRef(
11939
13071
  ({ className, variant = "default", size = "default", children, ...props }, ref) => {
11940
13072
  const classes = [
11941
13073
  "gantt-btn",
@@ -11943,7 +13075,7 @@ var Button = React16.forwardRef(
11943
13075
  size !== "default" ? `gantt-btn-${size}` : "",
11944
13076
  className || ""
11945
13077
  ].filter(Boolean).join(" ");
11946
- return /* @__PURE__ */ jsx19("button", { ref, className: classes, ...props, children });
13078
+ return /* @__PURE__ */ jsx20("button", { ref, className: classes, ...props, children });
11947
13079
  }
11948
13080
  );
11949
13081
  Button.displayName = "Button";
@@ -11990,6 +13122,7 @@ export {
11990
13122
  GanttChart,
11991
13123
  GridBackground_default as GridBackground,
11992
13124
  Input,
13125
+ PlanFactMatrix,
11993
13126
  Popover,
11994
13127
  PopoverContent,
11995
13128
  PopoverTrigger,