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.js CHANGED
@@ -41,6 +41,7 @@ __export(index_exports, {
41
41
  GanttChart: () => GanttChart,
42
42
  GridBackground: () => GridBackground_default,
43
43
  Input: () => Input,
44
+ PlanFactMatrix: () => PlanFactMatrix,
44
45
  Popover: () => Popover,
45
46
  PopoverContent: () => PopoverContent,
46
47
  PopoverTrigger: () => PopoverTrigger,
@@ -144,7 +145,7 @@ __export(index_exports, {
144
145
  module.exports = __toCommonJS(index_exports);
145
146
 
146
147
  // src/components/GanttChart/GanttChart.tsx
147
- var import_react17 = require("react");
148
+ var import_react18 = require("react");
148
149
 
149
150
  // src/core/scheduling/dateMath.ts
150
151
  var DAY_MS = 24 * 60 * 60 * 1e3;
@@ -698,10 +699,10 @@ function buildTaskRangeFromStart(startDate, duration, businessDays = false, week
698
699
  end: parseDateOnly(addBusinessDays(normalizedStart, duration, weekendPredicate))
699
700
  };
700
701
  }
701
- const DAY_MS5 = 24 * 60 * 60 * 1e3;
702
+ const DAY_MS6 = 24 * 60 * 60 * 1e3;
702
703
  return {
703
704
  start: normalizedStart,
704
- end: new Date(normalizedStart.getTime() + (Math.max(1, duration) - 1) * DAY_MS5)
705
+ end: new Date(normalizedStart.getTime() + (Math.max(1, duration) - 1) * DAY_MS6)
705
706
  };
706
707
  }
707
708
  function buildTaskRangeFromEnd(endDate, duration, businessDays = false, weekendPredicate, snapDirection = -1) {
@@ -712,9 +713,9 @@ function buildTaskRangeFromEnd(endDate, duration, businessDays = false, weekendP
712
713
  end: normalizedEnd
713
714
  };
714
715
  }
715
- const DAY_MS5 = 24 * 60 * 60 * 1e3;
716
+ const DAY_MS6 = 24 * 60 * 60 * 1e3;
716
717
  return {
717
- start: new Date(normalizedEnd.getTime() - (Math.max(1, duration) - 1) * DAY_MS5),
718
+ start: new Date(normalizedEnd.getTime() - (Math.max(1, duration) - 1) * DAY_MS6),
718
719
  end: normalizedEnd
719
720
  };
720
721
  }
@@ -1129,13 +1130,13 @@ function computeParentProgress(parentId, tasks) {
1129
1130
  if (children.length === 0) {
1130
1131
  return 0;
1131
1132
  }
1132
- const DAY_MS5 = 24 * 60 * 60 * 1e3;
1133
+ const DAY_MS6 = 24 * 60 * 60 * 1e3;
1133
1134
  let totalWeight = 0;
1134
1135
  let weightedSum = 0;
1135
1136
  for (const child of children) {
1136
1137
  const start = new Date(child.startDate).getTime();
1137
1138
  const end = new Date(child.endDate).getTime();
1138
- const duration = (end - start + DAY_MS5) / DAY_MS5;
1139
+ const duration = (end - start + DAY_MS6) / DAY_MS6;
1139
1140
  const progress = child.progress ?? 0;
1140
1141
  totalWeight += duration;
1141
1142
  weightedSum += duration * progress;
@@ -10564,6 +10565,1001 @@ function TableMatrix({
10564
10565
  ] });
10565
10566
  }
10566
10567
 
10568
+ // src/components/PlanFactMatrix/PlanFactMatrix.tsx
10569
+ var import_react17 = __toESM(require("react"));
10570
+ var import_jsx_runtime18 = require("react/jsx-runtime");
10571
+ var DAY_MS5 = 24 * 60 * 60 * 1e3;
10572
+ function joinClasses2(...values) {
10573
+ return values.filter(Boolean).join(" ");
10574
+ }
10575
+ function formatDateKey(date) {
10576
+ const year = date.getUTCFullYear();
10577
+ const month = String(date.getUTCMonth() + 1).padStart(2, "0");
10578
+ const day = String(date.getUTCDate()).padStart(2, "0");
10579
+ return `${year}-${month}-${day}`;
10580
+ }
10581
+ function escapeAttributeValue(value) {
10582
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
10583
+ }
10584
+ function parseNumberInput(value) {
10585
+ const trimmed = value.trim();
10586
+ if (trimmed === "") return void 0;
10587
+ const normalized = trimmed.replace(/\s+/g, "").replace(",", ".");
10588
+ const parsed = Number(normalized);
10589
+ if (!Number.isFinite(parsed) || parsed < 0) {
10590
+ return null;
10591
+ }
10592
+ return parsed;
10593
+ }
10594
+ function getDateOnlyMs(date) {
10595
+ return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
10596
+ }
10597
+ function getPlannedIndexRange(task, rangeStartMs, rangeLength) {
10598
+ if (rangeLength <= 0) return null;
10599
+ const start = parseUTCDate(task.startDate);
10600
+ const end = parseUTCDate(task.endDate);
10601
+ const startIndex = Math.ceil((getDateOnlyMs(start) - rangeStartMs) / DAY_MS5);
10602
+ const endIndex = Math.floor((getDateOnlyMs(end) - rangeStartMs) / DAY_MS5);
10603
+ const clampedStartIndex = Math.max(0, startIndex);
10604
+ const clampedEndIndex = Math.min(rangeLength - 1, endIndex);
10605
+ if (clampedStartIndex > clampedEndIndex) return null;
10606
+ return {
10607
+ startIndex: clampedStartIndex,
10608
+ endIndex: clampedEndIndex
10609
+ };
10610
+ }
10611
+ function isDateIndexWithinPlannedRange(dateIndex, range) {
10612
+ return !!range && range.startIndex <= dateIndex && dateIndex <= range.endIndex;
10613
+ }
10614
+ function formatValue(value) {
10615
+ if (value === void 0) return "";
10616
+ const absoluteValue = Math.abs(value);
10617
+ if (absoluteValue >= 1e6) {
10618
+ const compactValue = value / 1e6;
10619
+ const fractionDigits = absoluteValue >= 1e7 ? 0 : 1;
10620
+ return `${compactValue.toLocaleString("ru-RU", {
10621
+ minimumFractionDigits: 0,
10622
+ maximumFractionDigits: fractionDigits,
10623
+ useGrouping: false
10624
+ })}m`;
10625
+ }
10626
+ if (absoluteValue >= 1e4) {
10627
+ const compactValue = value / 1e3;
10628
+ const fractionDigits = absoluteValue >= 1e5 ? 0 : 1;
10629
+ return `${compactValue.toLocaleString("ru-RU", {
10630
+ minimumFractionDigits: 0,
10631
+ maximumFractionDigits: fractionDigits,
10632
+ useGrouping: false
10633
+ })}k`;
10634
+ }
10635
+ return Number.isInteger(value) ? String(value) : String(value).replace(".", ",");
10636
+ }
10637
+ function formatTooltipValue(value) {
10638
+ if (value === void 0) return "";
10639
+ return value.toLocaleString("ru-RU", { maximumFractionDigits: 20 });
10640
+ }
10641
+ function getSubrowIndex(taskIndex, kind) {
10642
+ return taskIndex * 2 + (kind === "plan" ? 0 : 1);
10643
+ }
10644
+ function findEditableTaskIndex(tasks, parentTaskIds, startIndex, step) {
10645
+ let index = startIndex;
10646
+ while (index >= 0 && index < tasks.length) {
10647
+ if (!parentTaskIds.has(tasks[index].id)) {
10648
+ return index;
10649
+ }
10650
+ index += step;
10651
+ }
10652
+ return null;
10653
+ }
10654
+ function PlanFactCellEditor({
10655
+ value,
10656
+ startValue,
10657
+ onCommit,
10658
+ onCommitRange,
10659
+ onCancel
10660
+ }) {
10661
+ const [draft, setDraft] = (0, import_react17.useState)(startValue ?? (value === void 0 ? "" : String(value)));
10662
+ const inputRef = (0, import_react17.useRef)(null);
10663
+ (0, import_react17.useEffect)(() => {
10664
+ inputRef.current?.focus();
10665
+ if (startValue === void 0) {
10666
+ inputRef.current?.select();
10667
+ } else {
10668
+ inputRef.current?.setSelectionRange(startValue.length, startValue.length);
10669
+ }
10670
+ }, [startValue]);
10671
+ const commit = (0, import_react17.useCallback)(() => {
10672
+ const parsed = parseNumberInput(draft);
10673
+ if (parsed === null) {
10674
+ onCancel();
10675
+ return;
10676
+ }
10677
+ onCommit(parsed);
10678
+ }, [draft, onCancel, onCommit]);
10679
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
10680
+ "input",
10681
+ {
10682
+ ref: inputRef,
10683
+ value: draft,
10684
+ className: "gantt-pf-editor",
10685
+ inputMode: "decimal",
10686
+ onChange: (event) => setDraft(event.target.value),
10687
+ onBlur: commit,
10688
+ onKeyDown: (event) => {
10689
+ if (event.key === "Escape") {
10690
+ event.preventDefault();
10691
+ onCancel();
10692
+ return;
10693
+ }
10694
+ if (event.key === "Enter") {
10695
+ event.preventDefault();
10696
+ if (event.ctrlKey || event.metaKey) {
10697
+ const parsed = parseNumberInput(draft);
10698
+ if (parsed === null) {
10699
+ onCancel();
10700
+ return;
10701
+ }
10702
+ onCommitRange?.(parsed);
10703
+ } else {
10704
+ commit();
10705
+ }
10706
+ }
10707
+ }
10708
+ }
10709
+ );
10710
+ }
10711
+ function getCellSignatureForTask(cell, taskId) {
10712
+ return cell?.taskId === taskId ? `${cell.dateIndex}:${cell.kind}` : "";
10713
+ }
10714
+ function getEditingCellSignatureForTask(cell, taskId) {
10715
+ return cell?.taskId === taskId ? `${cell.dateIndex}:${cell.kind}:${cell.startValue ?? ""}` : "";
10716
+ }
10717
+ function getRangeAnchorSignatureForTask(range, taskId) {
10718
+ return range?.anchor.taskId === taskId ? `${range.anchor.dateIndex}:${range.anchor.kind}` : "";
10719
+ }
10720
+ function doesRangeTouchRow(bounds, rowIndex) {
10721
+ if (!bounds) return false;
10722
+ const firstSubrowIndex = rowIndex * 2;
10723
+ const lastSubrowIndex = firstSubrowIndex + 1;
10724
+ return bounds.fromSubrowIndex <= lastSubrowIndex && bounds.toSubrowIndex >= firstSubrowIndex;
10725
+ }
10726
+ function areRangeBoundsEqual(left, right) {
10727
+ if (left === right) return true;
10728
+ if (!left || !right) return false;
10729
+ return left.fromDateIndex === right.fromDateIndex && left.toDateIndex === right.toDateIndex && left.fromSubrowIndex === right.fromSubrowIndex && left.toSubrowIndex === right.toSubrowIndex;
10730
+ }
10731
+ function PlanFactRowInner({
10732
+ task,
10733
+ rowIndex,
10734
+ dateRange,
10735
+ dateKeys,
10736
+ renderedDateIndices,
10737
+ rowHeight,
10738
+ subrowHeight,
10739
+ dayWidth,
10740
+ plannedRange,
10741
+ todayDateIndex,
10742
+ isParent,
10743
+ isHighlighted,
10744
+ selectedTaskId,
10745
+ activeCell,
10746
+ editingCell,
10747
+ selectedRange,
10748
+ renderedRangeBounds,
10749
+ didDragSelectRef,
10750
+ isSelectingRef,
10751
+ isFillDraggingRef,
10752
+ onTaskSelect,
10753
+ selectSingleCell,
10754
+ queueHoverCellUpdate,
10755
+ setActiveCell,
10756
+ setEditingCell,
10757
+ setFillRange,
10758
+ clearSelectedCells,
10759
+ commitCell,
10760
+ commitSelectedCells,
10761
+ moveActiveCell,
10762
+ extendSelectedRange,
10763
+ focusCell,
10764
+ showOverflowTooltip,
10765
+ hideOverflowTooltip
10766
+ }) {
10767
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
10768
+ "div",
10769
+ {
10770
+ "data-gantt-task-row-id": task.id,
10771
+ className: joinClasses2(
10772
+ "gantt-pf-row",
10773
+ isParent && "gantt-pf-row-parent",
10774
+ selectedTaskId === task.id && "gantt-pf-row-selected",
10775
+ isHighlighted && "gantt-pf-row-highlighted"
10776
+ ),
10777
+ style: {
10778
+ top: `${rowIndex * rowHeight}px`,
10779
+ height: `${rowHeight}px`,
10780
+ gridTemplateColumns: `repeat(${dateRange.length}, ${dayWidth}px)`,
10781
+ ["--gantt-pf-today-left"]: todayDateIndex !== void 0 && todayDateIndex >= 0 ? `${todayDateIndex * dayWidth}px` : void 0
10782
+ },
10783
+ onClick: () => onTaskSelect?.(task.id),
10784
+ children: renderedDateIndices.map((dateIndex) => {
10785
+ const dateKey = dateKeys[dateIndex];
10786
+ if (dateKey === void 0) return null;
10787
+ const planned = isDateIndexWithinPlannedRange(dateIndex, plannedRange);
10788
+ return ["plan", "fact"].map((kind) => {
10789
+ const subrowIndex = getSubrowIndex(rowIndex, kind);
10790
+ const isInRenderedRange = !!renderedRangeBounds && dateIndex >= renderedRangeBounds.fromDateIndex && dateIndex <= renderedRangeBounds.toDateIndex && subrowIndex >= renderedRangeBounds.fromSubrowIndex && subrowIndex <= renderedRangeBounds.toSubrowIndex;
10791
+ const planValue = task.planByDate?.[dateKey];
10792
+ const factValue = task.factByDate?.[dateKey];
10793
+ const value = kind === "plan" ? planValue : factValue;
10794
+ const factStatus = factValue === void 0 || planValue === void 0 ? null : factValue >= planValue ? "success" : "warning";
10795
+ const isActive = activeCell?.taskId === task.id && activeCell.dateIndex === dateIndex && activeCell.kind === kind;
10796
+ const isEditing = editingCell?.taskId === task.id && editingCell.dateIndex === dateIndex && editingCell.kind === kind;
10797
+ const currentCell = { taskId: task.id, dateIndex, kind };
10798
+ const isSelected = !isParent && isInRenderedRange;
10799
+ const showFillHandle = !isParent && !isEditing && isInRenderedRange && renderedRangeBounds !== null && dateIndex === renderedRangeBounds.toDateIndex && subrowIndex === renderedRangeBounds.toSubrowIndex;
10800
+ const isRangeAnchor = !showFillHandle && !isParent && selectedRange?.anchor.taskId === task.id && selectedRange.anchor.dateIndex === dateIndex && selectedRange.anchor.kind === kind;
10801
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
10802
+ "div",
10803
+ {
10804
+ "data-plan-fact-task-id": task.id,
10805
+ "data-plan-fact-date-index": dateIndex,
10806
+ "data-plan-fact-kind": kind,
10807
+ className: joinClasses2(
10808
+ "gantt-pf-cell",
10809
+ `gantt-pf-cell-${kind}`,
10810
+ planned && kind === "plan" && "gantt-pf-cell-planned",
10811
+ value !== void 0 && "gantt-pf-cell-hasValue",
10812
+ kind === "fact" && factStatus === "success" && "gantt-pf-cell-factSuccess",
10813
+ kind === "fact" && factStatus === "warning" && "gantt-pf-cell-factWarning",
10814
+ isSelected && "gantt-pf-cell-selected",
10815
+ isInRenderedRange && renderedRangeBounds !== null && dateIndex === renderedRangeBounds.fromDateIndex && "gantt-pf-cell-rangeLeft",
10816
+ isInRenderedRange && renderedRangeBounds !== null && dateIndex === renderedRangeBounds.toDateIndex && "gantt-pf-cell-rangeRight",
10817
+ isInRenderedRange && renderedRangeBounds !== null && subrowIndex === renderedRangeBounds.fromSubrowIndex && "gantt-pf-cell-rangeTop",
10818
+ isInRenderedRange && renderedRangeBounds !== null && subrowIndex === renderedRangeBounds.toSubrowIndex && "gantt-pf-cell-rangeBottom",
10819
+ isRangeAnchor && "gantt-pf-cell-rangeAnchor",
10820
+ isActive && "gantt-pf-cell-active",
10821
+ isEditing && "gantt-pf-cell-editing",
10822
+ isParent && "gantt-pf-cell-readonly"
10823
+ ),
10824
+ style: {
10825
+ gridColumn: dateIndex + 1,
10826
+ gridRow: kind === "plan" ? 1 : 2,
10827
+ height: `${subrowHeight}px`
10828
+ },
10829
+ tabIndex: isParent ? -1 : 0,
10830
+ onMouseDown: (event) => {
10831
+ if (isParent) return;
10832
+ event.preventDefault();
10833
+ event.stopPropagation();
10834
+ didDragSelectRef.current = false;
10835
+ isSelectingRef.current = true;
10836
+ selectSingleCell(currentCell);
10837
+ onTaskSelect?.(task.id);
10838
+ event.currentTarget.focus();
10839
+ },
10840
+ onMouseEnter: () => {
10841
+ if (isParent) return;
10842
+ if (!isFillDraggingRef.current && !isSelectingRef.current) return;
10843
+ queueHoverCellUpdate(currentCell);
10844
+ },
10845
+ onFocus: () => {
10846
+ if (isParent) return;
10847
+ setActiveCell({ taskId: task.id, dateIndex, kind });
10848
+ },
10849
+ onClick: (event) => {
10850
+ event.stopPropagation();
10851
+ if (didDragSelectRef.current) {
10852
+ didDragSelectRef.current = false;
10853
+ return;
10854
+ }
10855
+ onTaskSelect?.(task.id);
10856
+ if (isParent) return;
10857
+ selectSingleCell(currentCell);
10858
+ event.currentTarget.focus();
10859
+ },
10860
+ onDoubleClick: (event) => {
10861
+ event.stopPropagation();
10862
+ if (isParent) return;
10863
+ setEditingCell({ taskId: task.id, dateIndex, kind });
10864
+ },
10865
+ onKeyDown: (event) => {
10866
+ if (isParent || isEditing) return;
10867
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight" || event.key === "ArrowUp" || event.key === "ArrowDown") {
10868
+ event.preventDefault();
10869
+ event.stopPropagation();
10870
+ const direction = event.key.replace("Arrow", "").toLowerCase();
10871
+ if (event.shiftKey) {
10872
+ extendSelectedRange(selectedRange?.focus ?? currentCell, direction);
10873
+ } else {
10874
+ moveActiveCell(currentCell, direction);
10875
+ }
10876
+ return;
10877
+ }
10878
+ if (event.key === "Enter" || event.key === "F2") {
10879
+ event.preventDefault();
10880
+ event.stopPropagation();
10881
+ setEditingCell(selectedRange?.anchor ?? currentCell);
10882
+ return;
10883
+ }
10884
+ if (event.key === "Backspace" || event.key === "Delete") {
10885
+ event.preventDefault();
10886
+ event.stopPropagation();
10887
+ clearSelectedCells();
10888
+ return;
10889
+ }
10890
+ if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
10891
+ event.preventDefault();
10892
+ event.stopPropagation();
10893
+ setEditingCell({ ...currentCell, startValue: event.key });
10894
+ }
10895
+ },
10896
+ children: [
10897
+ isEditing ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
10898
+ PlanFactCellEditor,
10899
+ {
10900
+ value,
10901
+ startValue: editingCell.startValue,
10902
+ onCommit: (nextValue) => {
10903
+ commitCell(task, dateIndex, kind, nextValue);
10904
+ setEditingCell(null);
10905
+ const nextActiveCell = { taskId: task.id, dateIndex, kind };
10906
+ selectSingleCell(nextActiveCell);
10907
+ focusCell(nextActiveCell);
10908
+ },
10909
+ onCommitRange: (nextValue) => {
10910
+ commitSelectedCells(nextValue);
10911
+ setEditingCell(null);
10912
+ const nextActiveCell = { taskId: task.id, dateIndex, kind };
10913
+ selectSingleCell(nextActiveCell);
10914
+ focusCell(nextActiveCell);
10915
+ },
10916
+ onCancel: () => {
10917
+ setEditingCell(null);
10918
+ const nextActiveCell = { taskId: task.id, dateIndex, kind };
10919
+ selectSingleCell(nextActiveCell);
10920
+ focusCell(nextActiveCell);
10921
+ }
10922
+ }
10923
+ ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
10924
+ "span",
10925
+ {
10926
+ className: "gantt-pf-cellValue",
10927
+ onMouseEnter: (event) => {
10928
+ if (isParent || value === void 0) return;
10929
+ const compactValue = formatValue(value);
10930
+ const fullValue = formatTooltipValue(value);
10931
+ showOverflowTooltip(event.currentTarget, fullValue, compactValue !== fullValue);
10932
+ },
10933
+ onMouseLeave: hideOverflowTooltip,
10934
+ children: isParent ? "" : formatValue(value)
10935
+ }
10936
+ ),
10937
+ showFillHandle && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
10938
+ "span",
10939
+ {
10940
+ className: "gantt-pf-fillHandle",
10941
+ "aria-hidden": "true",
10942
+ onMouseDown: (event) => {
10943
+ event.preventDefault();
10944
+ event.stopPropagation();
10945
+ isSelectingRef.current = false;
10946
+ isFillDraggingRef.current = true;
10947
+ setFillRange(selectedRange);
10948
+ }
10949
+ }
10950
+ )
10951
+ ]
10952
+ },
10953
+ `${task.id}:${dateKey}:${kind}`
10954
+ );
10955
+ });
10956
+ })
10957
+ }
10958
+ );
10959
+ }
10960
+ function arePlanFactRowsEqual(previous, next) {
10961
+ const previousRangeTouchesRow = doesRangeTouchRow(previous.renderedRangeBounds, previous.rowIndex);
10962
+ const nextRangeTouchesRow = doesRangeTouchRow(next.renderedRangeBounds, next.rowIndex);
10963
+ 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));
10964
+ }
10965
+ var PlanFactRow = import_react17.default.memo(PlanFactRowInner, arePlanFactRowsEqual);
10966
+ function PlanFactMatrix({
10967
+ tasks,
10968
+ allTasks = tasks,
10969
+ dateRange,
10970
+ dayWidth,
10971
+ rowHeight,
10972
+ headerHeight,
10973
+ bodyMinHeight,
10974
+ selectedTaskId,
10975
+ onTaskSelect,
10976
+ onTasksChange,
10977
+ onCellCommit,
10978
+ highlightedTaskIds,
10979
+ filterMode = "highlight",
10980
+ visibleRowIndices,
10981
+ visibleDateIndices,
10982
+ todayDateIndex
10983
+ }) {
10984
+ const [activeCell, setActiveCell] = (0, import_react17.useState)(null);
10985
+ const [editingCell, setEditingCell] = (0, import_react17.useState)(null);
10986
+ const [selectedRange, setSelectedRange] = (0, import_react17.useState)(null);
10987
+ const [fillRange, setFillRange] = (0, import_react17.useState)(null);
10988
+ const [overflowTooltip, setOverflowTooltip] = (0, import_react17.useState)(null);
10989
+ const isSelectingRef = (0, import_react17.useRef)(false);
10990
+ const isFillDraggingRef = (0, import_react17.useRef)(false);
10991
+ const didDragSelectRef = (0, import_react17.useRef)(false);
10992
+ const pendingHoverCellRef = (0, import_react17.useRef)(null);
10993
+ const hoverFrameRef = (0, import_react17.useRef)(null);
10994
+ const rootRef = (0, import_react17.useRef)(null);
10995
+ const bodyRef = (0, import_react17.useRef)(null);
10996
+ const totalWidth = dateRange.length * dayWidth;
10997
+ const subrowHeight = rowHeight / 2;
10998
+ const renderedRowIndices = (0, import_react17.useMemo)(
10999
+ () => visibleRowIndices ?? tasks.map((_, index) => index),
11000
+ [tasks, visibleRowIndices]
11001
+ );
11002
+ const renderedDateIndices = (0, import_react17.useMemo)(
11003
+ () => visibleDateIndices ?? dateRange.map((_, index) => index),
11004
+ [dateRange, visibleDateIndices]
11005
+ );
11006
+ const parentTaskIds = (0, import_react17.useMemo)(() => {
11007
+ const ids = /* @__PURE__ */ new Set();
11008
+ for (const task of allTasks) {
11009
+ if (task.parentId) ids.add(task.parentId);
11010
+ }
11011
+ return ids;
11012
+ }, [allTasks]);
11013
+ const dateKeys = (0, import_react17.useMemo)(() => dateRange.map(formatDateKey), [dateRange]);
11014
+ const dateRangeStartMs = dateRange[0] ? getDateOnlyMs(dateRange[0]) : 0;
11015
+ const taskIndexById = (0, import_react17.useMemo)(() => {
11016
+ const indexById = /* @__PURE__ */ new Map();
11017
+ tasks.forEach((task, index) => indexById.set(task.id, index));
11018
+ return indexById;
11019
+ }, [tasks]);
11020
+ const taskById = (0, import_react17.useMemo)(
11021
+ () => new Map(tasks.map((task) => [task.id, task])),
11022
+ [tasks]
11023
+ );
11024
+ const monthSeparatorIndices = (0, import_react17.useMemo)(() => {
11025
+ const indices = [];
11026
+ for (let index = 1; index < dateRange.length; index += 1) {
11027
+ if (dateRange[index].getUTCDate() === 1) {
11028
+ indices.push(index);
11029
+ }
11030
+ }
11031
+ return indices;
11032
+ }, [dateRange]);
11033
+ const plannedRangeByTaskId = (0, import_react17.useMemo)(() => {
11034
+ const rangeByTaskId = /* @__PURE__ */ new Map();
11035
+ for (const task of tasks) {
11036
+ rangeByTaskId.set(task.id, getPlannedIndexRange(task, dateRangeStartMs, dateRange.length));
11037
+ }
11038
+ return rangeByTaskId;
11039
+ }, [dateRange.length, dateRangeStartMs, tasks]);
11040
+ const focusCell = (0, import_react17.useCallback)((cell) => {
11041
+ window.requestAnimationFrame(() => {
11042
+ const selector = [
11043
+ `[data-plan-fact-task-id="${escapeAttributeValue(cell.taskId)}"]`,
11044
+ `[data-plan-fact-date-index="${cell.dateIndex}"]`,
11045
+ `[data-plan-fact-kind="${cell.kind}"]`
11046
+ ].join("");
11047
+ bodyRef.current?.querySelector(selector)?.focus();
11048
+ });
11049
+ }, []);
11050
+ const clearSelection = (0, import_react17.useCallback)(() => {
11051
+ if (hoverFrameRef.current !== null) {
11052
+ window.cancelAnimationFrame(hoverFrameRef.current);
11053
+ hoverFrameRef.current = null;
11054
+ }
11055
+ pendingHoverCellRef.current = null;
11056
+ isSelectingRef.current = false;
11057
+ isFillDraggingRef.current = false;
11058
+ didDragSelectRef.current = false;
11059
+ setActiveCell(null);
11060
+ setEditingCell(null);
11061
+ setSelectedRange(null);
11062
+ setFillRange(null);
11063
+ setOverflowTooltip(null);
11064
+ }, []);
11065
+ const hideOverflowTooltip = (0, import_react17.useCallback)(() => {
11066
+ setOverflowTooltip(null);
11067
+ }, []);
11068
+ const flushPendingHoverCell = (0, import_react17.useCallback)(() => {
11069
+ hoverFrameRef.current = null;
11070
+ const currentCell = pendingHoverCellRef.current;
11071
+ if (!currentCell) return;
11072
+ if (isFillDraggingRef.current && selectedRange) {
11073
+ setFillRange({ anchor: selectedRange.anchor, focus: currentCell });
11074
+ setActiveCell(currentCell);
11075
+ onTaskSelect?.(currentCell.taskId);
11076
+ return;
11077
+ }
11078
+ if (!isSelectingRef.current) return;
11079
+ didDragSelectRef.current = true;
11080
+ setSelectedRange((currentRange) => ({
11081
+ anchor: currentRange?.anchor ?? currentCell,
11082
+ focus: currentCell
11083
+ }));
11084
+ onTaskSelect?.(currentCell.taskId);
11085
+ }, [onTaskSelect, selectedRange]);
11086
+ const queueHoverCellUpdate = (0, import_react17.useCallback)((cell) => {
11087
+ pendingHoverCellRef.current = cell;
11088
+ if (hoverFrameRef.current !== null) return;
11089
+ hoverFrameRef.current = window.requestAnimationFrame(flushPendingHoverCell);
11090
+ }, [flushPendingHoverCell]);
11091
+ const showOverflowTooltip = (0, import_react17.useCallback)((target, label, force = false) => {
11092
+ if (!rootRef.current || !label) return;
11093
+ if (!force && target.scrollWidth <= target.clientWidth) {
11094
+ setOverflowTooltip(null);
11095
+ return;
11096
+ }
11097
+ const rootRect = rootRef.current.getBoundingClientRect();
11098
+ const targetRect = target.getBoundingClientRect();
11099
+ setOverflowTooltip({
11100
+ label,
11101
+ left: targetRect.left - rootRect.left + targetRect.width / 2,
11102
+ top: targetRect.top - rootRect.top
11103
+ });
11104
+ }, []);
11105
+ const selectSingleCell = (0, import_react17.useCallback)((cell) => {
11106
+ setActiveCell(cell);
11107
+ setSelectedRange({ anchor: cell, focus: cell });
11108
+ setFillRange(null);
11109
+ }, []);
11110
+ const getRangeBounds = (0, import_react17.useCallback)((range) => {
11111
+ const anchorTaskIndex = taskIndexById.get(range.anchor.taskId);
11112
+ const focusTaskIndex = taskIndexById.get(range.focus.taskId);
11113
+ if (anchorTaskIndex === void 0 || focusTaskIndex === void 0) {
11114
+ return null;
11115
+ }
11116
+ return {
11117
+ fromDateIndex: Math.min(range.anchor.dateIndex, range.focus.dateIndex),
11118
+ toDateIndex: Math.max(range.anchor.dateIndex, range.focus.dateIndex),
11119
+ fromSubrowIndex: Math.min(
11120
+ getSubrowIndex(anchorTaskIndex, range.anchor.kind),
11121
+ getSubrowIndex(focusTaskIndex, range.focus.kind)
11122
+ ),
11123
+ toSubrowIndex: Math.max(
11124
+ getSubrowIndex(anchorTaskIndex, range.anchor.kind),
11125
+ getSubrowIndex(focusTaskIndex, range.focus.kind)
11126
+ )
11127
+ };
11128
+ }, [taskIndexById]);
11129
+ const renderedRange = fillRange ?? selectedRange;
11130
+ const renderedRangeBounds = (0, import_react17.useMemo)(
11131
+ () => renderedRange ? getRangeBounds(renderedRange) : null,
11132
+ [getRangeBounds, renderedRange]
11133
+ );
11134
+ const getCellFromPosition = (0, import_react17.useCallback)((subrowIndex, dateIndex) => {
11135
+ const task = tasks[Math.floor(subrowIndex / 2)];
11136
+ if (!task) return null;
11137
+ return {
11138
+ taskId: task.id,
11139
+ dateIndex,
11140
+ kind: subrowIndex % 2 === 0 ? "plan" : "fact"
11141
+ };
11142
+ }, [tasks]);
11143
+ const isCellInRange = (0, import_react17.useCallback)((cell, range) => {
11144
+ const bounds = getRangeBounds(range);
11145
+ if (!bounds) return false;
11146
+ const cellTaskIndex = taskIndexById.get(cell.taskId);
11147
+ if (cellTaskIndex === void 0) {
11148
+ return false;
11149
+ }
11150
+ const cellSubrowIndex = getSubrowIndex(cellTaskIndex, cell.kind);
11151
+ return cell.dateIndex >= bounds.fromDateIndex && cell.dateIndex <= bounds.toDateIndex && cellSubrowIndex >= bounds.fromSubrowIndex && cellSubrowIndex <= bounds.toSubrowIndex;
11152
+ }, [getRangeBounds, taskIndexById]);
11153
+ const commitCell = (0, import_react17.useCallback)((task, dateIndex, kind, value) => {
11154
+ const dateKey = dateKeys[dateIndex];
11155
+ const source = kind === "plan" ? task.planByDate : task.factByDate;
11156
+ const nextValues = { ...source ?? {} };
11157
+ if (value === void 0) {
11158
+ delete nextValues[dateKey];
11159
+ } else {
11160
+ nextValues[dateKey] = value;
11161
+ }
11162
+ const changedTask = {
11163
+ ...task,
11164
+ [kind === "plan" ? "planByDate" : "factByDate"]: nextValues
11165
+ };
11166
+ onTasksChange?.([changedTask]);
11167
+ onCellCommit?.({
11168
+ task,
11169
+ date: dateRange[dateIndex],
11170
+ dateKey,
11171
+ kind,
11172
+ value
11173
+ });
11174
+ }, [dateKeys, dateRange, onCellCommit, onTasksChange]);
11175
+ const commitRangeCells = (0, import_react17.useCallback)((bounds, value, mode) => {
11176
+ const changedTasksById = /* @__PURE__ */ new Map();
11177
+ for (let subrowIndex = bounds.fromSubrowIndex; subrowIndex <= bounds.toSubrowIndex; subrowIndex += 1) {
11178
+ const task = tasks[Math.floor(subrowIndex / 2)];
11179
+ if (!task || parentTaskIds.has(task.id)) continue;
11180
+ const kind = subrowIndex % 2 === 0 ? "plan" : "fact";
11181
+ const currentChangedTask = changedTasksById.get(task.id) ?? task;
11182
+ let nextPlanByDate = currentChangedTask.planByDate;
11183
+ let nextFactByDate = currentChangedTask.factByDate;
11184
+ let didChange = false;
11185
+ for (let dateIndex = bounds.fromDateIndex; dateIndex <= bounds.toDateIndex; dateIndex += 1) {
11186
+ const dateKey = dateKeys[dateIndex];
11187
+ if (dateKey === void 0) continue;
11188
+ const currentValues = kind === "plan" ? nextPlanByDate : nextFactByDate;
11189
+ const currentValue = currentValues?.[dateKey];
11190
+ const nextValue = mode === "clear" ? void 0 : value;
11191
+ if (currentValue === nextValue) continue;
11192
+ if (kind === "plan") {
11193
+ nextPlanByDate = { ...nextPlanByDate ?? {} };
11194
+ if (nextValue === void 0) {
11195
+ delete nextPlanByDate[dateKey];
11196
+ } else {
11197
+ nextPlanByDate[dateKey] = nextValue;
11198
+ }
11199
+ } else {
11200
+ nextFactByDate = { ...nextFactByDate ?? {} };
11201
+ if (nextValue === void 0) {
11202
+ delete nextFactByDate[dateKey];
11203
+ } else {
11204
+ nextFactByDate[dateKey] = nextValue;
11205
+ }
11206
+ }
11207
+ didChange = true;
11208
+ }
11209
+ if (didChange) {
11210
+ changedTasksById.set(task.id, {
11211
+ ...currentChangedTask,
11212
+ ...nextPlanByDate !== currentChangedTask.planByDate ? { planByDate: nextPlanByDate ?? {} } : {},
11213
+ ...nextFactByDate !== currentChangedTask.factByDate ? { factByDate: nextFactByDate ?? {} } : {}
11214
+ });
11215
+ }
11216
+ }
11217
+ const changedTasks = Array.from(changedTasksById.values());
11218
+ if (changedTasks.length > 0) {
11219
+ onTasksChange?.(changedTasks);
11220
+ }
11221
+ }, [dateKeys, onTasksChange, parentTaskIds, tasks]);
11222
+ const clearSelectedCells = (0, import_react17.useCallback)(() => {
11223
+ const activeRange = fillRange ?? selectedRange;
11224
+ if (!activeRange) {
11225
+ if (!activeCell) return;
11226
+ const task = taskById.get(activeCell.taskId);
11227
+ if (!task || parentTaskIds.has(task.id)) return;
11228
+ commitCell(task, activeCell.dateIndex, activeCell.kind, void 0);
11229
+ return;
11230
+ }
11231
+ const bounds = getRangeBounds(activeRange);
11232
+ if (bounds) {
11233
+ commitRangeCells(bounds, void 0, "clear");
11234
+ }
11235
+ }, [activeCell, commitCell, commitRangeCells, fillRange, getRangeBounds, parentTaskIds, selectedRange, taskById]);
11236
+ const commitSelectedCells = (0, import_react17.useCallback)((value) => {
11237
+ const activeRange = fillRange ?? selectedRange;
11238
+ if (!activeRange) {
11239
+ if (!activeCell) return;
11240
+ const task = taskById.get(activeCell.taskId);
11241
+ if (!task || parentTaskIds.has(task.id)) return;
11242
+ commitCell(task, activeCell.dateIndex, activeCell.kind, value);
11243
+ return;
11244
+ }
11245
+ const bounds = getRangeBounds(activeRange);
11246
+ if (bounds) {
11247
+ commitRangeCells(bounds, value, "set");
11248
+ }
11249
+ }, [activeCell, commitCell, commitRangeCells, fillRange, getRangeBounds, parentTaskIds, selectedRange, taskById]);
11250
+ const getCellValue = (0, import_react17.useCallback)((cell) => {
11251
+ const task = taskById.get(cell.taskId);
11252
+ if (!task) return void 0;
11253
+ const dateKey = dateKeys[cell.dateIndex];
11254
+ return cell.kind === "plan" ? task.planByDate?.[dateKey] : task.factByDate?.[dateKey];
11255
+ }, [dateKeys, taskById]);
11256
+ const applyFillRange = (0, import_react17.useCallback)((nextFillRange) => {
11257
+ const targetRange = nextFillRange ?? fillRange;
11258
+ if (!selectedRange || !targetRange) return;
11259
+ const sourceBounds = getRangeBounds(selectedRange);
11260
+ const targetBounds = getRangeBounds(targetRange);
11261
+ if (!sourceBounds || !targetBounds) return;
11262
+ const sourceDateSpan = sourceBounds.toDateIndex - sourceBounds.fromDateIndex + 1;
11263
+ const sourceSubrowSpan = sourceBounds.toSubrowIndex - sourceBounds.fromSubrowIndex + 1;
11264
+ const changedTasksById = /* @__PURE__ */ new Map();
11265
+ for (let subrowIndex = targetBounds.fromSubrowIndex; subrowIndex <= targetBounds.toSubrowIndex; subrowIndex += 1) {
11266
+ const targetCellForRow = getCellFromPosition(subrowIndex, targetBounds.fromDateIndex);
11267
+ if (!targetCellForRow || parentTaskIds.has(targetCellForRow.taskId)) continue;
11268
+ const originalTask = taskById.get(targetCellForRow.taskId);
11269
+ if (!originalTask) continue;
11270
+ let changedTask = changedTasksById.get(originalTask.id) ?? originalTask;
11271
+ let nextPlanByDate = changedTask.planByDate;
11272
+ let nextFactByDate = changedTask.factByDate;
11273
+ let didChange = false;
11274
+ for (let dateIndex = targetBounds.fromDateIndex; dateIndex <= targetBounds.toDateIndex; dateIndex += 1) {
11275
+ const targetCell = getCellFromPosition(subrowIndex, dateIndex);
11276
+ if (!targetCell || isCellInRange(targetCell, selectedRange)) continue;
11277
+ const sourceSubrowIndex = sourceBounds.fromSubrowIndex + ((subrowIndex - sourceBounds.fromSubrowIndex) % sourceSubrowSpan + sourceSubrowSpan) % sourceSubrowSpan;
11278
+ const sourceDateIndex = sourceBounds.fromDateIndex + ((dateIndex - sourceBounds.fromDateIndex) % sourceDateSpan + sourceDateSpan) % sourceDateSpan;
11279
+ const sourceCell = getCellFromPosition(sourceSubrowIndex, sourceDateIndex);
11280
+ if (!sourceCell) continue;
11281
+ const nextValue = getCellValue(sourceCell);
11282
+ const dateKey = dateKeys[dateIndex];
11283
+ const currentValues = targetCell.kind === "plan" ? nextPlanByDate : nextFactByDate;
11284
+ if (currentValues?.[dateKey] === nextValue) continue;
11285
+ if (targetCell.kind === "plan") {
11286
+ nextPlanByDate = { ...nextPlanByDate ?? {} };
11287
+ if (nextValue === void 0) {
11288
+ delete nextPlanByDate[dateKey];
11289
+ } else {
11290
+ nextPlanByDate[dateKey] = nextValue;
11291
+ }
11292
+ } else {
11293
+ nextFactByDate = { ...nextFactByDate ?? {} };
11294
+ if (nextValue === void 0) {
11295
+ delete nextFactByDate[dateKey];
11296
+ } else {
11297
+ nextFactByDate[dateKey] = nextValue;
11298
+ }
11299
+ }
11300
+ didChange = true;
11301
+ }
11302
+ if (didChange) {
11303
+ changedTasksById.set(originalTask.id, {
11304
+ ...changedTask,
11305
+ ...nextPlanByDate !== changedTask.planByDate ? { planByDate: nextPlanByDate ?? {} } : {},
11306
+ ...nextFactByDate !== changedTask.factByDate ? { factByDate: nextFactByDate ?? {} } : {}
11307
+ });
11308
+ }
11309
+ }
11310
+ const changedTasks = Array.from(changedTasksById.values());
11311
+ if (changedTasks.length > 0) {
11312
+ onTasksChange?.(changedTasks);
11313
+ }
11314
+ setSelectedRange(targetRange);
11315
+ setActiveCell(targetRange.anchor);
11316
+ setFillRange(null);
11317
+ }, [
11318
+ dateKeys,
11319
+ fillRange,
11320
+ getCellFromPosition,
11321
+ getCellValue,
11322
+ getRangeBounds,
11323
+ isCellInRange,
11324
+ onTasksChange,
11325
+ parentTaskIds,
11326
+ selectedRange,
11327
+ taskById
11328
+ ]);
11329
+ const moveActiveCell = (0, import_react17.useCallback)((cell, direction) => {
11330
+ const taskIndex = tasks.findIndex((task) => task.id === cell.taskId);
11331
+ if (taskIndex < 0) return;
11332
+ let nextCell = { ...cell };
11333
+ if (direction === "left") {
11334
+ nextCell.dateIndex = Math.max(0, cell.dateIndex - 1);
11335
+ } else if (direction === "right") {
11336
+ nextCell.dateIndex = Math.min(dateRange.length - 1, cell.dateIndex + 1);
11337
+ } else if (direction === "up") {
11338
+ if (cell.kind === "fact") {
11339
+ nextCell.kind = "plan";
11340
+ } else {
11341
+ const previousEditableTaskIndex = findEditableTaskIndex(tasks, parentTaskIds, taskIndex - 1, -1);
11342
+ if (previousEditableTaskIndex === null) return;
11343
+ const previousTask = tasks[previousEditableTaskIndex];
11344
+ nextCell = { taskId: previousTask.id, dateIndex: cell.dateIndex, kind: "fact" };
11345
+ }
11346
+ } else if (direction === "down") {
11347
+ if (cell.kind === "plan") {
11348
+ nextCell.kind = "fact";
11349
+ } else {
11350
+ const nextEditableTaskIndex = findEditableTaskIndex(tasks, parentTaskIds, taskIndex + 1, 1);
11351
+ if (nextEditableTaskIndex === null) return;
11352
+ const nextTask = tasks[nextEditableTaskIndex];
11353
+ nextCell = { taskId: nextTask.id, dateIndex: cell.dateIndex, kind: "plan" };
11354
+ }
11355
+ }
11356
+ setActiveCell(nextCell);
11357
+ setSelectedRange({ anchor: nextCell, focus: nextCell });
11358
+ onTaskSelect?.(nextCell.taskId);
11359
+ focusCell(nextCell);
11360
+ }, [dateRange.length, focusCell, onTaskSelect, parentTaskIds, tasks]);
11361
+ const extendSelectedRange = (0, import_react17.useCallback)((cell, direction) => {
11362
+ const taskIndex = tasks.findIndex((task) => task.id === cell.taskId);
11363
+ if (taskIndex < 0) return;
11364
+ let nextCell = { ...cell };
11365
+ if (direction === "left") {
11366
+ nextCell.dateIndex = Math.max(0, cell.dateIndex - 1);
11367
+ } else if (direction === "right") {
11368
+ nextCell.dateIndex = Math.min(dateRange.length - 1, cell.dateIndex + 1);
11369
+ } else if (direction === "up") {
11370
+ if (cell.kind === "fact") {
11371
+ nextCell.kind = "plan";
11372
+ } else {
11373
+ const previousEditableTaskIndex = findEditableTaskIndex(tasks, parentTaskIds, taskIndex - 1, -1);
11374
+ if (previousEditableTaskIndex === null) return;
11375
+ const previousTask = tasks[previousEditableTaskIndex];
11376
+ nextCell = { taskId: previousTask.id, dateIndex: cell.dateIndex, kind: "fact" };
11377
+ }
11378
+ } else if (direction === "down") {
11379
+ if (cell.kind === "plan") {
11380
+ nextCell.kind = "fact";
11381
+ } else {
11382
+ const nextEditableTaskIndex = findEditableTaskIndex(tasks, parentTaskIds, taskIndex + 1, 1);
11383
+ if (nextEditableTaskIndex === null) return;
11384
+ const nextTask = tasks[nextEditableTaskIndex];
11385
+ nextCell = { taskId: nextTask.id, dateIndex: cell.dateIndex, kind: "plan" };
11386
+ }
11387
+ }
11388
+ setActiveCell((currentActiveCell) => currentActiveCell ?? cell);
11389
+ setFillRange(null);
11390
+ setSelectedRange((currentRange) => ({
11391
+ anchor: currentRange?.anchor ?? cell,
11392
+ focus: nextCell
11393
+ }));
11394
+ onTaskSelect?.(nextCell.taskId);
11395
+ focusCell(selectedRange?.anchor ?? cell);
11396
+ }, [dateRange.length, focusCell, onTaskSelect, parentTaskIds, selectedRange, tasks]);
11397
+ (0, import_react17.useEffect)(() => {
11398
+ const endSelection = () => {
11399
+ let pendingFillRange = null;
11400
+ if (hoverFrameRef.current !== null) {
11401
+ window.cancelAnimationFrame(hoverFrameRef.current);
11402
+ hoverFrameRef.current = null;
11403
+ const pendingCell = pendingHoverCellRef.current;
11404
+ if (pendingCell && isFillDraggingRef.current && selectedRange) {
11405
+ pendingFillRange = { anchor: selectedRange.anchor, focus: pendingCell };
11406
+ }
11407
+ flushPendingHoverCell();
11408
+ }
11409
+ if (isFillDraggingRef.current) {
11410
+ isFillDraggingRef.current = false;
11411
+ applyFillRange(pendingFillRange);
11412
+ }
11413
+ isSelectingRef.current = false;
11414
+ };
11415
+ window.addEventListener("mouseup", endSelection);
11416
+ return () => {
11417
+ window.removeEventListener("mouseup", endSelection);
11418
+ };
11419
+ }, [applyFillRange, flushPendingHoverCell]);
11420
+ (0, import_react17.useEffect)(() => () => {
11421
+ if (hoverFrameRef.current !== null) {
11422
+ window.cancelAnimationFrame(hoverFrameRef.current);
11423
+ }
11424
+ }, []);
11425
+ (0, import_react17.useEffect)(() => {
11426
+ const handleKeyDown = (event) => {
11427
+ if (event.key !== "Escape") return;
11428
+ clearSelection();
11429
+ };
11430
+ const handleMouseDown = (event) => {
11431
+ const target = event.target;
11432
+ if (target instanceof Node && rootRef.current?.contains(target)) {
11433
+ return;
11434
+ }
11435
+ clearSelection();
11436
+ };
11437
+ window.addEventListener("keydown", handleKeyDown);
11438
+ document.addEventListener("mousedown", handleMouseDown);
11439
+ return () => {
11440
+ window.removeEventListener("keydown", handleKeyDown);
11441
+ document.removeEventListener("mousedown", handleMouseDown);
11442
+ };
11443
+ }, [clearSelection]);
11444
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
11445
+ "div",
11446
+ {
11447
+ ref: rootRef,
11448
+ className: "gantt-pf-root",
11449
+ style: {
11450
+ width: `${totalWidth}px`,
11451
+ ["--gantt-pf-day-width"]: `${dayWidth}px`
11452
+ },
11453
+ children: [
11454
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "gantt-pf-header", style: { width: `${totalWidth}px`, height: `${headerHeight}px` }, children: [
11455
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
11456
+ TimeScaleHeader_default,
11457
+ {
11458
+ days: dateRange,
11459
+ dayWidth,
11460
+ headerHeight: headerHeight - 1,
11461
+ viewMode: "day"
11462
+ }
11463
+ ),
11464
+ todayDateIndex !== void 0 && todayDateIndex >= 0 && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
11465
+ "span",
11466
+ {
11467
+ className: "gantt-pf-headerTodayLine",
11468
+ "aria-hidden": "true",
11469
+ style: {
11470
+ left: `${todayDateIndex * dayWidth}px`,
11471
+ top: `${Math.max(0, headerHeight / 2)}px`
11472
+ }
11473
+ }
11474
+ )
11475
+ ] }),
11476
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "gantt-pf-monthSeparatorLayer", "aria-hidden": "true", children: monthSeparatorIndices.map((dateIndex) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
11477
+ "span",
11478
+ {
11479
+ className: "gantt-pf-monthSeparator",
11480
+ style: {
11481
+ left: `${Math.round(dateIndex * dayWidth)}px`,
11482
+ top: `${Math.max(0, headerHeight / 2)}px`
11483
+ }
11484
+ },
11485
+ `month-separator-${dateIndex}`
11486
+ )) }),
11487
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
11488
+ "div",
11489
+ {
11490
+ ref: bodyRef,
11491
+ className: "gantt-pf-body",
11492
+ style: {
11493
+ height: `${tasks.length * rowHeight}px`,
11494
+ minHeight: bodyMinHeight,
11495
+ width: `${totalWidth}px`
11496
+ },
11497
+ children: renderedRowIndices.map((rowIndex) => {
11498
+ const task = tasks[rowIndex];
11499
+ if (!task) return null;
11500
+ const isParent = parentTaskIds.has(task.id);
11501
+ const isHighlighted = filterMode === "highlight" && !!highlightedTaskIds?.has(task.id);
11502
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
11503
+ PlanFactRow,
11504
+ {
11505
+ task,
11506
+ rowIndex,
11507
+ dateRange,
11508
+ dateKeys,
11509
+ renderedDateIndices,
11510
+ rowHeight,
11511
+ subrowHeight,
11512
+ dayWidth,
11513
+ plannedRange: plannedRangeByTaskId.get(task.id) ?? null,
11514
+ todayDateIndex,
11515
+ isParent,
11516
+ isHighlighted,
11517
+ selectedTaskId,
11518
+ activeCell,
11519
+ editingCell,
11520
+ selectedRange,
11521
+ renderedRangeBounds,
11522
+ didDragSelectRef,
11523
+ isSelectingRef,
11524
+ isFillDraggingRef,
11525
+ onTaskSelect,
11526
+ selectSingleCell,
11527
+ queueHoverCellUpdate,
11528
+ setActiveCell,
11529
+ setEditingCell,
11530
+ setFillRange,
11531
+ clearSelectedCells,
11532
+ commitCell,
11533
+ commitSelectedCells,
11534
+ moveActiveCell,
11535
+ extendSelectedRange,
11536
+ focusCell,
11537
+ showOverflowTooltip,
11538
+ hideOverflowTooltip
11539
+ },
11540
+ task.id
11541
+ );
11542
+ })
11543
+ }
11544
+ ),
11545
+ overflowTooltip && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
11546
+ "div",
11547
+ {
11548
+ className: "gantt-pf-overflowTooltip",
11549
+ role: "tooltip",
11550
+ "aria-hidden": "true",
11551
+ style: {
11552
+ left: `${overflowTooltip.left}px`,
11553
+ top: `${overflowTooltip.top}px`
11554
+ },
11555
+ children: overflowTooltip.label
11556
+ }
11557
+ )
11558
+ ]
11559
+ }
11560
+ );
11561
+ }
11562
+
10567
11563
  // src/components/GanttChart/print.ts
10568
11564
  function getPrintDocumentTitle({
10569
11565
  header,
@@ -10972,9 +11968,60 @@ function createTaskPreviewPositionStore() {
10972
11968
  }
10973
11969
 
10974
11970
  // src/components/GanttChart/GanttChart.tsx
10975
- var import_jsx_runtime18 = require("react/jsx-runtime");
11971
+ var import_jsx_runtime19 = require("react/jsx-runtime");
10976
11972
  var SCROLL_TO_ROW_CONTEXT_ROWS = 2;
10977
11973
  var TASK_ROW_OVERSCAN = 8;
11974
+ var PLAN_FACT_COLUMN_OVERSCAN = 24;
11975
+ var PLAN_FACT_COLUMN_WINDOW_STEP = 14;
11976
+ function getFullMonthDays(tasks) {
11977
+ if (!tasks || tasks.length === 0) {
11978
+ return getMultiMonthDays(tasks);
11979
+ }
11980
+ let minDate = null;
11981
+ let maxDate = null;
11982
+ for (const task of tasks) {
11983
+ const start = parseUTCDate(task.startDate);
11984
+ const end = parseUTCDate(task.endDate);
11985
+ if (!minDate || start.getTime() < minDate.getTime()) {
11986
+ minDate = start;
11987
+ }
11988
+ if (!maxDate || end.getTime() > maxDate.getTime()) {
11989
+ maxDate = end;
11990
+ }
11991
+ }
11992
+ if (!minDate || !maxDate) {
11993
+ return getMultiMonthDays(tasks);
11994
+ }
11995
+ const startOfMonth2 = new Date(Date.UTC(minDate.getUTCFullYear(), minDate.getUTCMonth(), 1));
11996
+ const endOfMonth = new Date(Date.UTC(maxDate.getUTCFullYear(), maxDate.getUTCMonth() + 1, 0));
11997
+ const days = [];
11998
+ const current = new Date(startOfMonth2);
11999
+ while (current.getTime() <= endOfMonth.getTime()) {
12000
+ days.push(new Date(Date.UTC(
12001
+ current.getUTCFullYear(),
12002
+ current.getUTCMonth(),
12003
+ current.getUTCDate()
12004
+ )));
12005
+ current.setUTCDate(current.getUTCDate() + 1);
12006
+ }
12007
+ return days;
12008
+ }
12009
+ function getPlanFactRangeTasks(tasks) {
12010
+ const rangeTasks = [];
12011
+ for (const task of tasks) {
12012
+ rangeTasks.push({ startDate: task.startDate, endDate: task.endDate });
12013
+ for (const dateKey of Object.keys(task.planByDate ?? {})) {
12014
+ rangeTasks.push({ startDate: dateKey, endDate: dateKey });
12015
+ }
12016
+ for (const dateKey of Object.keys(task.factByDate ?? {})) {
12017
+ rangeTasks.push({ startDate: dateKey, endDate: dateKey });
12018
+ }
12019
+ }
12020
+ return rangeTasks;
12021
+ }
12022
+ function clampScrollValue(value, max) {
12023
+ return Math.min(max, Math.max(0, value));
12024
+ }
10978
12025
  function arePositionMapsEqual(left, right) {
10979
12026
  if (left.size !== right.size) return false;
10980
12027
  for (const [taskId, leftPosition] of left) {
@@ -11011,9 +12058,9 @@ function arePreviewTaskMapsEqual(left, right) {
11011
12058
  }
11012
12059
  function GanttChartInner(props, ref) {
11013
12060
  if (props.mode === "resource-planner") {
11014
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ResourceTimelineChart, { ...props });
12061
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ResourceTimelineChart, { ...props });
11015
12062
  }
11016
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
12063
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
11017
12064
  TaskGanttChart,
11018
12065
  {
11019
12066
  ...props,
@@ -11023,6 +12070,7 @@ function GanttChartInner(props, ref) {
11023
12070
  }
11024
12071
  function TaskGanttChartInner(props, ref) {
11025
12072
  const isTableMatrixMode = props.mode === "table-matrix";
12073
+ const isPlanFactMode = props.mode === "plan-fact";
11026
12074
  const {
11027
12075
  tasks,
11028
12076
  rowHeight = 40,
@@ -11072,47 +12120,52 @@ function TaskGanttChartInner(props, ref) {
11072
12120
  onTaskDateChangeModeChange: externalOnTaskDateChangeModeChange
11073
12121
  } = props;
11074
12122
  const dayWidth = !isTableMatrixMode ? props.dayWidth ?? 40 : 40;
11075
- const viewMode = !isTableMatrixMode ? props.viewMode ?? "day" : "day";
11076
- const customDays = !isTableMatrixMode ? props.customDays : void 0;
11077
- const isWeekend3 = !isTableMatrixMode ? props.isWeekend : void 0;
11078
- const businessDays = !isTableMatrixMode ? props.businessDays ?? true : true;
12123
+ const viewMode = !isTableMatrixMode && !isPlanFactMode ? props.viewMode ?? "day" : "day";
12124
+ const customDays = !isTableMatrixMode && !isPlanFactMode ? props.customDays : void 0;
12125
+ const isWeekend3 = !isTableMatrixMode && !isPlanFactMode ? props.isWeekend : void 0;
12126
+ const businessDays = !isTableMatrixMode && !isPlanFactMode ? props.businessDays ?? true : true;
11079
12127
  const matrixColumns = isTableMatrixMode ? props.matrixColumns : [];
11080
12128
  const matrixColumnGroups = isTableMatrixMode ? props.matrixColumnGroups : void 0;
11081
12129
  const onMatrixCellClick = isTableMatrixMode ? props.onMatrixCellClick : void 0;
11082
12130
  const matrixDateOverlay = isTableMatrixMode ? props.matrixDateOverlay : void 0;
11083
- const containerRef = (0, import_react17.useRef)(null);
11084
- const scrollContainerRef = (0, import_react17.useRef)(null);
11085
- const scrollContentRef = (0, import_react17.useRef)(null);
11086
- const clearSelectedTaskTimeoutRef = (0, import_react17.useRef)(null);
11087
- const hasAutoScrolledToTodayRef = (0, import_react17.useRef)(false);
11088
- const previewPositionStoreRef = (0, import_react17.useRef)(null);
11089
- const renderedTaskIdsRef = (0, import_react17.useRef)(/* @__PURE__ */ new Set());
12131
+ const onPlanFactCellCommit = isPlanFactMode ? props.onPlanFactCellCommit : void 0;
12132
+ const containerRef = (0, import_react18.useRef)(null);
12133
+ const scrollContainerRef = (0, import_react18.useRef)(null);
12134
+ const scrollContentRef = (0, import_react18.useRef)(null);
12135
+ const clearSelectedTaskTimeoutRef = (0, import_react18.useRef)(null);
12136
+ const hasAutoScrolledToTodayRef = (0, import_react18.useRef)(false);
12137
+ const previewPositionStoreRef = (0, import_react18.useRef)(null);
12138
+ const renderedTaskIdsRef = (0, import_react18.useRef)(/* @__PURE__ */ new Set());
11090
12139
  if (previewPositionStoreRef.current === null) {
11091
12140
  previewPositionStoreRef.current = createTaskPreviewPositionStore();
11092
12141
  }
11093
12142
  const previewPositionStore = previewPositionStoreRef.current;
11094
- const [selectedTaskId, setSelectedTaskId] = (0, import_react17.useState)(null);
11095
- const [taskListHasRightShadow, setTaskListHasRightShadow] = (0, import_react17.useState)(false);
11096
- const [internalTaskDateChangeMode, setInternalTaskDateChangeMode] = (0, import_react17.useState)("preserve-duration");
11097
- const [scrollViewport, setScrollViewport] = (0, import_react17.useState)({ scrollTop: 0, viewportHeight: 0 });
11098
- const [selectedChip, setSelectedChip] = (0, import_react17.useState)(null);
11099
- const [activeTimelineTooltip, setActiveTimelineTooltip] = (0, import_react17.useState)(null);
11100
- const [internalCollapsedParentIds, setInternalCollapsedParentIds] = (0, import_react17.useState)(/* @__PURE__ */ new Set());
12143
+ const [selectedTaskId, setSelectedTaskId] = (0, import_react18.useState)(null);
12144
+ const [taskListHasRightShadow, setTaskListHasRightShadow] = (0, import_react18.useState)(false);
12145
+ const [internalTaskDateChangeMode, setInternalTaskDateChangeMode] = (0, import_react18.useState)("preserve-duration");
12146
+ const [scrollViewport, setScrollViewport] = (0, import_react18.useState)({ scrollTop: 0, viewportHeight: 0 });
12147
+ const [planFactDateWindow, setPlanFactDateWindow] = (0, import_react18.useState)(null);
12148
+ const [selectedChip, setSelectedChip] = (0, import_react18.useState)(null);
12149
+ const [activeTimelineTooltip, setActiveTimelineTooltip] = (0, import_react18.useState)(null);
12150
+ const [internalCollapsedParentIds, setInternalCollapsedParentIds] = (0, import_react18.useState)(/* @__PURE__ */ new Set());
11101
12151
  const collapsedParentIds = externalCollapsedParentIds ?? internalCollapsedParentIds;
11102
- const [editingTaskId, setEditingTaskId] = (0, import_react17.useState)(null);
12152
+ const [editingTaskId, setEditingTaskId] = (0, import_react18.useState)(null);
11103
12153
  const taskDateChangeMode = externalTaskDateChangeMode ?? internalTaskDateChangeMode;
11104
12154
  const handleTaskDateChangeMode = externalOnTaskDateChangeModeChange ?? setInternalTaskDateChangeMode;
11105
- const resolvedRowContentLines = Math.max(1, Math.floor(rowContentLines));
11106
- const effectiveRowHeight = (0, import_react17.useMemo)(
12155
+ const resolvedRowContentLines = isPlanFactMode ? Math.max(2, Math.floor(rowContentLines)) : Math.max(1, Math.floor(rowContentLines));
12156
+ const effectiveRowHeight = (0, import_react18.useMemo)(
11107
12157
  () => Math.max(rowHeight, 10 + resolvedRowContentLines * 18),
11108
12158
  [resolvedRowContentLines, rowHeight]
11109
12159
  );
11110
- const normalizedTasks = (0, import_react17.useMemo)(() => normalizeHierarchyTasks(tasks), [tasks]);
11111
- const isCustomWeekend = (0, import_react17.useMemo)(
12160
+ const normalizedTasks = (0, import_react18.useMemo)(() => normalizeHierarchyTasks(tasks), [tasks]);
12161
+ const isCustomWeekend = (0, import_react18.useMemo)(
11112
12162
  () => createCustomDayPredicate({ customDays, isWeekend: isWeekend3 }),
11113
12163
  [customDays, isWeekend3]
11114
12164
  );
11115
- const dateRangeTasks = (0, import_react17.useMemo)(() => {
12165
+ const dateRangeTasks = (0, import_react18.useMemo)(() => {
12166
+ if (isPlanFactMode) {
12167
+ return getPlanFactRangeTasks(normalizedTasks);
12168
+ }
11116
12169
  if (!showBaseline) {
11117
12170
  return normalizedTasks;
11118
12171
  }
@@ -11121,22 +12174,25 @@ function TaskGanttChartInner(props, ref) {
11121
12174
  startDate: task.baselineStartDate && parseUTCDate(task.baselineStartDate).getTime() < parseUTCDate(task.startDate).getTime() ? task.baselineStartDate : task.startDate,
11122
12175
  endDate: task.baselineEndDate && parseUTCDate(task.baselineEndDate).getTime() > parseUTCDate(task.endDate).getTime() ? task.baselineEndDate : task.endDate
11123
12176
  }));
11124
- }, [normalizedTasks, showBaseline]);
11125
- const dateRange = (0, import_react17.useMemo)(() => getMultiMonthDays(dateRangeTasks), [dateRangeTasks]);
11126
- const [validationResult, setValidationResult] = (0, import_react17.useState)(null);
11127
- const [cascadeOverrides, setCascadeOverrides] = (0, import_react17.useState)(/* @__PURE__ */ new Map());
11128
- const gridWidth = (0, import_react17.useMemo)(
12177
+ }, [isPlanFactMode, normalizedTasks, showBaseline]);
12178
+ const dateRange = (0, import_react18.useMemo)(
12179
+ () => isPlanFactMode ? getFullMonthDays(dateRangeTasks) : getMultiMonthDays(dateRangeTasks),
12180
+ [dateRangeTasks, isPlanFactMode]
12181
+ );
12182
+ const [validationResult, setValidationResult] = (0, import_react18.useState)(null);
12183
+ const [cascadeOverrides, setCascadeOverrides] = (0, import_react18.useState)(/* @__PURE__ */ new Map());
12184
+ const gridWidth = (0, import_react18.useMemo)(
11129
12185
  () => Math.round(dateRange.length * dayWidth),
11130
12186
  [dateRange.length, dayWidth]
11131
12187
  );
11132
- const matrixWidth = (0, import_react17.useMemo)(
12188
+ const matrixWidth = (0, import_react18.useMemo)(
11133
12189
  () => matrixColumns.reduce((sum, column) => {
11134
12190
  if (typeof column.width !== "number") return void 0;
11135
12191
  return sum !== void 0 ? sum + column.width : void 0;
11136
12192
  }, 0),
11137
12193
  [matrixColumns]
11138
12194
  );
11139
- const visibleTasks = (0, import_react17.useMemo)(() => {
12195
+ const visibleTasks = (0, import_react18.useMemo)(() => {
11140
12196
  const parentMap = new Map(normalizedTasks.map((t) => [t.id, t.parentId]));
11141
12197
  function isAnyAncestorCollapsed(parentId) {
11142
12198
  let current = parentId;
@@ -11152,11 +12208,11 @@ function TaskGanttChartInner(props, ref) {
11152
12208
  }
11153
12209
  return tasks2;
11154
12210
  }, [normalizedTasks, collapsedParentIds, filterMode, taskFilter]);
11155
- const matchedTaskIds = (0, import_react17.useMemo)(() => {
12211
+ const matchedTaskIds = (0, import_react18.useMemo)(() => {
11156
12212
  if (!taskFilter) return /* @__PURE__ */ new Set();
11157
12213
  return new Set(visibleTasks.filter(taskFilter).map((task) => task.id));
11158
12214
  }, [visibleTasks, taskFilter]);
11159
- const taskListHighlightedTaskIds = (0, import_react17.useMemo)(() => {
12215
+ const taskListHighlightedTaskIds = (0, import_react18.useMemo)(() => {
11160
12216
  if (filterMode === "hide") {
11161
12217
  return /* @__PURE__ */ new Set();
11162
12218
  }
@@ -11167,33 +12223,34 @@ function TaskGanttChartInner(props, ref) {
11167
12223
  matchedTaskIds.forEach((taskId) => mergedHighlightedTaskIds.add(taskId));
11168
12224
  return mergedHighlightedTaskIds;
11169
12225
  }, [filterMode, highlightedTaskIds, matchedTaskIds]);
11170
- const totalGridHeight = (0, import_react17.useMemo)(
12226
+ const totalGridHeight = (0, import_react18.useMemo)(
11171
12227
  () => visibleTasks.length * effectiveRowHeight,
11172
12228
  [effectiveRowHeight, visibleTasks.length]
11173
12229
  );
11174
12230
  const timelineHeaderHeight = headerHeight + 1;
11175
- const tableBodyMinHeight = (0, import_react17.useMemo)(() => {
11176
- if (!isTableMatrixMode || containerHeight === void 0) {
12231
+ const tableBodyMinHeight = (0, import_react18.useMemo)(() => {
12232
+ if (!isTableMatrixMode && !isPlanFactMode || containerHeight === void 0) {
11177
12233
  return void 0;
11178
12234
  }
11179
12235
  if (typeof containerHeight === "number") {
11180
12236
  return Math.max(0, containerHeight - timelineHeaderHeight);
11181
12237
  }
11182
12238
  return `calc(${containerHeight} - ${timelineHeaderHeight}px)`;
11183
- }, [containerHeight, isTableMatrixMode, timelineHeaderHeight]);
11184
- const monthStart = (0, import_react17.useMemo)(() => {
12239
+ }, [containerHeight, isPlanFactMode, isTableMatrixMode, timelineHeaderHeight]);
12240
+ const monthStart = (0, import_react18.useMemo)(() => {
11185
12241
  if (dateRange.length === 0) {
11186
12242
  return new Date(Date.UTC((/* @__PURE__ */ new Date()).getUTCFullYear(), (/* @__PURE__ */ new Date()).getUTCMonth(), 1));
11187
12243
  }
11188
12244
  const firstDay = dateRange[0];
11189
12245
  return new Date(Date.UTC(firstDay.getUTCFullYear(), firstDay.getUTCMonth(), 1));
11190
12246
  }, [dateRange]);
11191
- const todayInRange = (0, import_react17.useMemo)(() => {
12247
+ const todayIndex = (0, import_react18.useMemo)(() => {
11192
12248
  const now = /* @__PURE__ */ new Date();
11193
12249
  const today = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
11194
- return dateRange.some((day) => day.getTime() === today.getTime());
12250
+ return dateRange.findIndex((day) => day.getTime() === today.getTime());
11195
12251
  }, [dateRange]);
11196
- const visibleTimelineMarkers = (0, import_react17.useMemo)(() => {
12252
+ const todayInRange = todayIndex !== -1;
12253
+ const visibleTimelineMarkers = (0, import_react18.useMemo)(() => {
11197
12254
  if (isTableMatrixMode || !timelineMarkers || timelineMarkers.length === 0 || dateRange.length === 0) {
11198
12255
  return [];
11199
12256
  }
@@ -11204,22 +12261,22 @@ function TaskGanttChartInner(props, ref) {
11204
12261
  return markerDate >= rangeStartMs && markerDate <= rangeEndMs;
11205
12262
  });
11206
12263
  }, [dateRange, isTableMatrixMode, timelineMarkers]);
11207
- (0, import_react17.useEffect)(() => {
12264
+ (0, import_react18.useEffect)(() => {
11208
12265
  if (isTableMatrixMode) return;
11209
12266
  if (hasAutoScrolledToTodayRef.current) return;
11210
12267
  const container = scrollContainerRef.current;
11211
12268
  if (!container || dateRange.length === 0) return;
11212
12269
  const now = /* @__PURE__ */ new Date();
11213
12270
  const today = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
11214
- const todayIndex = dateRange.findIndex((day) => day.getTime() === today.getTime());
11215
- if (todayIndex === -1) return;
11216
- const todayOffset = todayIndex * dayWidth;
12271
+ const todayIndex2 = dateRange.findIndex((day) => day.getTime() === today.getTime());
12272
+ if (todayIndex2 === -1) return;
12273
+ const todayOffset = todayIndex2 * dayWidth;
11217
12274
  const containerWidth = container.clientWidth;
11218
12275
  const scrollLeft = Math.round(todayOffset + dayWidth / 2 - containerWidth * 0.3);
11219
12276
  container.scrollLeft = Math.max(0, scrollLeft);
11220
12277
  hasAutoScrolledToTodayRef.current = true;
11221
12278
  }, [dateRange, dayWidth, isTableMatrixMode]);
11222
- (0, import_react17.useEffect)(() => {
12279
+ (0, import_react18.useEffect)(() => {
11223
12280
  const container = scrollContainerRef.current;
11224
12281
  if (!container) return;
11225
12282
  let frameId = null;
@@ -11227,13 +12284,33 @@ function TaskGanttChartInner(props, ref) {
11227
12284
  frameId = null;
11228
12285
  const nextHasRightShadow = container.scrollLeft > 0;
11229
12286
  const nextViewportHeight = Math.max(0, container.clientHeight - timelineHeaderHeight);
12287
+ const nextViewportWidth = Math.max(0, container.clientWidth - (showTaskList ? taskListWidth : 0));
11230
12288
  const nextScrollTop = container.scrollTop;
12289
+ const nextScrollLeft = container.scrollLeft;
12290
+ const nextChartScrollLeft = Math.max(0, nextScrollLeft);
11231
12291
  setTaskListHasRightShadow(
11232
12292
  (previous) => previous === nextHasRightShadow ? previous : nextHasRightShadow
11233
12293
  );
11234
12294
  setScrollViewport(
11235
12295
  (previous) => previous.scrollTop === nextScrollTop && previous.viewportHeight === nextViewportHeight ? previous : { scrollTop: nextScrollTop, viewportHeight: nextViewportHeight }
11236
12296
  );
12297
+ setPlanFactDateWindow((previous) => {
12298
+ if (!isPlanFactMode || dateRange.length === 0 || nextViewportWidth <= 0) {
12299
+ return previous === null ? previous : null;
12300
+ }
12301
+ const firstVisibleColumn = Math.max(0, Math.floor(nextChartScrollLeft / dayWidth));
12302
+ const visibleColumnCount = Math.max(1, Math.ceil(nextViewportWidth / dayWidth));
12303
+ const lastVisibleColumn = Math.min(dateRange.length - 1, firstVisibleColumn + visibleColumnCount - 1);
12304
+ const rangeStart = Math.max(
12305
+ 0,
12306
+ Math.floor(Math.max(0, firstVisibleColumn - PLAN_FACT_COLUMN_OVERSCAN) / PLAN_FACT_COLUMN_WINDOW_STEP) * PLAN_FACT_COLUMN_WINDOW_STEP
12307
+ );
12308
+ const rangeEnd = Math.min(
12309
+ dateRange.length - 1,
12310
+ Math.ceil((lastVisibleColumn + PLAN_FACT_COLUMN_OVERSCAN + 1) / PLAN_FACT_COLUMN_WINDOW_STEP) * PLAN_FACT_COLUMN_WINDOW_STEP - 1
12311
+ );
12312
+ return previous?.start === rangeStart && previous.end === rangeEnd ? previous : { start: rangeStart, end: rangeEnd };
12313
+ });
11237
12314
  };
11238
12315
  const scheduleUpdate = () => {
11239
12316
  if (frameId !== null) return;
@@ -11254,21 +12331,21 @@ function TaskGanttChartInner(props, ref) {
11254
12331
  container.removeEventListener("scroll", scheduleUpdate);
11255
12332
  window.removeEventListener("resize", scheduleUpdate);
11256
12333
  };
11257
- }, [timelineHeaderHeight]);
11258
- const scrollToToday = (0, import_react17.useCallback)(() => {
12334
+ }, [dateRange.length, dayWidth, isPlanFactMode, showTaskList, taskListWidth, timelineHeaderHeight]);
12335
+ const scrollToToday = (0, import_react18.useCallback)(() => {
11259
12336
  if (isTableMatrixMode) return;
11260
12337
  const container = scrollContainerRef.current;
11261
12338
  if (!container || dateRange.length === 0) return;
11262
12339
  const now = /* @__PURE__ */ new Date();
11263
12340
  const today = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
11264
- const todayIndex = dateRange.findIndex((day) => day.getTime() === today.getTime());
11265
- if (todayIndex === -1) return;
11266
- const todayOffset = todayIndex * dayWidth;
12341
+ const todayIndex2 = dateRange.findIndex((day) => day.getTime() === today.getTime());
12342
+ if (todayIndex2 === -1) return;
12343
+ const todayOffset = todayIndex2 * dayWidth;
11267
12344
  const containerWidth = container.clientWidth;
11268
12345
  const scrollLeft = Math.round(todayOffset + dayWidth / 2 - containerWidth * 0.3);
11269
12346
  container.scrollTo({ left: Math.max(0, scrollLeft), behavior: "smooth" });
11270
12347
  }, [dateRange, dayWidth, isTableMatrixMode]);
11271
- const scrollToTask = (0, import_react17.useCallback)((taskId) => {
12348
+ const scrollToTask = (0, import_react18.useCallback)((taskId) => {
11272
12349
  if (isTableMatrixMode) {
11273
12350
  const container2 = scrollContainerRef.current;
11274
12351
  if (!container2) return;
@@ -11295,7 +12372,7 @@ function TaskGanttChartInner(props, ref) {
11295
12372
  const scrollLeft = Math.round(taskOffset - dayWidth * 2);
11296
12373
  container.scrollTo({ left: Math.max(0, scrollLeft), behavior: "smooth" });
11297
12374
  }, [dateRange, dayWidth, effectiveRowHeight, isTableMatrixMode, tasks, visibleTasks]);
11298
- const scrollToRow = (0, import_react17.useCallback)((taskId, options = {}) => {
12375
+ const scrollToRow = (0, import_react18.useCallback)((taskId, options = {}) => {
11299
12376
  const container = scrollContainerRef.current;
11300
12377
  if (!container) return;
11301
12378
  const task = tasks.find((t) => t.id === taskId);
@@ -11324,21 +12401,21 @@ function TaskGanttChartInner(props, ref) {
11324
12401
  }
11325
12402
  container.scrollTo({ top: scrollTop, behavior });
11326
12403
  }, [effectiveRowHeight, tasks, visibleTasks]);
11327
- const [dragGuideLines, setDragGuideLines] = (0, import_react17.useState)(null);
11328
- const [draggedTaskOverride, setDraggedTaskOverride] = (0, import_react17.useState)(null);
11329
- const [previewTasksById, setPreviewTasksById] = (0, import_react17.useState)(/* @__PURE__ */ new Map());
11330
- (0, import_react17.useEffect)(() => {
12404
+ const [dragGuideLines, setDragGuideLines] = (0, import_react18.useState)(null);
12405
+ const [draggedTaskOverride, setDraggedTaskOverride] = (0, import_react18.useState)(null);
12406
+ const [previewTasksById, setPreviewTasksById] = (0, import_react18.useState)(/* @__PURE__ */ new Map());
12407
+ (0, import_react18.useEffect)(() => {
11331
12408
  const result = validateDependencies(tasks);
11332
12409
  setValidationResult(result);
11333
12410
  onValidateDependencies?.(result);
11334
12411
  }, [tasks, onValidateDependencies]);
11335
- (0, import_react17.useEffect)(() => () => {
12412
+ (0, import_react18.useEffect)(() => () => {
11336
12413
  if (clearSelectedTaskTimeoutRef.current !== null) {
11337
12414
  window.clearTimeout(clearSelectedTaskTimeoutRef.current);
11338
12415
  }
11339
12416
  previewPositionStore.clear();
11340
12417
  }, [previewPositionStore]);
11341
- const handleTaskChange = (0, import_react17.useCallback)((updatedTasks) => {
12418
+ const handleTaskChange = (0, import_react18.useCallback)((updatedTasks) => {
11342
12419
  const updatedTask = updatedTasks[0];
11343
12420
  if (!updatedTask) return;
11344
12421
  const originalTask = tasks.find((t) => t.id === updatedTask.id);
@@ -11384,7 +12461,7 @@ function TaskGanttChartInner(props, ref) {
11384
12461
  const cascadedTasks = disableConstraints ? [updatedTask] : universalCascade(updatedTask, newStart, newEnd, sourceTasks, businessDays, isCustomWeekend);
11385
12462
  onTasksChange?.(cascadedTasks);
11386
12463
  }, [tasks, onTasksChange, disableConstraints, editingTaskId, businessDays, isCustomWeekend]);
11387
- const handleDelete = (0, import_react17.useCallback)((taskId) => {
12464
+ const handleDelete = (0, import_react18.useCallback)((taskId) => {
11388
12465
  const toDelete = /* @__PURE__ */ new Set([taskId]);
11389
12466
  function collectDescendants(parentId) {
11390
12467
  const children = getChildren(parentId, tasks);
@@ -11409,10 +12486,10 @@ function TaskGanttChartInner(props, ref) {
11409
12486
  }
11410
12487
  toDelete.forEach((id) => onDelete?.(id));
11411
12488
  }, [tasks, onTasksChange, onDelete]);
11412
- const handleInsertAfter = (0, import_react17.useCallback)((taskId, newTask) => {
12489
+ const handleInsertAfter = (0, import_react18.useCallback)((taskId, newTask) => {
11413
12490
  onInsertAfter?.(taskId, newTask);
11414
12491
  }, [onInsertAfter]);
11415
- const handleReorder = (0, import_react17.useCallback)((reorderedTasks, movedTaskId, inferredParentId) => {
12492
+ const handleReorder = (0, import_react18.useCallback)((reorderedTasks, movedTaskId, inferredParentId) => {
11416
12493
  let updated = reorderedTasks;
11417
12494
  if (movedTaskId) {
11418
12495
  updated = updated.map((t) => {
@@ -11429,7 +12506,7 @@ function TaskGanttChartInner(props, ref) {
11429
12506
  }
11430
12507
  onTasksChange?.(normalized);
11431
12508
  }, [onTasksChange, onReorder]);
11432
- const dependencyOverrides = (0, import_react17.useMemo)(() => {
12509
+ const dependencyOverrides = (0, import_react18.useMemo)(() => {
11433
12510
  const map = new Map(cascadeOverrides);
11434
12511
  if (draggedTaskOverride) {
11435
12512
  map.set(draggedTaskOverride.taskId, {
@@ -11439,7 +12516,7 @@ function TaskGanttChartInner(props, ref) {
11439
12516
  }
11440
12517
  return map;
11441
12518
  }, [cascadeOverrides, draggedTaskOverride]);
11442
- const handleCascadeProgress = (0, import_react17.useCallback)((overrides, previewTasks = []) => {
12519
+ const handleCascadeProgress = (0, import_react18.useCallback)((overrides, previewTasks = []) => {
11443
12520
  previewPositionStore.setPositions(overrides);
11444
12521
  const renderedTaskIds = renderedTaskIdsRef.current;
11445
12522
  const renderedOverrides = /* @__PURE__ */ new Map();
@@ -11454,26 +12531,26 @@ function TaskGanttChartInner(props, ref) {
11454
12531
  return arePreviewTaskMapsEqual(current, next) ? current : next;
11455
12532
  });
11456
12533
  }, [previewPositionStore]);
11457
- const previewNormalizedTasks = (0, import_react17.useMemo)(() => {
12534
+ const previewNormalizedTasks = (0, import_react18.useMemo)(() => {
11458
12535
  if (previewTasksById.size === 0) return normalizedTasks;
11459
12536
  return normalizedTasks.map((task) => previewTasksById.get(task.id) ?? task);
11460
12537
  }, [normalizedTasks, previewTasksById]);
11461
- const previewVisibleTasks = (0, import_react17.useMemo)(() => {
12538
+ const previewVisibleTasks = (0, import_react18.useMemo)(() => {
11462
12539
  if (previewTasksById.size === 0) return visibleTasks;
11463
12540
  return visibleTasks.map((task) => previewTasksById.get(task.id) ?? task);
11464
12541
  }, [visibleTasks, previewTasksById]);
11465
- const visibleTaskIndexMap = (0, import_react17.useMemo)(
12542
+ const visibleTaskIndexMap = (0, import_react18.useMemo)(
11466
12543
  () => new Map(visibleTasks.map((task, index) => [task.id, index])),
11467
12544
  [visibleTasks]
11468
12545
  );
11469
- const forcedRenderedTaskIds = (0, import_react17.useMemo)(() => {
12546
+ const forcedRenderedTaskIds = (0, import_react18.useMemo)(() => {
11470
12547
  const ids = /* @__PURE__ */ new Set();
11471
12548
  if (draggedTaskOverride) {
11472
12549
  ids.add(draggedTaskOverride.taskId);
11473
12550
  }
11474
12551
  return ids;
11475
12552
  }, [draggedTaskOverride]);
11476
- const visibleTaskWindowIndices = (0, import_react17.useMemo)(() => {
12553
+ const visibleTaskWindowIndices = (0, import_react18.useMemo)(() => {
11477
12554
  const totalTasks = visibleTasks.length;
11478
12555
  if (totalTasks === 0) {
11479
12556
  return [];
@@ -11499,35 +12576,44 @@ function TaskGanttChartInner(props, ref) {
11499
12576
  }
11500
12577
  return Array.from(indices).sort((left, right) => left - right);
11501
12578
  }, [effectiveRowHeight, forcedRenderedTaskIds, scrollViewport, visibleTaskIndexMap, visibleTasks.length]);
11502
- const renderedChartTasks = (0, import_react17.useMemo)(
12579
+ const visiblePlanFactDateIndices = (0, import_react18.useMemo)(() => {
12580
+ if (!isPlanFactMode || dateRange.length === 0 || !planFactDateWindow) {
12581
+ return void 0;
12582
+ }
12583
+ return Array.from(
12584
+ { length: planFactDateWindow.end - planFactDateWindow.start + 1 },
12585
+ (_, index) => planFactDateWindow.start + index
12586
+ );
12587
+ }, [dateRange.length, isPlanFactMode, planFactDateWindow]);
12588
+ const renderedChartTasks = (0, import_react18.useMemo)(
11503
12589
  () => visibleTaskWindowIndices.map((index) => {
11504
12590
  const task = previewVisibleTasks[index];
11505
12591
  return task ? { index, task } : null;
11506
12592
  }).filter((entry) => entry !== null),
11507
12593
  [previewVisibleTasks, visibleTaskWindowIndices]
11508
12594
  );
11509
- const renderedDependencyTasks = (0, import_react17.useMemo)(
12595
+ const renderedDependencyTasks = (0, import_react18.useMemo)(
11510
12596
  () => renderedChartTasks.map(({ task }) => task),
11511
12597
  [renderedChartTasks]
11512
12598
  );
11513
- renderedTaskIdsRef.current = (0, import_react17.useMemo)(
12599
+ renderedTaskIdsRef.current = (0, import_react18.useMemo)(
11514
12600
  () => new Set(renderedChartTasks.map(({ task }) => task.id)),
11515
12601
  [renderedChartTasks]
11516
12602
  );
11517
- const handleCascade = (0, import_react17.useCallback)((cascadedTasks) => {
12603
+ const handleCascade = (0, import_react18.useCallback)((cascadedTasks) => {
11518
12604
  onTasksChange?.(cascadedTasks);
11519
12605
  }, [tasks, onTasksChange]);
11520
- const handleTaskSelect = (0, import_react17.useCallback)((taskId) => {
12606
+ const handleTaskSelect = (0, import_react18.useCallback)((taskId) => {
11521
12607
  setSelectedTaskId(taskId);
11522
12608
  }, []);
11523
- const hoveredRowElementsRef = (0, import_react17.useRef)([]);
11524
- const clearHoveredRows = (0, import_react17.useCallback)(() => {
12609
+ const hoveredRowElementsRef = (0, import_react18.useRef)([]);
12610
+ const clearHoveredRows = (0, import_react18.useCallback)(() => {
11525
12611
  for (const element of hoveredRowElementsRef.current) {
11526
- element.classList.remove("gantt-tl-row-hovered", "gantt-tr-row-hovered", "gantt-mx-row-hovered");
12612
+ element.classList.remove("gantt-tl-row-hovered", "gantt-tr-row-hovered", "gantt-mx-row-hovered", "gantt-pf-row-hovered");
11527
12613
  }
11528
12614
  hoveredRowElementsRef.current = [];
11529
12615
  }, []);
11530
- const applyHoveredRows = (0, import_react17.useCallback)((taskId) => {
12616
+ const applyHoveredRows = (0, import_react18.useCallback)((taskId) => {
11531
12617
  const root = scrollContentRef.current;
11532
12618
  if (!root) return;
11533
12619
  clearHoveredRows();
@@ -11544,10 +12630,13 @@ function TaskGanttChartInner(props, ref) {
11544
12630
  if (element.classList.contains("gantt-mx-row")) {
11545
12631
  element.classList.add("gantt-mx-row-hovered");
11546
12632
  }
12633
+ if (element.classList.contains("gantt-pf-row")) {
12634
+ element.classList.add("gantt-pf-row-hovered");
12635
+ }
11547
12636
  }
11548
12637
  hoveredRowElementsRef.current = nextHoveredRows;
11549
12638
  }, [clearHoveredRows]);
11550
- const handleSharedRowHover = (0, import_react17.useCallback)((event) => {
12639
+ const handleSharedRowHover = (0, import_react18.useCallback)((event) => {
11551
12640
  const target = event.target;
11552
12641
  const row = target.closest("[data-gantt-task-row-id]");
11553
12642
  const taskId = row?.dataset.ganttTaskRowId;
@@ -11557,7 +12646,7 @@ function TaskGanttChartInner(props, ref) {
11557
12646
  }
11558
12647
  applyHoveredRows(taskId);
11559
12648
  }, [applyHoveredRows]);
11560
- const handleToggleCollapse = externalOnToggleCollapse ?? (0, import_react17.useCallback)((parentId) => {
12649
+ const handleToggleCollapse = externalOnToggleCollapse ?? (0, import_react18.useCallback)((parentId) => {
11561
12650
  setInternalCollapsedParentIds((prev) => {
11562
12651
  const next = new Set(prev);
11563
12652
  if (next.has(parentId)) {
@@ -11568,20 +12657,20 @@ function TaskGanttChartInner(props, ref) {
11568
12657
  return next;
11569
12658
  });
11570
12659
  }, []);
11571
- const allParentIds = (0, import_react17.useMemo)(() => {
12660
+ const allParentIds = (0, import_react18.useMemo)(() => {
11572
12661
  return new Set(
11573
12662
  normalizedTasks.filter((t) => isTaskParent(t.id, normalizedTasks)).map((t) => t.id)
11574
12663
  );
11575
12664
  }, [normalizedTasks]);
11576
- const handleCollapseAll = (0, import_react17.useCallback)(() => {
12665
+ const handleCollapseAll = (0, import_react18.useCallback)(() => {
11577
12666
  if (externalCollapsedParentIds) return;
11578
12667
  setInternalCollapsedParentIds(allParentIds);
11579
12668
  }, [allParentIds, externalCollapsedParentIds]);
11580
- const handleExpandAll = (0, import_react17.useCallback)(() => {
12669
+ const handleExpandAll = (0, import_react18.useCallback)(() => {
11581
12670
  if (externalCollapsedParentIds) return;
11582
12671
  setInternalCollapsedParentIds(/* @__PURE__ */ new Set());
11583
12672
  }, [externalCollapsedParentIds]);
11584
- const exportToPdf = (0, import_react17.useCallback)(async (options) => {
12673
+ const exportToPdf = (0, import_react18.useCallback)(async (options) => {
11585
12674
  const sourceContainer = containerRef.current;
11586
12675
  const sourceContent = scrollContentRef.current;
11587
12676
  if (!sourceContainer || !sourceContent || typeof window === "undefined" || typeof document === "undefined") {
@@ -11617,7 +12706,7 @@ function TaskGanttChartInner(props, ref) {
11617
12706
  orientation: options?.orientation
11618
12707
  });
11619
12708
  }, [showTaskList, showChart]);
11620
- (0, import_react17.useImperativeHandle)(
12709
+ (0, import_react18.useImperativeHandle)(
11621
12710
  ref,
11622
12711
  () => ({
11623
12712
  scrollToToday,
@@ -11640,7 +12729,7 @@ function TaskGanttChartInner(props, ref) {
11640
12729
  }
11641
12730
  return depth;
11642
12731
  }
11643
- const handlePromoteTask = (0, import_react17.useCallback)((taskId) => {
12732
+ const handlePromoteTask = (0, import_react18.useCallback)((taskId) => {
11644
12733
  if (onPromoteTask) {
11645
12734
  onPromoteTask(taskId);
11646
12735
  return;
@@ -11672,7 +12761,7 @@ function TaskGanttChartInner(props, ref) {
11672
12761
  ]);
11673
12762
  onTasksChange?.(reorderedTasks);
11674
12763
  }, [tasks, onTasksChange, onPromoteTask]);
11675
- const handleDemoteTask = (0, import_react17.useCallback)((taskId, newParentId) => {
12764
+ const handleDemoteTask = (0, import_react18.useCallback)((taskId, newParentId) => {
11676
12765
  if (onDemoteTask) {
11677
12766
  onDemoteTask(taskId, newParentId);
11678
12767
  return;
@@ -11713,7 +12802,7 @@ function TaskGanttChartInner(props, ref) {
11713
12802
  };
11714
12803
  onTasksChange?.([updatedDemotedTask]);
11715
12804
  }, [tasks, onTasksChange, onDemoteTask]);
11716
- const handleUngroupTask = (0, import_react17.useCallback)((taskId) => {
12805
+ const handleUngroupTask = (0, import_react18.useCallback)((taskId) => {
11717
12806
  if (onUngroupTask) {
11718
12807
  onUngroupTask(taskId);
11719
12808
  return;
@@ -11738,8 +12827,8 @@ function TaskGanttChartInner(props, ref) {
11738
12827
  onTasksChange?.(changedTasks);
11739
12828
  }
11740
12829
  }, [tasks, onTasksChange, onUngroupTask]);
11741
- const panStateRef = (0, import_react17.useRef)(null);
11742
- const handlePanStart = (0, import_react17.useCallback)((e) => {
12830
+ const panStateRef = (0, import_react18.useRef)(null);
12831
+ const handlePanStart = (0, import_react18.useCallback)((e) => {
11743
12832
  if (e.button !== 0) return;
11744
12833
  const target = e.target;
11745
12834
  if (target.closest("[data-taskbar]")) return;
@@ -11752,7 +12841,10 @@ function TaskGanttChartInner(props, ref) {
11752
12841
  startX: e.clientX,
11753
12842
  startY: e.clientY,
11754
12843
  scrollX: container.scrollLeft,
11755
- scrollY: container.scrollTop
12844
+ scrollY: container.scrollTop,
12845
+ currentX: e.clientX,
12846
+ currentY: e.clientY,
12847
+ frameId: null
11756
12848
  };
11757
12849
  if (document.activeElement instanceof HTMLElement) {
11758
12850
  document.activeElement.blur();
@@ -11760,17 +12852,38 @@ function TaskGanttChartInner(props, ref) {
11760
12852
  container.style.cursor = "grabbing";
11761
12853
  e.preventDefault();
11762
12854
  }, []);
11763
- (0, import_react17.useEffect)(() => {
11764
- const handlePanMove = (e) => {
12855
+ (0, import_react18.useEffect)(() => {
12856
+ const flushPanMove = () => {
11765
12857
  const pan = panStateRef.current;
11766
12858
  if (!pan?.active) return;
12859
+ pan.frameId = null;
11767
12860
  const container = scrollContainerRef.current;
11768
12861
  if (!container) return;
11769
- container.scrollLeft = pan.scrollX - (e.clientX - pan.startX);
11770
- container.scrollTop = pan.scrollY - (e.clientY - pan.startY);
12862
+ const maxScrollLeft = Math.max(0, container.scrollWidth - container.clientWidth);
12863
+ const maxScrollTop = Math.max(0, container.scrollHeight - container.clientHeight);
12864
+ const nextScrollLeft = clampScrollValue(pan.scrollX - (pan.currentX - pan.startX), maxScrollLeft);
12865
+ const nextScrollTop = clampScrollValue(pan.scrollY - (pan.currentY - pan.startY), maxScrollTop);
12866
+ if (Math.abs(container.scrollLeft - nextScrollLeft) > 0.5) {
12867
+ container.scrollLeft = nextScrollLeft;
12868
+ }
12869
+ if (Math.abs(container.scrollTop - nextScrollTop) > 0.5) {
12870
+ container.scrollTop = nextScrollTop;
12871
+ }
12872
+ };
12873
+ const handlePanMove = (e) => {
12874
+ const pan = panStateRef.current;
12875
+ if (!pan?.active) return;
12876
+ pan.currentX = e.clientX;
12877
+ pan.currentY = e.clientY;
12878
+ if (pan.frameId !== null) return;
12879
+ pan.frameId = window.requestAnimationFrame(flushPanMove);
11771
12880
  };
11772
12881
  const handlePanEnd = () => {
11773
- if (!panStateRef.current?.active) return;
12882
+ const pan = panStateRef.current;
12883
+ if (!pan?.active) return;
12884
+ if (pan.frameId !== null) {
12885
+ window.cancelAnimationFrame(pan.frameId);
12886
+ }
11774
12887
  panStateRef.current = null;
11775
12888
  const container = scrollContainerRef.current;
11776
12889
  if (container) container.style.cursor = "";
@@ -11782,19 +12895,19 @@ function TaskGanttChartInner(props, ref) {
11782
12895
  window.removeEventListener("mouseup", handlePanEnd);
11783
12896
  };
11784
12897
  }, []);
11785
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
12898
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
11786
12899
  "div",
11787
12900
  {
11788
12901
  ref: containerRef,
11789
- className: isTableMatrixMode ? "gantt-container gantt-container-tableMatrix" : "gantt-container",
11790
- children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
12902
+ className: isTableMatrixMode ? "gantt-container gantt-container-tableMatrix" : isPlanFactMode ? "gantt-container gantt-container-planFact" : "gantt-container",
12903
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
11791
12904
  "div",
11792
12905
  {
11793
12906
  ref: scrollContainerRef,
11794
12907
  className: "gantt-scrollContainer",
11795
12908
  style: { height: containerHeight ?? "auto", cursor: "grab" },
11796
12909
  onMouseDown: handlePanStart,
11797
- children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
12910
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
11798
12911
  "div",
11799
12912
  {
11800
12913
  ref: scrollContentRef,
@@ -11802,7 +12915,7 @@ function TaskGanttChartInner(props, ref) {
11802
12915
  onMouseOver: handleSharedRowHover,
11803
12916
  onMouseLeave: clearHoveredRows,
11804
12917
  children: [
11805
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
12918
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
11806
12919
  TaskList,
11807
12920
  {
11808
12921
  tasks: normalizedTasks,
@@ -11855,17 +12968,17 @@ function TaskGanttChartInner(props, ref) {
11855
12968
  visibleRowIndices: visibleTaskWindowIndices
11856
12969
  }
11857
12970
  ),
11858
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
12971
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
11859
12972
  "div",
11860
12973
  {
11861
- className: isTableMatrixMode || showChart ? "gantt-chartSurface" : "gantt-chartSurface gantt-chart-hidden",
12974
+ className: isTableMatrixMode || isPlanFactMode || showChart ? "gantt-chartSurface" : "gantt-chartSurface gantt-chart-hidden",
11862
12975
  style: {
11863
- minWidth: isTableMatrixMode ? matrixWidth !== void 0 ? `${matrixWidth}px` : void 0 : `${gridWidth}px`,
11864
- width: isTableMatrixMode ? matrixWidth !== void 0 ? `${matrixWidth}px` : "max-content" : void 0,
11865
- flex: isTableMatrixMode ? "0 0 auto" : 1,
11866
- display: isTableMatrixMode || showChart ? void 0 : "none"
12976
+ minWidth: isTableMatrixMode ? matrixWidth !== void 0 ? `${matrixWidth}px` : void 0 : isPlanFactMode ? `${gridWidth}px` : `${gridWidth}px`,
12977
+ width: isTableMatrixMode ? matrixWidth !== void 0 ? `${matrixWidth}px` : "max-content" : isPlanFactMode ? `${gridWidth}px` : void 0,
12978
+ flex: isTableMatrixMode || isPlanFactMode ? "0 0 auto" : 1,
12979
+ display: isTableMatrixMode || isPlanFactMode || showChart ? void 0 : "none"
11867
12980
  },
11868
- children: isTableMatrixMode ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
12981
+ children: isTableMatrixMode ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
11869
12982
  TableMatrix,
11870
12983
  {
11871
12984
  tasks: visibleTasks,
@@ -11882,14 +12995,34 @@ function TaskGanttChartInner(props, ref) {
11882
12995
  highlightedTaskIds: taskListHighlightedTaskIds,
11883
12996
  filterMode
11884
12997
  }
11885
- ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
11886
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
12998
+ ) : isPlanFactMode ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
12999
+ PlanFactMatrix,
13000
+ {
13001
+ tasks: visibleTasks,
13002
+ allTasks: normalizedTasks,
13003
+ dateRange,
13004
+ dayWidth,
13005
+ rowHeight: effectiveRowHeight,
13006
+ headerHeight: timelineHeaderHeight,
13007
+ bodyMinHeight: tableBodyMinHeight,
13008
+ selectedTaskId,
13009
+ onTaskSelect: handleTaskSelect,
13010
+ onTasksChange: handleTaskChange,
13011
+ onCellCommit: onPlanFactCellCommit,
13012
+ highlightedTaskIds: taskListHighlightedTaskIds,
13013
+ filterMode,
13014
+ visibleRowIndices: visibleTaskWindowIndices,
13015
+ visibleDateIndices: visiblePlanFactDateIndices,
13016
+ todayDateIndex: todayInRange ? todayIndex : void 0
13017
+ }
13018
+ ) : /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
13019
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
11887
13020
  "div",
11888
13021
  {
11889
13022
  className: "gantt-stickyHeader",
11890
13023
  style: { width: `${gridWidth}px`, height: `${timelineHeaderHeight}px` },
11891
13024
  children: [
11892
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
13025
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
11893
13026
  TimeScaleHeader_default,
11894
13027
  {
11895
13028
  days: dateRange,
@@ -11902,7 +13035,7 @@ function TaskGanttChartInner(props, ref) {
11902
13035
  onTimelineHoverEnd: () => setActiveTimelineTooltip(null)
11903
13036
  }
11904
13037
  ),
11905
- activeTimelineTooltip && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "gantt-timelineTooltipLayer", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
13038
+ activeTimelineTooltip && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "gantt-timelineTooltipLayer", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
11906
13039
  "div",
11907
13040
  {
11908
13041
  className: "gantt-timelineTooltip",
@@ -11916,7 +13049,7 @@ function TaskGanttChartInner(props, ref) {
11916
13049
  ]
11917
13050
  }
11918
13051
  ),
11919
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
13052
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
11920
13053
  "div",
11921
13054
  {
11922
13055
  className: "gantt-taskArea",
@@ -11926,7 +13059,7 @@ function TaskGanttChartInner(props, ref) {
11926
13059
  height: `${totalGridHeight}px`
11927
13060
  },
11928
13061
  children: [
11929
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
13062
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
11930
13063
  GridBackground_default,
11931
13064
  {
11932
13065
  dateRange,
@@ -11936,7 +13069,7 @@ function TaskGanttChartInner(props, ref) {
11936
13069
  isCustomWeekend
11937
13070
  }
11938
13071
  ),
11939
- todayInRange && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
13072
+ todayInRange && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
11940
13073
  TodayIndicator_default,
11941
13074
  {
11942
13075
  monthStart,
@@ -11945,7 +13078,7 @@ function TaskGanttChartInner(props, ref) {
11945
13078
  onHoverEnd: () => setActiveTimelineTooltip(null)
11946
13079
  }
11947
13080
  ),
11948
- visibleTimelineMarkers.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
13081
+ visibleTimelineMarkers.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
11949
13082
  TimelineMarkers_default,
11950
13083
  {
11951
13084
  rangeStart: monthStart,
@@ -11956,7 +13089,7 @@ function TaskGanttChartInner(props, ref) {
11956
13089
  onHoverEnd: () => setActiveTimelineTooltip(null)
11957
13090
  }
11958
13091
  ),
11959
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
13092
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
11960
13093
  DependencyLines_default,
11961
13094
  {
11962
13095
  tasks: renderedDependencyTasks,
@@ -11974,7 +13107,7 @@ function TaskGanttChartInner(props, ref) {
11974
13107
  weekendPredicate: isCustomWeekend
11975
13108
  }
11976
13109
  ),
11977
- dragGuideLines && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
13110
+ dragGuideLines && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
11978
13111
  DragGuideLines_default,
11979
13112
  {
11980
13113
  isDragging: dragGuideLines.isDragging,
@@ -11984,7 +13117,7 @@ function TaskGanttChartInner(props, ref) {
11984
13117
  totalHeight: totalGridHeight
11985
13118
  }
11986
13119
  ),
11987
- renderedChartTasks.map(({ task, index }) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
13120
+ renderedChartTasks.map(({ task, index }) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
11988
13121
  "div",
11989
13122
  {
11990
13123
  style: {
@@ -11994,7 +13127,7 @@ function TaskGanttChartInner(props, ref) {
11994
13127
  right: 0,
11995
13128
  height: `${effectiveRowHeight}px`
11996
13129
  },
11997
- children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
13130
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
11998
13131
  TaskRow_default,
11999
13132
  {
12000
13133
  task,
@@ -12045,14 +13178,14 @@ function TaskGanttChartInner(props, ref) {
12045
13178
  }
12046
13179
  );
12047
13180
  }
12048
- var TaskGanttChart = (0, import_react17.forwardRef)(TaskGanttChartInner);
12049
- var GanttChart = (0, import_react17.forwardRef)(GanttChartInner);
13181
+ var TaskGanttChart = (0, import_react18.forwardRef)(TaskGanttChartInner);
13182
+ var GanttChart = (0, import_react18.forwardRef)(GanttChartInner);
12050
13183
  GanttChart.displayName = "GanttChart";
12051
13184
 
12052
13185
  // src/components/ui/Button.tsx
12053
- var import_react18 = __toESM(require("react"));
12054
- var import_jsx_runtime19 = require("react/jsx-runtime");
12055
- var Button = import_react18.default.forwardRef(
13186
+ var import_react19 = __toESM(require("react"));
13187
+ var import_jsx_runtime20 = require("react/jsx-runtime");
13188
+ var Button = import_react19.default.forwardRef(
12056
13189
  ({ className, variant = "default", size = "default", children, ...props }, ref) => {
12057
13190
  const classes = [
12058
13191
  "gantt-btn",
@@ -12060,7 +13193,7 @@ var Button = import_react18.default.forwardRef(
12060
13193
  size !== "default" ? `gantt-btn-${size}` : "",
12061
13194
  className || ""
12062
13195
  ].filter(Boolean).join(" ");
12063
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("button", { ref, className: classes, ...props, children });
13196
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("button", { ref, className: classes, ...props, children });
12064
13197
  }
12065
13198
  );
12066
13199
  Button.displayName = "Button";
@@ -12108,6 +13241,7 @@ var nameContains = (substring, caseSensitive = false) => (task) => {
12108
13241
  GanttChart,
12109
13242
  GridBackground,
12110
13243
  Input,
13244
+ PlanFactMatrix,
12111
13245
  Popover,
12112
13246
  PopoverContent,
12113
13247
  PopoverTrigger,