gantt-lib 0.136.0 → 0.137.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
@@ -33,6 +33,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
33
33
  // src/index.ts
34
34
  var index_exports = {};
35
35
  __export(index_exports, {
36
+ ACTIVITY_LANES_PADDING: () => ACTIVITY_LANES_PADDING,
37
+ ACTIVITY_LANE_BAR_HEIGHT: () => ACTIVITY_LANE_BAR_HEIGHT,
38
+ ACTIVITY_LANE_STEP: () => ACTIVITY_LANE_STEP,
36
39
  Button: () => Button,
37
40
  Calendar: () => Calendar,
38
41
  DAY_MS: () => DAY_MS,
@@ -54,6 +57,10 @@ __export(index_exports, {
54
57
  TaskRow: () => TaskRow_default,
55
58
  TimeScaleHeader: () => TimeScaleHeader_default,
56
59
  TodayIndicator: () => TodayIndicator_default,
60
+ activityChainMinStartDelta: () => activityChainMinStartDelta,
61
+ activityLanesExtraHeight: () => activityLanesExtraHeight,
62
+ activitySpanMs: () => activitySpanMs,
63
+ activityStartConstraint: () => activityStartConstraint,
57
64
  addBusinessDays: () => addBusinessDays2,
58
65
  alignToWorkingDay: () => alignToWorkingDay,
59
66
  and: () => and,
@@ -76,6 +83,7 @@ __export(index_exports, {
76
83
  cascadeByLinks: () => cascadeByLinks,
77
84
  clampDateRangeForIncomingFS: () => clampDateRangeForIncomingFS,
78
85
  clampTaskRangeForIncomingFS: () => clampTaskRangeForIncomingFS,
86
+ computeActivityLanes: () => computeActivityLanes,
79
87
  computeCriticalPath: () => computeCriticalPath,
80
88
  computeLagFromDates: () => computeLagFromDates,
81
89
  computeParentDates: () => computeParentDates,
@@ -130,10 +138,12 @@ __export(index_exports, {
130
138
  normalizeUTCDate: () => normalizeUTCDate,
131
139
  not: () => not,
132
140
  or: () => or,
141
+ packIntervals: () => packIntervals,
133
142
  parseDateOnly: () => parseDateOnly,
134
143
  parseUTCDate: () => parseUTCDate,
135
144
  pixelsToDate: () => pixelsToDate,
136
145
  progressInRange: () => progressInRange,
146
+ pushActivityChain: () => pushActivityChain,
137
147
  recalculateIncomingLags: () => recalculateIncomingLags,
138
148
  recalculateProjectSchedule: () => recalculateProjectSchedule,
139
149
  recalculateTaskFromDependencies: () => recalculateTaskFromDependencies,
@@ -143,6 +153,8 @@ __export(index_exports, {
143
153
  resolveDateRangeFromPixels: () => resolveDateRangeFromPixels,
144
154
  resolveTaskHorizontalGeometry: () => resolveTaskHorizontalGeometry,
145
155
  scaleTaskSubtreeDuration: () => scaleTaskSubtreeDuration,
156
+ shiftActivityChain: () => shiftActivityChain,
157
+ shiftActivityDate: () => shiftActivityDate,
146
158
  shiftBusinessDayOffset: () => shiftBusinessDayOffset,
147
159
  subtractBusinessDays: () => subtractBusinessDays2,
148
160
  universalCascade: () => universalCascade,
@@ -611,644 +623,965 @@ function subtractBusinessDays2(endDate, businessDays, weekendPredicate) {
611
623
  return result.toISOString().split("T")[0];
612
624
  }
613
625
 
614
- // src/core/scheduling/dependencies.ts
615
- function normalizePredecessorDates(predecessor, parseDateFn) {
616
- const predStart = parseDateFn(predecessor.startDate);
617
- const predEnd = predecessor.type === "milestone" ? predStart : parseDateFn(predecessor.endDate);
618
- return { predStart, predEnd };
619
- }
620
- function getDependencyLag(dep) {
621
- return Number.isFinite(dep.lag) ? dep.lag : 0;
622
- }
623
- function normalizeTaskDependencyLags(task) {
624
- return task;
625
- }
626
- function normalizeDependencyLag(linkType, lag, predecessorStart, predecessorEnd, businessDays = false, weekendPredicate, successorType) {
627
- if (linkType !== "FS") {
628
- return lag;
629
- }
630
- const predecessorDuration = getTaskDuration(
631
- predecessorStart,
632
- predecessorEnd,
633
- businessDays,
634
- weekendPredicate
626
+ // src/utils/geometry.ts
627
+ var getUTCDayDifference = (date1, date2) => {
628
+ const ms1 = Date.UTC(
629
+ date1.getUTCFullYear(),
630
+ date1.getUTCMonth(),
631
+ date1.getUTCDate()
635
632
  );
636
- const minimumLag = successorType === "milestone" ? -Math.max(0, predecessorDuration - 1) : -predecessorDuration;
637
- return Math.max(minimumLag, lag);
638
- }
639
- function computeLagFromDates(linkType, predStart, predEnd, succStart, succEnd, businessDays = false, weekendPredicate, successorType) {
640
- const pS = Date.UTC(predStart.getUTCFullYear(), predStart.getUTCMonth(), predStart.getUTCDate());
641
- const pE = Date.UTC(predEnd.getUTCFullYear(), predEnd.getUTCMonth(), predEnd.getUTCDate());
642
- const sS = Date.UTC(succStart.getUTCFullYear(), succStart.getUTCMonth(), succStart.getUTCDate());
643
- const sE = Date.UTC(succEnd.getUTCFullYear(), succEnd.getUTCMonth(), succEnd.getUTCDate());
644
- if (!businessDays || !weekendPredicate) {
645
- switch (linkType) {
646
- case "FS":
647
- return normalizeDependencyLag(
648
- linkType,
649
- Math.round((sS - pE) / DAY_MS) - (successorType === "milestone" ? 0 : 1),
650
- predStart,
651
- predEnd,
652
- businessDays,
653
- weekendPredicate,
654
- successorType
655
- );
656
- case "SS":
657
- return Math.round((sS - pS) / DAY_MS);
658
- case "FF":
659
- return Math.round((sE - pE) / DAY_MS);
660
- case "SF":
661
- return Math.round((sE - pS) / DAY_MS) + 1;
633
+ const ms2 = Date.UTC(
634
+ date2.getUTCFullYear(),
635
+ date2.getUTCMonth(),
636
+ date2.getUTCDate()
637
+ );
638
+ return Math.round((ms1 - ms2) / (1e3 * 60 * 60 * 24));
639
+ };
640
+ var calculateTaskBar = (taskStartDate, taskEndDate, monthStart, dayWidth) => {
641
+ const startOffset = getUTCDayDifference(taskStartDate, monthStart);
642
+ const duration = getUTCDayDifference(taskEndDate, taskStartDate);
643
+ const left = Math.round(startOffset * dayWidth);
644
+ const width = Math.round((duration + 1) * dayWidth);
645
+ return { left, width };
646
+ };
647
+ var calculateMilestoneGeometry = (taskDate, monthStart, dayWidth, size = 14) => {
648
+ const { left, width } = calculateTaskBar(taskDate, taskDate, monthStart, dayWidth);
649
+ const centerX = Math.round(left + width / 2);
650
+ const halfSize = Math.round(size / 2);
651
+ return {
652
+ centerX,
653
+ left: centerX - halfSize,
654
+ right: centerX + halfSize,
655
+ size
656
+ };
657
+ };
658
+ var calculateMilestoneConnectionBounds = (dayLeft, dayWidth, size = 14) => {
659
+ const halfDiagonal = Math.round(size / Math.SQRT2);
660
+ const centerX = Math.round(dayLeft + dayWidth / 2);
661
+ return {
662
+ left: centerX - halfDiagonal,
663
+ right: centerX + halfDiagonal
664
+ };
665
+ };
666
+ var resolveTaskHorizontalGeometry = (task, monthStart, dayWidth, override) => {
667
+ const startDate = new Date(task.startDate);
668
+ const endDate = new Date(task.endDate);
669
+ if (task.type === "milestone") {
670
+ const size = 14;
671
+ if (override) {
672
+ const bounds2 = calculateMilestoneConnectionBounds(override.left, dayWidth, size);
673
+ return {
674
+ ...bounds2,
675
+ centerX: Math.round(override.left + dayWidth / 2)
676
+ };
662
677
  }
678
+ const bar2 = calculateTaskBar(startDate, startDate, monthStart, dayWidth);
679
+ const bounds = calculateMilestoneConnectionBounds(bar2.left, dayWidth, size);
680
+ return {
681
+ ...bounds,
682
+ centerX: Math.round(bar2.left + dayWidth / 2)
683
+ };
663
684
  }
664
- const anchorDate = linkType === "SS" || linkType === "SF" ? predStart : predEnd;
665
- const targetDate = linkType === "FS" || linkType === "SS" ? succStart : succEnd;
666
- const businessOffset = getBusinessDayOffset(anchorDate, targetDate, weekendPredicate);
667
- switch (linkType) {
668
- case "FS":
669
- return normalizeDependencyLag(
670
- linkType,
671
- businessOffset - (successorType === "milestone" ? 0 : 1),
672
- predStart,
673
- predEnd,
674
- businessDays,
675
- weekendPredicate,
676
- successorType
677
- );
678
- case "SS":
679
- return businessOffset;
680
- case "FF":
681
- return businessOffset;
682
- case "SF":
683
- return businessOffset + 1;
685
+ if (override) {
686
+ return {
687
+ left: override.left,
688
+ right: override.left + override.width,
689
+ centerX: Math.round(override.left + override.width / 2)
690
+ };
684
691
  }
685
- }
686
- function calculateSuccessorDate(predecessorStart, predecessorEnd, linkType, lag = 0, businessDays = false, weekendPredicate, successorType) {
687
- const normalizedLag = normalizeDependencyLag(
688
- linkType,
689
- lag,
690
- predecessorStart,
691
- predecessorEnd,
692
- businessDays,
693
- weekendPredicate,
694
- successorType
692
+ const bar = calculateTaskBar(startDate, endDate, monthStart, dayWidth);
693
+ return {
694
+ left: bar.left,
695
+ right: bar.left + bar.width,
696
+ centerX: Math.round(bar.left + bar.width / 2)
697
+ };
698
+ };
699
+ var pixelsToDate = (pixels, monthStart, dayWidth) => {
700
+ const days = Math.round(pixels / dayWidth);
701
+ return new Date(Date.UTC(
702
+ monthStart.getUTCFullYear(),
703
+ monthStart.getUTCMonth(),
704
+ monthStart.getUTCDate() + days
705
+ ));
706
+ };
707
+ var calculateGridWidth = (daysInMonth, dayWidth) => {
708
+ return Math.round(daysInMonth * dayWidth);
709
+ };
710
+ var MIN_EDGE_ZONE_PX = 8;
711
+ var MIN_MOVE_ZONE_PX = 16;
712
+ var detectEdgeZone = (clientX, taskBarElement, edgeZoneWidth = 12) => {
713
+ const rect = taskBarElement.getBoundingClientRect();
714
+ const relativeX = Math.round(clientX - rect.left);
715
+ const width = Math.round(rect.width);
716
+ const effectiveZone = Math.max(
717
+ MIN_EDGE_ZONE_PX,
718
+ Math.min(edgeZoneWidth, (width - MIN_MOVE_ZONE_PX) / 2)
695
719
  );
696
- if (!businessDays || !weekendPredicate) {
697
- switch (linkType) {
698
- case "FS":
699
- return new Date(predecessorEnd.getTime() + (normalizedLag + (successorType === "milestone" ? 0 : 1)) * DAY_MS);
700
- case "SS":
701
- return new Date(predecessorStart.getTime() + normalizedLag * DAY_MS);
702
- case "FF":
703
- return new Date(predecessorEnd.getTime() + normalizedLag * DAY_MS);
704
- case "SF":
705
- return new Date(predecessorStart.getTime() + (normalizedLag - 1) * DAY_MS);
706
- }
707
- }
708
- const anchorDate = linkType === "FS" || linkType === "FF" ? predecessorEnd : predecessorStart;
709
- let offset;
710
- switch (linkType) {
711
- case "FS":
712
- offset = normalizedLag + (successorType === "milestone" ? 0 : 1);
713
- break;
714
- case "SS":
715
- offset = normalizedLag;
716
- break;
717
- case "FF":
718
- offset = normalizedLag;
719
- break;
720
- case "SF":
721
- offset = normalizedLag - 1;
722
- break;
720
+ if (relativeX >= 0 && relativeX <= effectiveZone) {
721
+ return "left";
723
722
  }
724
- return shiftBusinessDayOffset(anchorDate, offset, weekendPredicate);
725
- }
726
-
727
- // src/core/scheduling/criticalPath.ts
728
- function computeCriticalPath(tasks, options = {}) {
729
- const { businessDays = false, weekendPredicate } = options;
730
- const taskById = new Map(tasks.map((task) => [task.id, task]));
731
- const childIds = /* @__PURE__ */ new Set();
732
- for (const task of tasks) {
733
- if (task.parentId) childIds.add(task.parentId);
723
+ if (relativeX >= width - effectiveZone && relativeX <= width) {
724
+ return "right";
734
725
  }
735
- const leafTasks = tasks.filter((task) => !childIds.has(task.id));
736
- const leafIds = new Set(leafTasks.map((task) => task.id));
737
- const leafIdsByAncestor = /* @__PURE__ */ new Map();
738
- for (const leaf of leafTasks) {
739
- let ancestor = leaf.parentId;
740
- while (ancestor) {
741
- const set = leafIdsByAncestor.get(ancestor) ?? /* @__PURE__ */ new Set();
742
- set.add(leaf.id);
743
- leafIdsByAncestor.set(ancestor, set);
744
- ancestor = taskById.get(ancestor)?.parentId;
745
- }
726
+ return "move";
727
+ };
728
+ var getCursorForPosition = (position) => {
729
+ switch (position) {
730
+ case "left":
731
+ case "right":
732
+ return "ew-resize";
733
+ case "move":
734
+ return "grab";
735
+ default:
736
+ return "default";
746
737
  }
747
- const durationOf = (task) => getTaskDuration(task.startDate, task.endDate, businessDays, weekendPredicate);
748
- const projectStartDate = leafTasks.reduce((earliest, task) => {
749
- const taskStart = parseDateOnly(task.startDate);
750
- return taskStart.getTime() < earliest.getTime() ? taskStart : earliest;
751
- }, parseDateOnly(leafTasks[0]?.startDate ?? /* @__PURE__ */ new Date()));
752
- const offsetFromProjectStart = (date) => {
753
- const targetDate = parseDateOnly(date);
754
- if (businessDays && weekendPredicate) {
755
- return getBusinessDayOffset(projectStartDate, targetDate, weekendPredicate);
756
- }
757
- return Math.round((targetDate.getTime() - projectStartDate.getTime()) / DAY_MS);
758
- };
759
- const scheduledStart = /* @__PURE__ */ new Map();
760
- for (const leaf of leafTasks) {
761
- scheduledStart.set(leaf.id, offsetFromProjectStart(leaf.startDate));
738
+ };
739
+ var calculateGridLines = (dateRange, dayWidth) => {
740
+ const lines = [];
741
+ for (let i = 0; i < dateRange.length; i++) {
742
+ const date = dateRange[i];
743
+ const x = Math.round(i * dayWidth);
744
+ const isMonthStart = i === 0 ? false : date.getUTCDate() === 1;
745
+ const isWeekStart = date.getUTCDay() === 1;
746
+ lines.push({ x, isMonthStart, isWeekStart });
762
747
  }
763
- const succByPred = /* @__PURE__ */ new Map();
764
- const predBySucc = /* @__PURE__ */ new Map();
765
- const seenEdges = /* @__PURE__ */ new Set();
766
- const addEdge = (predecessorId, succId, lag, type) => {
767
- const edgeKey = `${predecessorId}|${succId}|${type}`;
768
- if (seenEdges.has(edgeKey)) return;
769
- seenEdges.add(edgeKey);
770
- const outEdges = succByPred.get(predecessorId) ?? [];
771
- outEdges.push({ succId, lag, type });
772
- succByPred.set(predecessorId, outEdges);
773
- const inEdges = predBySucc.get(succId) ?? [];
774
- inEdges.push({ predId: predecessorId, lag, type });
775
- predBySucc.set(succId, inEdges);
776
- };
777
- for (const succ of leafTasks) {
778
- for (const dep of succ.dependencies ?? []) {
779
- const lag = getDependencyLag(dep);
780
- if (leafIds.has(dep.taskId)) {
781
- addEdge(dep.taskId, succ.id, lag, dep.type);
782
- continue;
783
- }
784
- const descendants = leafIdsByAncestor.get(dep.taskId);
785
- if (!descendants || descendants.size === 0) continue;
786
- for (const leafId of descendants) {
787
- if (leafId === succ.id) continue;
788
- addEdge(leafId, succ.id, lag, dep.type);
789
- }
790
- }
748
+ if (dateRange.length > 0) {
749
+ lines.push({
750
+ x: Math.round(dateRange.length * dayWidth),
751
+ isMonthStart: false,
752
+ isWeekStart: false
753
+ });
791
754
  }
792
- const indegree = /* @__PURE__ */ new Map();
793
- for (const leaf of leafTasks) indegree.set(leaf.id, 0);
794
- for (const edges of succByPred.values()) {
795
- for (const { succId } of edges) {
796
- indegree.set(succId, (indegree.get(succId) ?? 0) + 1);
755
+ return lines;
756
+ };
757
+ var calculateWeekendBlocks = (dateRange, dayWidth, isCustomWeekend) => {
758
+ const blocks = [];
759
+ let inWeekend = false;
760
+ let weekendStartIndex = -1;
761
+ for (let i = 0; i < dateRange.length; i++) {
762
+ const date = dateRange[i];
763
+ const isWeekend3 = isCustomWeekend ? isCustomWeekend(date) : date.getUTCDay() === 0 || date.getUTCDay() === 6;
764
+ if (isWeekend3 && !inWeekend) {
765
+ inWeekend = true;
766
+ weekendStartIndex = i;
767
+ } else if (!isWeekend3 && inWeekend) {
768
+ inWeekend = false;
769
+ const left = Math.round(weekendStartIndex * dayWidth);
770
+ const width = Math.round((i - weekendStartIndex) * dayWidth);
771
+ blocks.push({ left, width });
797
772
  }
798
773
  }
799
- const order = [];
800
- const enqueued = /* @__PURE__ */ new Set();
801
- const queue = leafTasks.filter((leaf) => (indegree.get(leaf.id) ?? 0) === 0).map((leaf) => leaf.id);
802
- for (const id of queue) enqueued.add(id);
803
- while (queue.length > 0) {
804
- const current = queue.shift();
805
- order.push(current);
806
- for (const { succId } of succByPred.get(current) ?? []) {
807
- const next = (indegree.get(succId) ?? 0) - 1;
808
- indegree.set(succId, next);
809
- if (next === 0 && !enqueued.has(succId)) {
810
- enqueued.add(succId);
811
- queue.push(succId);
812
- }
813
- }
774
+ if (inWeekend && weekendStartIndex >= 0) {
775
+ const left = Math.round(weekendStartIndex * dayWidth);
776
+ const width = Math.round((dateRange.length - weekendStartIndex) * dayWidth);
777
+ blocks.push({ left, width });
814
778
  }
815
- const projectFinish = Math.max(
816
- 0,
817
- ...leafTasks.map((leaf) => (scheduledStart.get(leaf.id) ?? 0) + durationOf(leaf))
818
- );
819
- const LF = /* @__PURE__ */ new Map();
820
- const LS = /* @__PURE__ */ new Map();
821
- for (const leaf of leafTasks) LF.set(leaf.id, projectFinish);
822
- for (let i = order.length - 1; i >= 0; i--) {
823
- const id = order[i];
824
- const task = taskById.get(id);
825
- const durPred = durationOf(task);
826
- const outEdges = succByPred.get(id) ?? [];
827
- let lf = projectFinish;
828
- if (outEdges.length > 0) {
829
- const candidates = outEdges.map(({ succId, lag, type }) => {
830
- const succLs = LS.get(succId) ?? projectFinish;
831
- const durSucc = durationOf(taskById.get(succId));
832
- switch (type) {
833
- case "FS":
834
- return succLs - lag;
835
- case "SS":
836
- return succLs - lag + durPred;
837
- case "FF":
838
- return succLs + durSucc - lag;
839
- case "SF":
840
- return succLs + durSucc - lag + durPred;
841
- default:
842
- return projectFinish;
843
- }
844
- });
845
- lf = Math.min(projectFinish, ...candidates);
779
+ return blocks;
780
+ };
781
+ var calculateBezierPath = (from, to) => {
782
+ const verticalDistance = Math.abs(to.y - from.y);
783
+ const cpOffset = Math.max(verticalDistance * 0.5, 20);
784
+ if (from.y === to.y) {
785
+ const arcHeight = 20;
786
+ const midX = (from.x + to.x) / 2;
787
+ return `M ${from.x} ${from.y} Q ${midX} ${from.y - arcHeight} ${to.x} ${to.y}`;
788
+ }
789
+ const cp1x = from.x;
790
+ const cp1y = from.y + (to.y > from.y ? cpOffset : -cpOffset);
791
+ const cp2x = to.x;
792
+ const cp2y = to.y - (to.y > from.y ? cpOffset : -cpOffset);
793
+ return `M ${Math.round(from.x)} ${Math.round(from.y)} C ${Math.round(cp1x)} ${Math.round(cp1y)}, ${Math.round(cp2x)} ${Math.round(cp2y)}, ${Math.round(to.x)} ${Math.round(to.y)}`;
794
+ };
795
+ var calculateDependencyPath = (from, to, arrivesFromRight) => {
796
+ const fx = Math.round(from.x);
797
+ const fy = Math.round(from.y);
798
+ const tx = Math.round(to.x);
799
+ const ty = Math.round(to.y);
800
+ if (fy === ty) {
801
+ return `M ${fx} ${fy} H ${tx}`;
802
+ }
803
+ if (fx === tx) {
804
+ return `M ${fx} ${fy} V ${ty}`;
805
+ }
806
+ const C = 2;
807
+ const goingDown = ty > fy;
808
+ const dirY = goingDown ? 1 : -1;
809
+ if (arrivesFromRight) {
810
+ const goingRight = tx >= fx;
811
+ const dirX = goingRight ? 1 : -1;
812
+ if (Math.abs(ty - fy) >= C && Math.abs(tx - fx) >= C) {
813
+ return [
814
+ `M ${fx} ${fy}`,
815
+ `H ${tx - dirX * C}`,
816
+ `L ${tx} ${fy + dirY * C}`,
817
+ `V ${ty}`
818
+ ].join(" ");
846
819
  }
847
- LF.set(id, lf);
848
- LS.set(id, lf - durPred);
820
+ return `M ${fx} ${fy} H ${tx} V ${ty}`;
821
+ } else {
822
+ const goingRight = tx >= fx;
823
+ const dirX = goingRight ? 1 : -1;
824
+ if (Math.abs(ty - fy) >= C && Math.abs(tx - fx) >= C) {
825
+ return [
826
+ `M ${fx} ${fy}`,
827
+ `H ${tx - dirX * C}`,
828
+ `L ${tx} ${fy + dirY * C}`,
829
+ `V ${ty}`
830
+ ].join(" ");
831
+ }
832
+ return `M ${fx} ${fy} H ${tx} V ${ty}`;
849
833
  }
850
- const critical = /* @__PURE__ */ new Set();
851
- const ordered = new Set(order);
852
- for (const leaf of leafTasks) {
853
- if (!ordered.has(leaf.id)) continue;
854
- const hasGraphEdge = (succByPred.get(leaf.id)?.length ?? 0) > 0 || (predBySucc.get(leaf.id)?.length ?? 0) > 0;
855
- if (!hasGraphEdge && (leaf.dependencies?.length ?? 0) > 0) continue;
856
- const es = scheduledStart.get(leaf.id) ?? 0;
857
- const ls = LS.get(leaf.id) ?? 0;
858
- if (ls - es <= 0) critical.add(leaf.id);
834
+ };
835
+ var calculateWeekGridLines = (dateRange, dayWidth) => {
836
+ const blocks = getWeekBlocks(dateRange);
837
+ const lines = [];
838
+ let currentDayIndex = 0;
839
+ for (let i = 0; i < blocks.length; i++) {
840
+ const block = blocks[i];
841
+ currentDayIndex += block.days;
842
+ const blockEndMonth = dateRange[currentDayIndex - 1];
843
+ const nextBlockMonth = currentDayIndex < dateRange.length ? dateRange[currentDayIndex] : null;
844
+ const isMonthBoundary = nextBlockMonth && blockEndMonth.getUTCMonth() !== nextBlockMonth.getUTCMonth();
845
+ lines.push({
846
+ x: Math.round(currentDayIndex * dayWidth),
847
+ isMonthStart: isMonthBoundary || false
848
+ });
859
849
  }
860
- return critical;
861
- }
862
- function extendCriticalIdsToParents(criticalLeafIds, tasks) {
863
- const result = new Set(criticalLeafIds);
864
- const parentByChild = /* @__PURE__ */ new Map();
865
- for (const task of tasks) {
866
- if (task.parentId) parentByChild.set(task.id, task.parentId);
850
+ if (lines.length > 0 && lines[lines.length - 1].x === Math.round(dateRange.length * dayWidth)) {
851
+ lines.pop();
867
852
  }
868
- const stack = Array.from(criticalLeafIds);
869
- while (stack.length > 0) {
870
- const id = stack.pop();
871
- const parentId = parentByChild.get(id);
872
- if (parentId && !result.has(parentId)) {
873
- result.add(parentId);
874
- stack.push(parentId);
853
+ return lines;
854
+ };
855
+ var calculateMonthGridLines = (dateRange, dayWidth) => {
856
+ const lines = [];
857
+ for (let i = 1; i < dateRange.length; i++) {
858
+ const date = dateRange[i];
859
+ if (date.getUTCDate() === 1) {
860
+ const x = Math.round(i * dayWidth);
861
+ const isYearBoundary = date.getUTCMonth() === 0;
862
+ lines.push({ x, isMonthStart: isYearBoundary });
875
863
  }
876
864
  }
877
- return result;
878
- }
865
+ return lines;
866
+ };
867
+ var calculateOrthogonalPath = (from, to) => {
868
+ const fx = Math.round(from.x);
869
+ const fy = Math.round(from.y);
870
+ const tx = Math.round(to.x);
871
+ const ty = Math.round(to.y);
872
+ if (fy === ty) {
873
+ return `M ${fx} ${fy} H ${tx}`;
874
+ }
875
+ const C = 2;
876
+ const goingDown = ty > fy;
877
+ const goingRight = tx >= fx;
878
+ const dirY = goingDown ? 1 : -1;
879
+ const dirX = goingRight ? 1 : -1;
880
+ if (Math.abs(ty - fy) >= C && Math.abs(tx - fx) >= C) {
881
+ return [
882
+ `M ${fx} ${fy}`,
883
+ `H ${tx - dirX * C}`,
884
+ `L ${tx} ${fy + dirY * C}`,
885
+ `V ${ty}`
886
+ ].join(" ");
887
+ }
888
+ return `M ${fx} ${fy} H ${tx} V ${ty}`;
889
+ };
879
890
 
880
- // src/core/scheduling/commands.ts
881
- function buildTaskRangeFromStart(startDate, duration, businessDays = false, weekendPredicate, snapDirection = 1) {
882
- const normalizedStart = businessDays && weekendPredicate ? alignToWorkingDay(startDate, snapDirection, weekendPredicate) : normalizeUTCDate(startDate);
883
- if (businessDays && weekendPredicate) {
891
+ // src/utils/activities.ts
892
+ var ACTIVITY_LANE_STEP = 28;
893
+ var ACTIVITY_LANE_BAR_HEIGHT = 24;
894
+ var ACTIVITY_LANES_PADDING = 2;
895
+ var DAY_MS2 = 24 * 60 * 60 * 1e3;
896
+ function packIntervals(intervals) {
897
+ const sorted = [...intervals].sort((a, b) => a.start - b.start);
898
+ const laneEnds = [];
899
+ return sorted.map((item) => {
900
+ let lane = 0;
901
+ while (lane < laneEnds.length && item.start < laneEnds[lane]) {
902
+ lane += 1;
903
+ }
904
+ laneEnds[lane] = item.end;
905
+ return { ...item, lane };
906
+ });
907
+ }
908
+ function computeActivityLanes(activities, monthStart, dayWidth) {
909
+ if (activities.length === 0) {
910
+ return { segments: [], laneCount: 0 };
911
+ }
912
+ const packed = packIntervals(activities.map((activity) => {
913
+ const bar = calculateTaskBar(parseUTCDate(activity.startDate), parseUTCDate(activity.endDate), monthStart, dayWidth);
884
914
  return {
885
- start: normalizedStart,
886
- end: parseDateOnly(addBusinessDays(normalizedStart, duration, weekendPredicate))
915
+ id: activity.id,
916
+ name: activity.name,
917
+ color: activity.color,
918
+ lag: activity.lag,
919
+ start: bar.left,
920
+ end: bar.left + bar.width,
921
+ left: bar.left,
922
+ width: bar.width
887
923
  };
888
- }
889
- const DAY_MS6 = 24 * 60 * 60 * 1e3;
924
+ }));
890
925
  return {
891
- start: normalizedStart,
892
- end: new Date(normalizedStart.getTime() + (Math.max(1, duration) - 1) * DAY_MS6)
926
+ segments: packed.map(({ id, name, color, lag, lane, left, width }) => ({ id, name, color, lag, lane, left, width })),
927
+ laneCount: Math.max(...packed.map((item) => item.lane + 1))
893
928
  };
894
929
  }
895
- function buildTaskRangeFromEnd(endDate, duration, businessDays = false, weekendPredicate, snapDirection = -1) {
896
- const normalizedEnd = businessDays && weekendPredicate ? alignToWorkingDay(endDate, snapDirection, weekendPredicate) : normalizeUTCDate(endDate);
897
- if (businessDays && weekendPredicate) {
898
- return {
899
- start: parseDateOnly(subtractBusinessDays(normalizedEnd, duration, weekendPredicate)),
900
- end: normalizedEnd
901
- };
902
- }
903
- const DAY_MS6 = 24 * 60 * 60 * 1e3;
930
+ function activityLanesExtraHeight(laneCount) {
931
+ if (laneCount <= 1) return 0;
932
+ return laneCount * ACTIVITY_LANE_STEP + ACTIVITY_LANES_PADDING * 2;
933
+ }
934
+ function activitySpanMs(activity) {
904
935
  return {
905
- start: new Date(normalizedEnd.getTime() - (Math.max(1, duration) - 1) * DAY_MS6),
906
- end: normalizedEnd
936
+ start: parseUTCDate(activity.startDate).getTime(),
937
+ end: parseUTCDate(activity.endDate).getTime() + DAY_MS2
907
938
  };
908
939
  }
909
- function moveTaskRange(originalStart, originalEnd, proposedStart, businessDays = false, weekendPredicate, snapDirection = 1) {
910
- return buildTaskRangeFromStart(
911
- proposedStart,
912
- getTaskDuration(originalStart, originalEnd, businessDays, weekendPredicate),
913
- businessDays,
914
- weekendPredicate,
915
- snapDirection
916
- );
940
+ function shiftActivityDate(value, days) {
941
+ const shifted = new Date(parseUTCDate(value).getTime() + days * DAY_MS2);
942
+ return typeof value === "string" ? shifted.toISOString().slice(0, 10) : shifted;
917
943
  }
918
- function clampTaskRangeForIncomingFS(task, proposedStart, proposedEnd, allTasks, businessDays = false, weekendPredicate, taskById) {
919
- if (!task.dependencies?.length) {
920
- return { start: proposedStart, end: proposedEnd };
921
- }
922
- let minAllowedStart = null;
923
- for (const dep of task.dependencies) {
924
- if (dep.type !== "FS") {
925
- continue;
944
+ var CHAIN_KEY_SEPARATOR = "\0";
945
+ var chainKey = (taskId, activityId) => `${taskId}${CHAIN_KEY_SEPARATOR}${activityId}`;
946
+ var toDayNumber = (value) => Math.round(parseUTCDate(value).getTime() / DAY_MS2);
947
+ function buildChainGraph(orderedTasks) {
948
+ const successors = /* @__PURE__ */ new Map();
949
+ const predecessors = /* @__PURE__ */ new Map();
950
+ const info = /* @__PURE__ */ new Map();
951
+ const link = (from, to, lag) => {
952
+ const list = successors.get(from);
953
+ if (list) {
954
+ if (!list.some((edge) => edge.key === to)) list.push({ key: to, lag });
955
+ } else {
956
+ successors.set(from, [{ key: to, lag }]);
926
957
  }
927
- const predecessor = taskById?.get(dep.taskId) ?? allTasks.find((candidate) => candidate.id === dep.taskId);
928
- if (!predecessor) {
929
- continue;
958
+ const back = predecessors.get(to);
959
+ if (back) {
960
+ if (!back.some((edge) => edge.key === from)) back.push({ key: from, lag });
961
+ } else {
962
+ predecessors.set(to, [{ key: from, lag }]);
930
963
  }
931
- const { predStart: predecessorStart, predEnd: predecessorEnd } = normalizePredecessorDates(predecessor, parseDateOnly);
932
- const predecessorDuration = getTaskDuration(
933
- predecessorStart,
934
- predecessorEnd,
935
- businessDays,
936
- weekendPredicate
937
- );
938
- const candidateMinStart = calculateSuccessorDate(
939
- predecessorStart,
940
- predecessorEnd,
941
- "FS",
942
- -predecessorDuration,
943
- businessDays,
944
- weekendPredicate,
945
- task.type
946
- );
947
- if (!minAllowedStart || candidateMinStart.getTime() > minAllowedStart.getTime()) {
948
- minAllowedStart = candidateMinStart;
964
+ };
965
+ const flagged = orderedTasks.filter((task) => Boolean(task.activityChain) && Array.isArray(task.activities) && task.activities.length > 0);
966
+ flagged.forEach((task, floorIdx) => {
967
+ task.activities.forEach((activity, workIdx) => {
968
+ info.set(chainKey(task.id, activity.id), {
969
+ taskId: task.id,
970
+ activityId: activity.id,
971
+ startDay: toDayNumber(activity.startDate),
972
+ durationDays: Math.max(1, toDayNumber(activity.endDate) - toDayNumber(activity.startDate) + 1),
973
+ floorIdx,
974
+ workIdx
975
+ });
976
+ });
977
+ for (let i = 0; i < task.activities.length - 1; i += 1) {
978
+ link(chainKey(task.id, task.activities[i].id), chainKey(task.id, task.activities[i + 1].id), task.activities[i + 1].lag ?? 0);
979
+ }
980
+ });
981
+ for (let f = 0; f < flagged.length - 1; f += 1) {
982
+ const current = flagged[f];
983
+ const next = flagged[f + 1];
984
+ for (const activity of current.activities) {
985
+ const twin = next.activities.find((candidate) => candidate.id === activity.id || candidate.name === activity.name);
986
+ if (twin) link(chainKey(current.id, activity.id), chainKey(next.id, twin.id), 0);
949
987
  }
950
988
  }
951
- if (!minAllowedStart || proposedStart.getTime() >= minAllowedStart.getTime()) {
952
- return { start: proposedStart, end: proposedEnd };
989
+ return { successors, predecessors, info };
990
+ }
991
+ function collectDownstream(graph, startKey) {
992
+ const visited = /* @__PURE__ */ new Set();
993
+ const queue = [...(graph.successors.get(startKey) ?? []).map((edge) => edge.key)];
994
+ while (queue.length > 0) {
995
+ const key = queue.shift();
996
+ if (visited.has(key)) continue;
997
+ visited.add(key);
998
+ queue.push(...(graph.successors.get(key) ?? []).map((edge) => edge.key));
953
999
  }
954
- return buildTaskRangeFromStart(
955
- minAllowedStart,
956
- getTaskDuration(proposedStart, proposedEnd, businessDays, weekendPredicate),
957
- businessDays,
958
- weekendPredicate
959
- );
1000
+ return visited;
960
1001
  }
961
- function recalculateIncomingLags(task, newStartDate, newEndDate, allTasks, businessDays = false, weekendPredicate, taskById) {
962
- if (!task.dependencies) return [];
963
- return task.dependencies.map((dep) => {
964
- const predecessor = taskById?.get(dep.taskId) ?? allTasks.find((candidate) => candidate.id === dep.taskId);
965
- if (!predecessor) {
966
- return { ...dep, lag: getDependencyLag(dep) };
1002
+ function shiftsFromDeltas(orderedTasks, deltasByTask) {
1003
+ const shifts = [];
1004
+ for (const task of orderedTasks) {
1005
+ const deltas = deltasByTask.get(task.id);
1006
+ if (!deltas || deltas.size === 0) continue;
1007
+ shifts.push({
1008
+ taskId: task.id,
1009
+ shiftedIds: new Set(deltas.keys()),
1010
+ deltas,
1011
+ activities: (task.activities ?? []).map((activity) => deltas.has(activity.id) ? {
1012
+ ...activity,
1013
+ startDate: shiftActivityDate(activity.startDate, deltas.get(activity.id)),
1014
+ endDate: shiftActivityDate(activity.endDate, deltas.get(activity.id))
1015
+ } : activity)
1016
+ });
1017
+ }
1018
+ return shifts;
1019
+ }
1020
+ function shiftActivityChain(orderedTasks, draggedTaskId, draggedActivityId, deltaDays) {
1021
+ if (deltaDays === 0) return [];
1022
+ const graph = buildChainGraph(orderedTasks);
1023
+ const shifted = collectDownstream(graph, chainKey(draggedTaskId, draggedActivityId));
1024
+ if (shifted.size === 0) return [];
1025
+ const deltasByTask = /* @__PURE__ */ new Map();
1026
+ for (const key of shifted) {
1027
+ const node = graph.info.get(key);
1028
+ const set = deltasByTask.get(node.taskId);
1029
+ if (set) set.set(node.activityId, deltaDays);
1030
+ else deltasByTask.set(node.taskId, /* @__PURE__ */ new Map([[node.activityId, deltaDays]]));
1031
+ }
1032
+ return shiftsFromDeltas(orderedTasks, deltasByTask);
1033
+ }
1034
+ function activityStartConstraint(orderedTasks, taskId, activityId) {
1035
+ const graph = buildChainGraph(orderedTasks);
1036
+ const key = chainKey(taskId, activityId);
1037
+ const node = graph.info.get(key);
1038
+ if (!node) return null;
1039
+ const candidates = [];
1040
+ let minStart = Number.NEGATIVE_INFINITY;
1041
+ for (const pred of graph.predecessors.get(key) ?? []) {
1042
+ const predNode = graph.info.get(pred.key);
1043
+ if (!predNode) continue;
1044
+ const min = predNode.startDay + predNode.durationDays + pred.lag;
1045
+ candidates.push({ taskId: predNode.taskId, activityId: predNode.activityId, min, lag: pred.lag });
1046
+ if (min > minStart) minStart = min;
1047
+ }
1048
+ if (!Number.isFinite(minStart)) return null;
1049
+ const blockers = candidates.filter((candidate) => candidate.min === minStart);
1050
+ return {
1051
+ minStartDay: minStart,
1052
+ minStartDelta: minStart - node.startDay,
1053
+ blockers: blockers.map(({ taskId: blockerTaskId, activityId: blockerActivityId }) => ({
1054
+ taskId: blockerTaskId,
1055
+ activityId: blockerActivityId
1056
+ })),
1057
+ lag: Math.max(0, ...blockers.map((blocker) => blocker.lag))
1058
+ };
1059
+ }
1060
+ function activityChainMinStartDelta(orderedTasks, taskId, activityId) {
1061
+ return activityStartConstraint(orderedTasks, taskId, activityId)?.minStartDelta ?? null;
1062
+ }
1063
+ function pushActivityChain(orderedTasks, draggedTaskId, draggedActivityId, requestedDeltaDays) {
1064
+ const graph = buildChainGraph(orderedTasks);
1065
+ const draggedKey = chainKey(draggedTaskId, draggedActivityId);
1066
+ if (!graph.info.has(draggedKey)) {
1067
+ return { shifts: [], draggedDeltaDays: requestedDeltaDays };
1068
+ }
1069
+ const downstream = collectDownstream(graph, draggedKey);
1070
+ if (downstream.size === 0) {
1071
+ return { shifts: [], draggedDeltaDays: requestedDeltaDays };
1072
+ }
1073
+ const newStart = /* @__PURE__ */ new Map();
1074
+ const dragged = graph.info.get(draggedKey);
1075
+ newStart.set(draggedKey, dragged.startDay + requestedDeltaDays);
1076
+ const endOfDay = (key) => {
1077
+ const node = graph.info.get(key);
1078
+ return (newStart.get(key) ?? node.startDay) + node.durationDays;
1079
+ };
1080
+ const order = [...downstream].map((key) => ({ key, node: graph.info.get(key) })).sort((a, b) => a.node.workIdx - b.node.workIdx || a.node.floorIdx - b.node.floorIdx);
1081
+ for (const { key, node } of order) {
1082
+ let earliest = Number.NEGATIVE_INFINITY;
1083
+ let oldEarliest = Number.NEGATIVE_INFINITY;
1084
+ for (const pred of graph.predecessors.get(key) ?? []) {
1085
+ const predNode = graph.info.get(pred.key);
1086
+ earliest = Math.max(earliest, endOfDay(pred.key) + pred.lag);
1087
+ oldEarliest = Math.max(oldEarliest, predNode.startDay + predNode.durationDays + pred.lag);
1088
+ }
1089
+ let nextStart;
1090
+ if (earliest > node.startDay) {
1091
+ nextStart = earliest;
1092
+ } else if (earliest < oldEarliest) {
1093
+ nextStart = earliest;
1094
+ } else {
1095
+ nextStart = node.startDay;
967
1096
  }
968
- const { predStart: predecessorStart, predEnd: predecessorEnd } = normalizePredecessorDates(
969
- predecessor,
970
- (d) => new Date(d instanceof Date ? d.getTime() : `${String(d).split("T")[0]}T00:00:00.000Z`)
971
- );
972
- const nextLag = computeLagFromDates(
973
- dep.type,
974
- predecessorStart,
975
- predecessorEnd,
976
- newStartDate,
977
- newEndDate,
978
- businessDays,
979
- weekendPredicate,
980
- task.type
981
- );
982
- return { ...dep, lag: nextLag };
983
- });
1097
+ newStart.set(key, nextStart);
1098
+ }
1099
+ const deltasByTask = /* @__PURE__ */ new Map();
1100
+ for (const key of downstream) {
1101
+ const node = graph.info.get(key);
1102
+ const delta = (newStart.get(key) ?? node.startDay) - node.startDay;
1103
+ if (delta === 0) continue;
1104
+ const set = deltasByTask.get(node.taskId);
1105
+ if (set) set.set(node.activityId, delta);
1106
+ else deltasByTask.set(node.taskId, /* @__PURE__ */ new Map([[node.activityId, delta]]));
1107
+ }
1108
+ return { shifts: shiftsFromDeltas(orderedTasks, deltasByTask), draggedDeltaDays: requestedDeltaDays };
984
1109
  }
985
1110
 
986
- // src/core/scheduling/cascade.ts
987
- function parseCascadeDateInput(date) {
988
- if (date instanceof Date) {
989
- return normalizeUTCDate(date);
1111
+ // src/core/scheduling/dependencies.ts
1112
+ function normalizePredecessorDates(predecessor, parseDateFn) {
1113
+ const predStart = parseDateFn(predecessor.startDate);
1114
+ const predEnd = predecessor.type === "milestone" ? predStart : parseDateFn(predecessor.endDate);
1115
+ return { predStart, predEnd };
1116
+ }
1117
+ function getDependencyLag(dep) {
1118
+ return Number.isFinite(dep.lag) ? dep.lag : 0;
1119
+ }
1120
+ function normalizeTaskDependencyLags(task) {
1121
+ return task;
1122
+ }
1123
+ function normalizeDependencyLag(linkType, lag, predecessorStart, predecessorEnd, businessDays = false, weekendPredicate, successorType) {
1124
+ if (linkType !== "FS") {
1125
+ return lag;
990
1126
  }
991
- return normalizeUTCDate(/* @__PURE__ */ new Date(`${date.split("T")[0]}T00:00:00.000Z`));
1127
+ const predecessorDuration = getTaskDuration(
1128
+ predecessorStart,
1129
+ predecessorEnd,
1130
+ businessDays,
1131
+ weekendPredicate
1132
+ );
1133
+ const minimumLag = successorType === "milestone" ? -Math.max(0, predecessorDuration - 1) : -predecessorDuration;
1134
+ return Math.max(minimumLag, lag);
992
1135
  }
993
- function createCascadeContext(allTasks) {
994
- const childrenByParentId = /* @__PURE__ */ new Map();
995
- const dependentsByPredecessorId = /* @__PURE__ */ new Map();
996
- const taskById = /* @__PURE__ */ new Map();
997
- for (const task of allTasks) {
998
- taskById.set(task.id, task);
999
- if (task.parentId) {
1000
- const children = childrenByParentId.get(task.parentId) ?? [];
1001
- children.push(task);
1002
- childrenByParentId.set(task.parentId, children);
1136
+ function computeLagFromDates(linkType, predStart, predEnd, succStart, succEnd, businessDays = false, weekendPredicate, successorType) {
1137
+ const pS = Date.UTC(predStart.getUTCFullYear(), predStart.getUTCMonth(), predStart.getUTCDate());
1138
+ const pE = Date.UTC(predEnd.getUTCFullYear(), predEnd.getUTCMonth(), predEnd.getUTCDate());
1139
+ const sS = Date.UTC(succStart.getUTCFullYear(), succStart.getUTCMonth(), succStart.getUTCDate());
1140
+ const sE = Date.UTC(succEnd.getUTCFullYear(), succEnd.getUTCMonth(), succEnd.getUTCDate());
1141
+ if (!businessDays || !weekendPredicate) {
1142
+ switch (linkType) {
1143
+ case "FS":
1144
+ return normalizeDependencyLag(
1145
+ linkType,
1146
+ Math.round((sS - pE) / DAY_MS) - (successorType === "milestone" ? 0 : 1),
1147
+ predStart,
1148
+ predEnd,
1149
+ businessDays,
1150
+ weekendPredicate,
1151
+ successorType
1152
+ );
1153
+ case "SS":
1154
+ return Math.round((sS - pS) / DAY_MS);
1155
+ case "FF":
1156
+ return Math.round((sE - pE) / DAY_MS);
1157
+ case "SF":
1158
+ return Math.round((sE - pS) / DAY_MS) + 1;
1003
1159
  }
1004
- if (!task.dependencies) continue;
1005
- task.dependencies.forEach((dependency, dependencyIndex) => {
1006
- const dependents = dependentsByPredecessorId.get(dependency.taskId) ?? [];
1007
- dependents.push({ task, dependencyIndex });
1008
- dependentsByPredecessorId.set(dependency.taskId, dependents);
1009
- });
1010
1160
  }
1011
- return { childrenByParentId, dependentsByPredecessorId, taskById };
1161
+ const anchorDate = linkType === "SS" || linkType === "SF" ? predStart : predEnd;
1162
+ const targetDate = linkType === "FS" || linkType === "SS" ? succStart : succEnd;
1163
+ const businessOffset = getBusinessDayOffset(anchorDate, targetDate, weekendPredicate);
1164
+ switch (linkType) {
1165
+ case "FS":
1166
+ return normalizeDependencyLag(
1167
+ linkType,
1168
+ businessOffset - (successorType === "milestone" ? 0 : 1),
1169
+ predStart,
1170
+ predEnd,
1171
+ businessDays,
1172
+ weekendPredicate,
1173
+ successorType
1174
+ );
1175
+ case "SS":
1176
+ return businessOffset;
1177
+ case "FF":
1178
+ return businessOffset;
1179
+ case "SF":
1180
+ return businessOffset + 1;
1181
+ }
1012
1182
  }
1013
- function getSuccessorChain(draggedTaskId, allTasks, linkTypes = ["FS"]) {
1014
- const { dependentsByPredecessorId, taskById } = createCascadeContext(allTasks);
1015
- const visited = /* @__PURE__ */ new Set();
1016
- const queue = [draggedTaskId];
1017
- const chain = [];
1018
- visited.add(draggedTaskId);
1019
- while (queue.length > 0) {
1020
- const current = queue.shift();
1021
- const successors = dependentsByPredecessorId.get(current) ?? [];
1022
- for (const { task } of successors) {
1023
- if (!linkTypes.includes(task.dependencies?.find((dependency) => dependency.taskId === current)?.type ?? "FS")) {
1024
- continue;
1025
- }
1026
- const sid = task.id;
1027
- if (!visited.has(sid)) {
1028
- visited.add(sid);
1029
- const successorTask = taskById.get(sid);
1030
- if (successorTask) {
1031
- chain.push(successorTask);
1032
- queue.push(sid);
1033
- }
1034
- }
1183
+ function calculateSuccessorDate(predecessorStart, predecessorEnd, linkType, lag = 0, businessDays = false, weekendPredicate, successorType) {
1184
+ const normalizedLag = normalizeDependencyLag(
1185
+ linkType,
1186
+ lag,
1187
+ predecessorStart,
1188
+ predecessorEnd,
1189
+ businessDays,
1190
+ weekendPredicate,
1191
+ successorType
1192
+ );
1193
+ if (!businessDays || !weekendPredicate) {
1194
+ switch (linkType) {
1195
+ case "FS":
1196
+ return new Date(predecessorEnd.getTime() + (normalizedLag + (successorType === "milestone" ? 0 : 1)) * DAY_MS);
1197
+ case "SS":
1198
+ return new Date(predecessorStart.getTime() + normalizedLag * DAY_MS);
1199
+ case "FF":
1200
+ return new Date(predecessorEnd.getTime() + normalizedLag * DAY_MS);
1201
+ case "SF":
1202
+ return new Date(predecessorStart.getTime() + (normalizedLag - 1) * DAY_MS);
1035
1203
  }
1036
1204
  }
1037
- return chain;
1205
+ const anchorDate = linkType === "FS" || linkType === "FF" ? predecessorEnd : predecessorStart;
1206
+ let offset;
1207
+ switch (linkType) {
1208
+ case "FS":
1209
+ offset = normalizedLag + (successorType === "milestone" ? 0 : 1);
1210
+ break;
1211
+ case "SS":
1212
+ offset = normalizedLag;
1213
+ break;
1214
+ case "FF":
1215
+ offset = normalizedLag;
1216
+ break;
1217
+ case "SF":
1218
+ offset = normalizedLag - 1;
1219
+ break;
1220
+ }
1221
+ return shiftBusinessDayOffset(anchorDate, offset, weekendPredicate);
1038
1222
  }
1039
- function cascadeByLinks(movedTaskId, newStart, newEnd, allTasks, skipChildCascade = false) {
1040
- const { childrenByParentId, dependentsByPredecessorId, taskById } = createCascadeContext(allTasks);
1041
- const updatedDates = /* @__PURE__ */ new Map();
1042
- updatedDates.set(movedTaskId, { start: newStart, end: newEnd });
1043
- const result = [];
1044
- const queue = [movedTaskId];
1045
- const visited = /* @__PURE__ */ new Set([movedTaskId]);
1046
- while (queue.length > 0) {
1047
- const currentId = queue.shift();
1048
- const { start: predStart, end: predEnd } = updatedDates.get(currentId);
1049
- if (!skipChildCascade) {
1050
- const children = childrenByParentId.get(currentId) ?? [];
1051
- for (const child of children) {
1052
- if (visited.has(child.id) || child.locked) continue;
1053
- const origStart = new Date(child.startDate);
1054
- const origEnd = new Date(child.endDate);
1055
- const durationMs = origEnd.getTime() - origStart.getTime();
1056
- const parentOrig = taskById.get(currentId);
1057
- const parentOrigStart = new Date(parentOrig.startDate);
1058
- const parentOrigEnd = new Date(parentOrig.endDate);
1059
- const parentStartDelta = predStart.getTime() - parentOrigStart.getTime();
1060
- const parentEndDelta = predEnd.getTime() - parentOrigEnd.getTime();
1061
- const newChildStart = new Date(origStart.getTime() + parentStartDelta);
1062
- const newChildEnd = new Date(origEnd.getTime() + parentEndDelta);
1063
- visited.add(child.id);
1064
- updatedDates.set(child.id, { start: newChildStart, end: newChildEnd });
1065
- result.push(normalizeTaskDependencyLags({
1066
- ...child,
1067
- startDate: newChildStart.toISOString().split("T")[0],
1068
- endDate: newChildEnd.toISOString().split("T")[0]
1069
- }));
1070
- queue.push(child.id);
1071
- }
1223
+
1224
+ // src/core/scheduling/criticalPath.ts
1225
+ function computeCriticalPath(tasks, options = {}) {
1226
+ const { businessDays = false, weekendPredicate } = options;
1227
+ const taskById = new Map(tasks.map((task) => [task.id, task]));
1228
+ const childIds = /* @__PURE__ */ new Set();
1229
+ for (const task of tasks) {
1230
+ if (task.parentId) childIds.add(task.parentId);
1231
+ }
1232
+ const leafTasks = tasks.filter((task) => !childIds.has(task.id));
1233
+ const leafIds = new Set(leafTasks.map((task) => task.id));
1234
+ const leafIdsByAncestor = /* @__PURE__ */ new Map();
1235
+ for (const leaf of leafTasks) {
1236
+ let ancestor = leaf.parentId;
1237
+ while (ancestor) {
1238
+ const set = leafIdsByAncestor.get(ancestor) ?? /* @__PURE__ */ new Set();
1239
+ set.add(leaf.id);
1240
+ leafIdsByAncestor.set(ancestor, set);
1241
+ ancestor = taskById.get(ancestor)?.parentId;
1072
1242
  }
1073
- const dependents = dependentsByPredecessorId.get(currentId) ?? [];
1074
- for (const { task, dependencyIndex } of dependents) {
1075
- if (visited.has(task.id) || !task.dependencies || task.locked) continue;
1076
- const dep = task.dependencies[dependencyIndex];
1077
- if (!dep) continue;
1078
- const orig = taskById.get(task.id);
1079
- const origStart = new Date(orig.startDate);
1080
- const origEnd = new Date(orig.endDate);
1081
- const duration = getTaskDuration(origStart, origEnd);
1082
- const currentTask = taskById.get(currentId);
1083
- const { predStart: normalizedPredStart, predEnd: normalizedPredEnd } = normalizePredecessorDates(
1084
- {
1085
- startDate: predStart,
1086
- endDate: predEnd,
1087
- type: currentTask.type
1088
- },
1089
- parseCascadeDateInput
1090
- );
1091
- const constraintDate = calculateSuccessorDate(
1092
- normalizedPredStart,
1093
- normalizedPredEnd,
1094
- dep.type,
1095
- getDependencyLag(dep),
1096
- false,
1097
- void 0,
1098
- task.type
1099
- );
1100
- let newSuccStart;
1101
- let newSuccEnd;
1102
- if (dep.type === "FS" || dep.type === "SS") {
1103
- ({ start: newSuccStart, end: newSuccEnd } = buildTaskRangeFromStart(constraintDate, duration));
1104
- } else {
1105
- ({ start: newSuccStart, end: newSuccEnd } = buildTaskRangeFromEnd(constraintDate, duration));
1243
+ }
1244
+ const durationOf = (task) => getTaskDuration(task.startDate, task.endDate, businessDays, weekendPredicate);
1245
+ const projectStartDate = leafTasks.reduce((earliest, task) => {
1246
+ const taskStart = parseDateOnly(task.startDate);
1247
+ return taskStart.getTime() < earliest.getTime() ? taskStart : earliest;
1248
+ }, parseDateOnly(leafTasks[0]?.startDate ?? /* @__PURE__ */ new Date()));
1249
+ const offsetFromProjectStart = (date) => {
1250
+ const targetDate = parseDateOnly(date);
1251
+ if (businessDays && weekendPredicate) {
1252
+ return getBusinessDayOffset(projectStartDate, targetDate, weekendPredicate);
1253
+ }
1254
+ return Math.round((targetDate.getTime() - projectStartDate.getTime()) / DAY_MS);
1255
+ };
1256
+ const scheduledStart = /* @__PURE__ */ new Map();
1257
+ for (const leaf of leafTasks) {
1258
+ scheduledStart.set(leaf.id, offsetFromProjectStart(leaf.startDate));
1259
+ }
1260
+ const succByPred = /* @__PURE__ */ new Map();
1261
+ const predBySucc = /* @__PURE__ */ new Map();
1262
+ const seenEdges = /* @__PURE__ */ new Set();
1263
+ const addEdge = (predecessorId, succId, lag, type) => {
1264
+ const edgeKey = `${predecessorId}|${succId}|${type}`;
1265
+ if (seenEdges.has(edgeKey)) return;
1266
+ seenEdges.add(edgeKey);
1267
+ const outEdges = succByPred.get(predecessorId) ?? [];
1268
+ outEdges.push({ succId, lag, type });
1269
+ succByPred.set(predecessorId, outEdges);
1270
+ const inEdges = predBySucc.get(succId) ?? [];
1271
+ inEdges.push({ predId: predecessorId, lag, type });
1272
+ predBySucc.set(succId, inEdges);
1273
+ };
1274
+ for (const succ of leafTasks) {
1275
+ for (const dep of succ.dependencies ?? []) {
1276
+ const lag = getDependencyLag(dep);
1277
+ if (leafIds.has(dep.taskId)) {
1278
+ addEdge(dep.taskId, succ.id, lag, dep.type);
1279
+ continue;
1280
+ }
1281
+ const descendants = leafIdsByAncestor.get(dep.taskId);
1282
+ if (!descendants || descendants.size === 0) continue;
1283
+ for (const leafId of descendants) {
1284
+ if (leafId === succ.id) continue;
1285
+ addEdge(leafId, succ.id, lag, dep.type);
1106
1286
  }
1107
- visited.add(task.id);
1108
- updatedDates.set(task.id, { start: newSuccStart, end: newSuccEnd });
1109
- result.push(normalizeTaskDependencyLags({
1110
- ...task,
1111
- startDate: newSuccStart.toISOString().split("T")[0],
1112
- endDate: newSuccEnd.toISOString().split("T")[0]
1113
- }));
1114
- queue.push(task.id);
1115
1287
  }
1116
1288
  }
1117
- return result;
1118
- }
1119
- function getTransitiveCascadeChain(changedTaskId, allTasks, firstLevelLinkTypes) {
1120
- const { childrenByParentId, dependentsByPredecessorId } = createCascadeContext(allTasks);
1121
- const directChildren = childrenByParentId.get(changedTaskId) ?? [];
1122
- const directSuccessors = getSuccessorChain(changedTaskId, allTasks, firstLevelLinkTypes);
1123
- const initialChain = [...directChildren, ...directSuccessors].filter(
1124
- (task, index, arr) => arr.findIndex((candidate) => candidate.id === task.id) === index
1125
- );
1126
- const chain = [...initialChain];
1127
- const visited = /* @__PURE__ */ new Set([changedTaskId, ...initialChain.map((t) => t.id)]);
1128
- const queue = [...initialChain];
1289
+ const indegree = /* @__PURE__ */ new Map();
1290
+ for (const leaf of leafTasks) indegree.set(leaf.id, 0);
1291
+ for (const edges of succByPred.values()) {
1292
+ for (const { succId } of edges) {
1293
+ indegree.set(succId, (indegree.get(succId) ?? 0) + 1);
1294
+ }
1295
+ }
1296
+ const order = [];
1297
+ const enqueued = /* @__PURE__ */ new Set();
1298
+ const queue = leafTasks.filter((leaf) => (indegree.get(leaf.id) ?? 0) === 0).map((leaf) => leaf.id);
1299
+ for (const id of queue) enqueued.add(id);
1129
1300
  while (queue.length > 0) {
1130
1301
  const current = queue.shift();
1131
- const children = childrenByParentId.get(current.id) ?? [];
1132
- for (const child of children) {
1133
- if (!visited.has(child.id)) {
1134
- visited.add(child.id);
1135
- chain.push(child);
1136
- queue.push(child);
1302
+ order.push(current);
1303
+ for (const { succId } of succByPred.get(current) ?? []) {
1304
+ const next = (indegree.get(succId) ?? 0) - 1;
1305
+ indegree.set(succId, next);
1306
+ if (next === 0 && !enqueued.has(succId)) {
1307
+ enqueued.add(succId);
1308
+ queue.push(succId);
1137
1309
  }
1138
1310
  }
1139
- const successors = dependentsByPredecessorId.get(current.id) ?? [];
1140
- for (const { task: successor } of successors) {
1141
- if (!visited.has(successor.id)) {
1142
- visited.add(successor.id);
1143
- chain.push(successor);
1144
- queue.push(successor);
1145
- }
1311
+ }
1312
+ const projectFinish = Math.max(
1313
+ 0,
1314
+ ...leafTasks.map((leaf) => (scheduledStart.get(leaf.id) ?? 0) + durationOf(leaf))
1315
+ );
1316
+ const LF = /* @__PURE__ */ new Map();
1317
+ const LS = /* @__PURE__ */ new Map();
1318
+ for (const leaf of leafTasks) LF.set(leaf.id, projectFinish);
1319
+ for (let i = order.length - 1; i >= 0; i--) {
1320
+ const id = order[i];
1321
+ const task = taskById.get(id);
1322
+ const durPred = durationOf(task);
1323
+ const outEdges = succByPred.get(id) ?? [];
1324
+ let lf = projectFinish;
1325
+ if (outEdges.length > 0) {
1326
+ const candidates = outEdges.map(({ succId, lag, type }) => {
1327
+ const succLs = LS.get(succId) ?? projectFinish;
1328
+ const durSucc = durationOf(taskById.get(succId));
1329
+ switch (type) {
1330
+ case "FS":
1331
+ return succLs - lag;
1332
+ case "SS":
1333
+ return succLs - lag + durPred;
1334
+ case "FF":
1335
+ return succLs + durSucc - lag;
1336
+ case "SF":
1337
+ return succLs + durSucc - lag + durPred;
1338
+ default:
1339
+ return projectFinish;
1340
+ }
1341
+ });
1342
+ lf = Math.min(projectFinish, ...candidates);
1146
1343
  }
1344
+ LF.set(id, lf);
1345
+ LS.set(id, lf - durPred);
1147
1346
  }
1148
- return chain;
1347
+ const critical = /* @__PURE__ */ new Set();
1348
+ const ordered = new Set(order);
1349
+ for (const leaf of leafTasks) {
1350
+ if (!ordered.has(leaf.id)) continue;
1351
+ const hasGraphEdge = (succByPred.get(leaf.id)?.length ?? 0) > 0 || (predBySucc.get(leaf.id)?.length ?? 0) > 0;
1352
+ if (!hasGraphEdge && (leaf.dependencies?.length ?? 0) > 0) continue;
1353
+ const es = scheduledStart.get(leaf.id) ?? 0;
1354
+ const ls = LS.get(leaf.id) ?? 0;
1355
+ if (ls - es <= 0) critical.add(leaf.id);
1356
+ }
1357
+ return critical;
1149
1358
  }
1150
- function universalCascade(movedTask, newStart, newEnd, allTasks, businessDays = false, weekendPredicate, context = createCascadeContext(allTasks)) {
1151
- const { childrenByParentId, dependentsByPredecessorId, taskById } = context;
1152
- const normalizedMovedTask = normalizeTaskDependencyLags({
1153
- ...movedTask,
1154
- startDate: newStart.toISOString().split("T")[0],
1155
- endDate: newEnd.toISOString().split("T")[0]
1156
- });
1157
- if (!movedTask.parentId && !childrenByParentId.has(movedTask.id) && !dependentsByPredecessorId.has(movedTask.id)) {
1158
- return [normalizedMovedTask];
1359
+ function extendCriticalIdsToParents(criticalLeafIds, tasks) {
1360
+ const result = new Set(criticalLeafIds);
1361
+ const parentByChild = /* @__PURE__ */ new Map();
1362
+ for (const task of tasks) {
1363
+ if (task.parentId) parentByChild.set(task.id, task.parentId);
1159
1364
  }
1160
- const updatedDates = /* @__PURE__ */ new Map();
1161
- updatedDates.set(movedTask.id, { start: newStart, end: newEnd });
1162
- const resultMap = /* @__PURE__ */ new Map();
1163
- resultMap.set(movedTask.id, normalizedMovedTask);
1164
- const queue = [[movedTask.id, "direct"]];
1165
- const childShifted = /* @__PURE__ */ new Set();
1166
- let iterations = 0;
1167
- const MAX_ITERATIONS = allTasks.length * 3;
1168
- while (queue.length > 0 && iterations < MAX_ITERATIONS) {
1169
- iterations++;
1170
- const [currentId, arrivalMode] = queue.shift();
1171
- const { start: currStart, end: currEnd } = updatedDates.get(currentId);
1172
- const currentOriginal = taskById.get(currentId);
1173
- if (arrivalMode !== "parent-recalc") {
1174
- const children = childrenByParentId.get(currentId) ?? [];
1175
- for (const child of children) {
1176
- if (childShifted.has(child.id) || child.locked) continue;
1177
- const parentOrigStart = new Date(currentOriginal.startDate);
1178
- const parentOrigEnd = new Date(currentOriginal.endDate);
1179
- const childOrigStart = new Date(child.startDate);
1180
- const childOrigEnd = new Date(child.endDate);
1181
- const startDeltaMs = currStart.getTime() - parentOrigStart.getTime();
1182
- const endDeltaMs = currEnd.getTime() - parentOrigEnd.getTime();
1183
- let childNewStart;
1184
- let childNewEnd;
1185
- if (businessDays && weekendPredicate) {
1186
- const proposedStart = new Date(childOrigStart.getTime() + startDeltaMs);
1187
- const snapDirection = currStart.getTime() >= parentOrigStart.getTime() ? 1 : -1;
1188
- const movedRange = moveTaskRange(
1189
- child.startDate,
1190
- child.endDate,
1191
- proposedStart,
1192
- true,
1193
- weekendPredicate,
1194
- snapDirection
1195
- );
1196
- childNewStart = movedRange.start;
1197
- childNewEnd = movedRange.end;
1198
- } else {
1199
- childNewStart = new Date(childOrigStart.getTime() + startDeltaMs);
1200
- childNewEnd = new Date(childOrigEnd.getTime() + endDeltaMs);
1201
- }
1202
- const prev = updatedDates.get(child.id);
1203
- if (prev && prev.start.getTime() === childNewStart.getTime() && prev.end.getTime() === childNewEnd.getTime()) {
1204
- continue;
1365
+ const stack = Array.from(criticalLeafIds);
1366
+ while (stack.length > 0) {
1367
+ const id = stack.pop();
1368
+ const parentId = parentByChild.get(id);
1369
+ if (parentId && !result.has(parentId)) {
1370
+ result.add(parentId);
1371
+ stack.push(parentId);
1372
+ }
1373
+ }
1374
+ return result;
1375
+ }
1376
+
1377
+ // src/core/scheduling/commands.ts
1378
+ function buildTaskRangeFromStart(startDate, duration, businessDays = false, weekendPredicate, snapDirection = 1) {
1379
+ const normalizedStart = businessDays && weekendPredicate ? alignToWorkingDay(startDate, snapDirection, weekendPredicate) : normalizeUTCDate(startDate);
1380
+ if (businessDays && weekendPredicate) {
1381
+ return {
1382
+ start: normalizedStart,
1383
+ end: parseDateOnly(addBusinessDays(normalizedStart, duration, weekendPredicate))
1384
+ };
1385
+ }
1386
+ const DAY_MS7 = 24 * 60 * 60 * 1e3;
1387
+ return {
1388
+ start: normalizedStart,
1389
+ end: new Date(normalizedStart.getTime() + (Math.max(1, duration) - 1) * DAY_MS7)
1390
+ };
1391
+ }
1392
+ function buildTaskRangeFromEnd(endDate, duration, businessDays = false, weekendPredicate, snapDirection = -1) {
1393
+ const normalizedEnd = businessDays && weekendPredicate ? alignToWorkingDay(endDate, snapDirection, weekendPredicate) : normalizeUTCDate(endDate);
1394
+ if (businessDays && weekendPredicate) {
1395
+ return {
1396
+ start: parseDateOnly(subtractBusinessDays(normalizedEnd, duration, weekendPredicate)),
1397
+ end: normalizedEnd
1398
+ };
1399
+ }
1400
+ const DAY_MS7 = 24 * 60 * 60 * 1e3;
1401
+ return {
1402
+ start: new Date(normalizedEnd.getTime() - (Math.max(1, duration) - 1) * DAY_MS7),
1403
+ end: normalizedEnd
1404
+ };
1405
+ }
1406
+ function moveTaskRange(originalStart, originalEnd, proposedStart, businessDays = false, weekendPredicate, snapDirection = 1) {
1407
+ return buildTaskRangeFromStart(
1408
+ proposedStart,
1409
+ getTaskDuration(originalStart, originalEnd, businessDays, weekendPredicate),
1410
+ businessDays,
1411
+ weekendPredicate,
1412
+ snapDirection
1413
+ );
1414
+ }
1415
+ function clampTaskRangeForIncomingFS(task, proposedStart, proposedEnd, allTasks, businessDays = false, weekendPredicate, taskById) {
1416
+ if (!task.dependencies?.length) {
1417
+ return { start: proposedStart, end: proposedEnd };
1418
+ }
1419
+ let minAllowedStart = null;
1420
+ for (const dep of task.dependencies) {
1421
+ if (dep.type !== "FS") {
1422
+ continue;
1423
+ }
1424
+ const predecessor = taskById?.get(dep.taskId) ?? allTasks.find((candidate) => candidate.id === dep.taskId);
1425
+ if (!predecessor) {
1426
+ continue;
1427
+ }
1428
+ const { predStart: predecessorStart, predEnd: predecessorEnd } = normalizePredecessorDates(predecessor, parseDateOnly);
1429
+ const predecessorDuration = getTaskDuration(
1430
+ predecessorStart,
1431
+ predecessorEnd,
1432
+ businessDays,
1433
+ weekendPredicate
1434
+ );
1435
+ const candidateMinStart = calculateSuccessorDate(
1436
+ predecessorStart,
1437
+ predecessorEnd,
1438
+ "FS",
1439
+ -predecessorDuration,
1440
+ businessDays,
1441
+ weekendPredicate,
1442
+ task.type
1443
+ );
1444
+ if (!minAllowedStart || candidateMinStart.getTime() > minAllowedStart.getTime()) {
1445
+ minAllowedStart = candidateMinStart;
1446
+ }
1447
+ }
1448
+ if (!minAllowedStart || proposedStart.getTime() >= minAllowedStart.getTime()) {
1449
+ return { start: proposedStart, end: proposedEnd };
1450
+ }
1451
+ return buildTaskRangeFromStart(
1452
+ minAllowedStart,
1453
+ getTaskDuration(proposedStart, proposedEnd, businessDays, weekendPredicate),
1454
+ businessDays,
1455
+ weekendPredicate
1456
+ );
1457
+ }
1458
+ function recalculateIncomingLags(task, newStartDate, newEndDate, allTasks, businessDays = false, weekendPredicate, taskById) {
1459
+ if (!task.dependencies) return [];
1460
+ return task.dependencies.map((dep) => {
1461
+ const predecessor = taskById?.get(dep.taskId) ?? allTasks.find((candidate) => candidate.id === dep.taskId);
1462
+ if (!predecessor) {
1463
+ return { ...dep, lag: getDependencyLag(dep) };
1464
+ }
1465
+ const { predStart: predecessorStart, predEnd: predecessorEnd } = normalizePredecessorDates(
1466
+ predecessor,
1467
+ (d) => new Date(d instanceof Date ? d.getTime() : `${String(d).split("T")[0]}T00:00:00.000Z`)
1468
+ );
1469
+ const nextLag = computeLagFromDates(
1470
+ dep.type,
1471
+ predecessorStart,
1472
+ predecessorEnd,
1473
+ newStartDate,
1474
+ newEndDate,
1475
+ businessDays,
1476
+ weekendPredicate,
1477
+ task.type
1478
+ );
1479
+ return { ...dep, lag: nextLag };
1480
+ });
1481
+ }
1482
+
1483
+ // src/core/scheduling/cascade.ts
1484
+ function parseCascadeDateInput(date) {
1485
+ if (date instanceof Date) {
1486
+ return normalizeUTCDate(date);
1487
+ }
1488
+ return normalizeUTCDate(/* @__PURE__ */ new Date(`${date.split("T")[0]}T00:00:00.000Z`));
1489
+ }
1490
+ function createCascadeContext(allTasks) {
1491
+ const childrenByParentId = /* @__PURE__ */ new Map();
1492
+ const dependentsByPredecessorId = /* @__PURE__ */ new Map();
1493
+ const taskById = /* @__PURE__ */ new Map();
1494
+ for (const task of allTasks) {
1495
+ taskById.set(task.id, task);
1496
+ if (task.parentId) {
1497
+ const children = childrenByParentId.get(task.parentId) ?? [];
1498
+ children.push(task);
1499
+ childrenByParentId.set(task.parentId, children);
1500
+ }
1501
+ if (!task.dependencies) continue;
1502
+ task.dependencies.forEach((dependency, dependencyIndex) => {
1503
+ const dependents = dependentsByPredecessorId.get(dependency.taskId) ?? [];
1504
+ dependents.push({ task, dependencyIndex });
1505
+ dependentsByPredecessorId.set(dependency.taskId, dependents);
1506
+ });
1507
+ }
1508
+ return { childrenByParentId, dependentsByPredecessorId, taskById };
1509
+ }
1510
+ function getSuccessorChain(draggedTaskId, allTasks, linkTypes = ["FS"]) {
1511
+ const { dependentsByPredecessorId, taskById } = createCascadeContext(allTasks);
1512
+ const visited = /* @__PURE__ */ new Set();
1513
+ const queue = [draggedTaskId];
1514
+ const chain = [];
1515
+ visited.add(draggedTaskId);
1516
+ while (queue.length > 0) {
1517
+ const current = queue.shift();
1518
+ const successors = dependentsByPredecessorId.get(current) ?? [];
1519
+ for (const { task } of successors) {
1520
+ if (!linkTypes.includes(task.dependencies?.find((dependency) => dependency.taskId === current)?.type ?? "FS")) {
1521
+ continue;
1522
+ }
1523
+ const sid = task.id;
1524
+ if (!visited.has(sid)) {
1525
+ visited.add(sid);
1526
+ const successorTask = taskById.get(sid);
1527
+ if (successorTask) {
1528
+ chain.push(successorTask);
1529
+ queue.push(sid);
1205
1530
  }
1206
- updatedDates.set(child.id, { start: childNewStart, end: childNewEnd });
1207
- childShifted.add(child.id);
1208
- queue.push([child.id, "child-delta"]);
1209
- resultMap.set(child.id, normalizeTaskDependencyLags({
1210
- ...child,
1211
- startDate: childNewStart.toISOString().split("T")[0],
1212
- endDate: childNewEnd.toISOString().split("T")[0]
1213
- }));
1214
1531
  }
1215
1532
  }
1216
- const parentId = currentOriginal.parentId;
1217
- if (parentId && parentId !== movedTask.id) {
1218
- const parent = taskById.get(parentId);
1219
- if (parent && !parent.locked) {
1220
- const siblings = childrenByParentId.get(parentId) ?? [];
1221
- const siblingPositions = siblings.map((sib) => {
1222
- if (updatedDates.has(sib.id)) return updatedDates.get(sib.id);
1223
- return { start: new Date(sib.startDate), end: new Date(sib.endDate) };
1224
- });
1225
- const minStart = new Date(Math.min(...siblingPositions.map((p) => p.start.getTime())));
1226
- const maxEnd = new Date(Math.max(...siblingPositions.map((p) => p.end.getTime())));
1227
- const prev = updatedDates.get(parentId);
1228
- if (!prev || prev.start.getTime() !== minStart.getTime() || prev.end.getTime() !== maxEnd.getTime()) {
1229
- updatedDates.set(parentId, { start: minStart, end: maxEnd });
1230
- queue.push([parentId, "parent-recalc"]);
1231
- resultMap.set(parentId, normalizeTaskDependencyLags({
1232
- ...parent,
1233
- startDate: minStart.toISOString().split("T")[0],
1234
- endDate: maxEnd.toISOString().split("T")[0]
1235
- }));
1236
- }
1533
+ }
1534
+ return chain;
1535
+ }
1536
+ function cascadeByLinks(movedTaskId, newStart, newEnd, allTasks, skipChildCascade = false) {
1537
+ const { childrenByParentId, dependentsByPredecessorId, taskById } = createCascadeContext(allTasks);
1538
+ const updatedDates = /* @__PURE__ */ new Map();
1539
+ updatedDates.set(movedTaskId, { start: newStart, end: newEnd });
1540
+ const result = [];
1541
+ const queue = [movedTaskId];
1542
+ const visited = /* @__PURE__ */ new Set([movedTaskId]);
1543
+ while (queue.length > 0) {
1544
+ const currentId = queue.shift();
1545
+ const { start: predStart, end: predEnd } = updatedDates.get(currentId);
1546
+ if (!skipChildCascade) {
1547
+ const children = childrenByParentId.get(currentId) ?? [];
1548
+ for (const child of children) {
1549
+ if (visited.has(child.id) || child.locked) continue;
1550
+ const origStart = new Date(child.startDate);
1551
+ const origEnd = new Date(child.endDate);
1552
+ const durationMs = origEnd.getTime() - origStart.getTime();
1553
+ const parentOrig = taskById.get(currentId);
1554
+ const parentOrigStart = new Date(parentOrig.startDate);
1555
+ const parentOrigEnd = new Date(parentOrig.endDate);
1556
+ const parentStartDelta = predStart.getTime() - parentOrigStart.getTime();
1557
+ const parentEndDelta = predEnd.getTime() - parentOrigEnd.getTime();
1558
+ const newChildStart = new Date(origStart.getTime() + parentStartDelta);
1559
+ const newChildEnd = new Date(origEnd.getTime() + parentEndDelta);
1560
+ visited.add(child.id);
1561
+ updatedDates.set(child.id, { start: newChildStart, end: newChildEnd });
1562
+ result.push(normalizeTaskDependencyLags({
1563
+ ...child,
1564
+ startDate: newChildStart.toISOString().split("T")[0],
1565
+ endDate: newChildEnd.toISOString().split("T")[0]
1566
+ }));
1567
+ queue.push(child.id);
1237
1568
  }
1238
1569
  }
1239
1570
  const dependents = dependentsByPredecessorId.get(currentId) ?? [];
1240
1571
  for (const { task, dependencyIndex } of dependents) {
1241
- if (task.locked || !task.dependencies) continue;
1242
- if (childShifted.has(task.id)) continue;
1572
+ if (visited.has(task.id) || !task.dependencies || task.locked) continue;
1243
1573
  const dep = task.dependencies[dependencyIndex];
1244
1574
  if (!dep) continue;
1245
- const origStart = new Date(task.startDate);
1246
- const origEnd = new Date(task.endDate);
1575
+ const orig = taskById.get(task.id);
1576
+ const origStart = new Date(orig.startDate);
1577
+ const origEnd = new Date(orig.endDate);
1578
+ const duration = getTaskDuration(origStart, origEnd);
1579
+ const currentTask = taskById.get(currentId);
1247
1580
  const { predStart: normalizedPredStart, predEnd: normalizedPredEnd } = normalizePredecessorDates(
1248
1581
  {
1249
- startDate: currStart,
1250
- endDate: currEnd,
1251
- type: currentOriginal.type
1582
+ startDate: predStart,
1583
+ endDate: predEnd,
1584
+ type: currentTask.type
1252
1585
  },
1253
1586
  parseCascadeDateInput
1254
1587
  );
@@ -1257,8 +1590,172 @@ function universalCascade(movedTask, newStart, newEnd, allTasks, businessDays =
1257
1590
  normalizedPredEnd,
1258
1591
  dep.type,
1259
1592
  getDependencyLag(dep),
1260
- businessDays,
1261
- weekendPredicate,
1593
+ false,
1594
+ void 0,
1595
+ task.type
1596
+ );
1597
+ let newSuccStart;
1598
+ let newSuccEnd;
1599
+ if (dep.type === "FS" || dep.type === "SS") {
1600
+ ({ start: newSuccStart, end: newSuccEnd } = buildTaskRangeFromStart(constraintDate, duration));
1601
+ } else {
1602
+ ({ start: newSuccStart, end: newSuccEnd } = buildTaskRangeFromEnd(constraintDate, duration));
1603
+ }
1604
+ visited.add(task.id);
1605
+ updatedDates.set(task.id, { start: newSuccStart, end: newSuccEnd });
1606
+ result.push(normalizeTaskDependencyLags({
1607
+ ...task,
1608
+ startDate: newSuccStart.toISOString().split("T")[0],
1609
+ endDate: newSuccEnd.toISOString().split("T")[0]
1610
+ }));
1611
+ queue.push(task.id);
1612
+ }
1613
+ }
1614
+ return result;
1615
+ }
1616
+ function getTransitiveCascadeChain(changedTaskId, allTasks, firstLevelLinkTypes) {
1617
+ const { childrenByParentId, dependentsByPredecessorId } = createCascadeContext(allTasks);
1618
+ const directChildren = childrenByParentId.get(changedTaskId) ?? [];
1619
+ const directSuccessors = getSuccessorChain(changedTaskId, allTasks, firstLevelLinkTypes);
1620
+ const initialChain = [...directChildren, ...directSuccessors].filter(
1621
+ (task, index, arr) => arr.findIndex((candidate) => candidate.id === task.id) === index
1622
+ );
1623
+ const chain = [...initialChain];
1624
+ const visited = /* @__PURE__ */ new Set([changedTaskId, ...initialChain.map((t) => t.id)]);
1625
+ const queue = [...initialChain];
1626
+ while (queue.length > 0) {
1627
+ const current = queue.shift();
1628
+ const children = childrenByParentId.get(current.id) ?? [];
1629
+ for (const child of children) {
1630
+ if (!visited.has(child.id)) {
1631
+ visited.add(child.id);
1632
+ chain.push(child);
1633
+ queue.push(child);
1634
+ }
1635
+ }
1636
+ const successors = dependentsByPredecessorId.get(current.id) ?? [];
1637
+ for (const { task: successor } of successors) {
1638
+ if (!visited.has(successor.id)) {
1639
+ visited.add(successor.id);
1640
+ chain.push(successor);
1641
+ queue.push(successor);
1642
+ }
1643
+ }
1644
+ }
1645
+ return chain;
1646
+ }
1647
+ function universalCascade(movedTask, newStart, newEnd, allTasks, businessDays = false, weekendPredicate, context = createCascadeContext(allTasks)) {
1648
+ const { childrenByParentId, dependentsByPredecessorId, taskById } = context;
1649
+ const normalizedMovedTask = normalizeTaskDependencyLags({
1650
+ ...movedTask,
1651
+ startDate: newStart.toISOString().split("T")[0],
1652
+ endDate: newEnd.toISOString().split("T")[0]
1653
+ });
1654
+ if (!movedTask.parentId && !childrenByParentId.has(movedTask.id) && !dependentsByPredecessorId.has(movedTask.id)) {
1655
+ return [normalizedMovedTask];
1656
+ }
1657
+ const updatedDates = /* @__PURE__ */ new Map();
1658
+ updatedDates.set(movedTask.id, { start: newStart, end: newEnd });
1659
+ const resultMap = /* @__PURE__ */ new Map();
1660
+ resultMap.set(movedTask.id, normalizedMovedTask);
1661
+ const queue = [[movedTask.id, "direct"]];
1662
+ const childShifted = /* @__PURE__ */ new Set();
1663
+ let iterations = 0;
1664
+ const MAX_ITERATIONS = allTasks.length * 3;
1665
+ while (queue.length > 0 && iterations < MAX_ITERATIONS) {
1666
+ iterations++;
1667
+ const [currentId, arrivalMode] = queue.shift();
1668
+ const { start: currStart, end: currEnd } = updatedDates.get(currentId);
1669
+ const currentOriginal = taskById.get(currentId);
1670
+ if (arrivalMode !== "parent-recalc") {
1671
+ const children = childrenByParentId.get(currentId) ?? [];
1672
+ for (const child of children) {
1673
+ if (childShifted.has(child.id) || child.locked) continue;
1674
+ const parentOrigStart = new Date(currentOriginal.startDate);
1675
+ const parentOrigEnd = new Date(currentOriginal.endDate);
1676
+ const childOrigStart = new Date(child.startDate);
1677
+ const childOrigEnd = new Date(child.endDate);
1678
+ const startDeltaMs = currStart.getTime() - parentOrigStart.getTime();
1679
+ const endDeltaMs = currEnd.getTime() - parentOrigEnd.getTime();
1680
+ let childNewStart;
1681
+ let childNewEnd;
1682
+ if (businessDays && weekendPredicate) {
1683
+ const proposedStart = new Date(childOrigStart.getTime() + startDeltaMs);
1684
+ const snapDirection = currStart.getTime() >= parentOrigStart.getTime() ? 1 : -1;
1685
+ const movedRange = moveTaskRange(
1686
+ child.startDate,
1687
+ child.endDate,
1688
+ proposedStart,
1689
+ true,
1690
+ weekendPredicate,
1691
+ snapDirection
1692
+ );
1693
+ childNewStart = movedRange.start;
1694
+ childNewEnd = movedRange.end;
1695
+ } else {
1696
+ childNewStart = new Date(childOrigStart.getTime() + startDeltaMs);
1697
+ childNewEnd = new Date(childOrigEnd.getTime() + endDeltaMs);
1698
+ }
1699
+ const prev = updatedDates.get(child.id);
1700
+ if (prev && prev.start.getTime() === childNewStart.getTime() && prev.end.getTime() === childNewEnd.getTime()) {
1701
+ continue;
1702
+ }
1703
+ updatedDates.set(child.id, { start: childNewStart, end: childNewEnd });
1704
+ childShifted.add(child.id);
1705
+ queue.push([child.id, "child-delta"]);
1706
+ resultMap.set(child.id, normalizeTaskDependencyLags({
1707
+ ...child,
1708
+ startDate: childNewStart.toISOString().split("T")[0],
1709
+ endDate: childNewEnd.toISOString().split("T")[0]
1710
+ }));
1711
+ }
1712
+ }
1713
+ const parentId = currentOriginal.parentId;
1714
+ if (parentId && parentId !== movedTask.id) {
1715
+ const parent = taskById.get(parentId);
1716
+ if (parent && !parent.locked) {
1717
+ const siblings = childrenByParentId.get(parentId) ?? [];
1718
+ const siblingPositions = siblings.map((sib) => {
1719
+ if (updatedDates.has(sib.id)) return updatedDates.get(sib.id);
1720
+ return { start: new Date(sib.startDate), end: new Date(sib.endDate) };
1721
+ });
1722
+ const minStart = new Date(Math.min(...siblingPositions.map((p) => p.start.getTime())));
1723
+ const maxEnd = new Date(Math.max(...siblingPositions.map((p) => p.end.getTime())));
1724
+ const prev = updatedDates.get(parentId);
1725
+ if (!prev || prev.start.getTime() !== minStart.getTime() || prev.end.getTime() !== maxEnd.getTime()) {
1726
+ updatedDates.set(parentId, { start: minStart, end: maxEnd });
1727
+ queue.push([parentId, "parent-recalc"]);
1728
+ resultMap.set(parentId, normalizeTaskDependencyLags({
1729
+ ...parent,
1730
+ startDate: minStart.toISOString().split("T")[0],
1731
+ endDate: maxEnd.toISOString().split("T")[0]
1732
+ }));
1733
+ }
1734
+ }
1735
+ }
1736
+ const dependents = dependentsByPredecessorId.get(currentId) ?? [];
1737
+ for (const { task, dependencyIndex } of dependents) {
1738
+ if (task.locked || !task.dependencies) continue;
1739
+ if (childShifted.has(task.id)) continue;
1740
+ const dep = task.dependencies[dependencyIndex];
1741
+ if (!dep) continue;
1742
+ const origStart = new Date(task.startDate);
1743
+ const origEnd = new Date(task.endDate);
1744
+ const { predStart: normalizedPredStart, predEnd: normalizedPredEnd } = normalizePredecessorDates(
1745
+ {
1746
+ startDate: currStart,
1747
+ endDate: currEnd,
1748
+ type: currentOriginal.type
1749
+ },
1750
+ parseCascadeDateInput
1751
+ );
1752
+ const constraintDate = calculateSuccessorDate(
1753
+ normalizedPredStart,
1754
+ normalizedPredEnd,
1755
+ dep.type,
1756
+ getDependencyLag(dep),
1757
+ businessDays,
1758
+ weekendPredicate,
1262
1759
  task.type
1263
1760
  );
1264
1761
  let succNewStart;
@@ -1324,13 +1821,13 @@ function computeParentProgress(parentId, tasks) {
1324
1821
  if (children.length === 0) {
1325
1822
  return 0;
1326
1823
  }
1327
- const DAY_MS6 = 24 * 60 * 60 * 1e3;
1824
+ const DAY_MS7 = 24 * 60 * 60 * 1e3;
1328
1825
  let totalWeight = 0;
1329
1826
  let weightedSum = 0;
1330
1827
  for (const child of children) {
1331
1828
  const start = new Date(child.startDate).getTime();
1332
1829
  const end = new Date(child.endDate).getTime();
1333
- const duration = (end - start + DAY_MS6) / DAY_MS6;
1830
+ const duration = (end - start + DAY_MS7) / DAY_MS7;
1334
1831
  const progress = child.progress ?? 0;
1335
1832
  totalWeight += duration;
1336
1833
  weightedSum += duration * progress;
@@ -3047,7 +3544,7 @@ function normalizeHierarchyTasks(tasks) {
3047
3544
  children.push(index);
3048
3545
  childrenByParent.set(parentId, children);
3049
3546
  }
3050
- const DAY_MS6 = 24 * 60 * 60 * 1e3;
3547
+ const DAY_MS7 = 24 * 60 * 60 * 1e3;
3051
3548
  for (let index = orderedTasks.length - 1; index >= 0; index--) {
3052
3549
  const children = childrenByParent.get(orderedTasks[index].id);
3053
3550
  if (!children?.length) continue;
@@ -3061,7 +3558,7 @@ function normalizeHierarchyTasks(tasks) {
3061
3558
  const end = new Date(child.endDate).getTime();
3062
3559
  minStart = Math.min(minStart, start);
3063
3560
  maxEnd = Math.max(maxEnd, end);
3064
- const duration = (end - start + DAY_MS6) / DAY_MS6;
3561
+ const duration = (end - start + DAY_MS7) / DAY_MS7;
3065
3562
  totalWeight += duration;
3066
3563
  weightedProgress += duration * (child.progress ?? 0);
3067
3564
  }
@@ -3277,301 +3774,36 @@ var TimeScaleHeader = ({
3277
3774
  "div",
3278
3775
  {
3279
3776
  className: `gantt-tsh-dayCell ${isWeekendDay ? "gantt-tsh-weekendDay" : ""} ${isTodayDate ? "gantt-tsh-today" : ""} ${marker && !isTodayDate ? "gantt-tsh-markerDay" : ""} ${tooltipText ? "gantt-tsh-dayCell-tooltipTrigger" : ""}`,
3280
- style: markerColor && !isTodayDate ? {
3281
- ["--gantt-marker-day-color"]: markerColor
3282
- } : void 0,
3283
- "aria-label": tooltipText,
3284
- onMouseEnter: () => {
3285
- if (!tooltipText) return;
3286
- onTimelineHover?.({
3287
- label: tooltipText,
3288
- left: index * dayWidth,
3289
- color: isTodayDate ? "#dc2626" : markerColor || "var(--gantt-timeline-marker-color, #dc2626)"
3290
- });
3291
- },
3292
- onMouseLeave: onTimelineHoverEnd,
3293
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gantt-tsh-dayLabel", children: (0, import_date_fns.format)(day, "d") })
3294
- },
3295
- `day-${index}`
3296
- );
3297
- })
3298
- )
3299
- }
3300
- )
3301
- ]
3302
- }
3303
- );
3304
- };
3305
- var TimeScaleHeader_default = TimeScaleHeader;
3306
-
3307
- // src/components/TaskRow/TaskRow.tsx
3308
- var import_react3 = __toESM(require("react"));
3309
- var import_react_dom = require("react-dom");
3310
-
3311
- // src/utils/geometry.ts
3312
- var getUTCDayDifference = (date1, date2) => {
3313
- const ms1 = Date.UTC(
3314
- date1.getUTCFullYear(),
3315
- date1.getUTCMonth(),
3316
- date1.getUTCDate()
3317
- );
3318
- const ms2 = Date.UTC(
3319
- date2.getUTCFullYear(),
3320
- date2.getUTCMonth(),
3321
- date2.getUTCDate()
3322
- );
3323
- return Math.round((ms1 - ms2) / (1e3 * 60 * 60 * 24));
3324
- };
3325
- var calculateTaskBar = (taskStartDate, taskEndDate, monthStart, dayWidth) => {
3326
- const startOffset = getUTCDayDifference(taskStartDate, monthStart);
3327
- const duration = getUTCDayDifference(taskEndDate, taskStartDate);
3328
- const left = Math.round(startOffset * dayWidth);
3329
- const width = Math.round((duration + 1) * dayWidth);
3330
- return { left, width };
3331
- };
3332
- var calculateMilestoneGeometry = (taskDate, monthStart, dayWidth, size = 14) => {
3333
- const { left, width } = calculateTaskBar(taskDate, taskDate, monthStart, dayWidth);
3334
- const centerX = Math.round(left + width / 2);
3335
- const halfSize = Math.round(size / 2);
3336
- return {
3337
- centerX,
3338
- left: centerX - halfSize,
3339
- right: centerX + halfSize,
3340
- size
3341
- };
3342
- };
3343
- var calculateMilestoneConnectionBounds = (dayLeft, dayWidth, size = 14) => {
3344
- const halfDiagonal = Math.round(size / Math.SQRT2);
3345
- const centerX = Math.round(dayLeft + dayWidth / 2);
3346
- return {
3347
- left: centerX - halfDiagonal,
3348
- right: centerX + halfDiagonal
3349
- };
3350
- };
3351
- var resolveTaskHorizontalGeometry = (task, monthStart, dayWidth, override) => {
3352
- const startDate = new Date(task.startDate);
3353
- const endDate = new Date(task.endDate);
3354
- if (task.type === "milestone") {
3355
- const size = 14;
3356
- if (override) {
3357
- const bounds2 = calculateMilestoneConnectionBounds(override.left, dayWidth, size);
3358
- return {
3359
- ...bounds2,
3360
- centerX: Math.round(override.left + dayWidth / 2)
3361
- };
3362
- }
3363
- const bar2 = calculateTaskBar(startDate, startDate, monthStart, dayWidth);
3364
- const bounds = calculateMilestoneConnectionBounds(bar2.left, dayWidth, size);
3365
- return {
3366
- ...bounds,
3367
- centerX: Math.round(bar2.left + dayWidth / 2)
3368
- };
3369
- }
3370
- if (override) {
3371
- return {
3372
- left: override.left,
3373
- right: override.left + override.width,
3374
- centerX: Math.round(override.left + override.width / 2)
3375
- };
3376
- }
3377
- const bar = calculateTaskBar(startDate, endDate, monthStart, dayWidth);
3378
- return {
3379
- left: bar.left,
3380
- right: bar.left + bar.width,
3381
- centerX: Math.round(bar.left + bar.width / 2)
3382
- };
3383
- };
3384
- var pixelsToDate = (pixels, monthStart, dayWidth) => {
3385
- const days = Math.round(pixels / dayWidth);
3386
- return new Date(Date.UTC(
3387
- monthStart.getUTCFullYear(),
3388
- monthStart.getUTCMonth(),
3389
- monthStart.getUTCDate() + days
3390
- ));
3391
- };
3392
- var calculateGridWidth = (daysInMonth, dayWidth) => {
3393
- return Math.round(daysInMonth * dayWidth);
3394
- };
3395
- var MIN_EDGE_ZONE_PX = 8;
3396
- var MIN_MOVE_ZONE_PX = 16;
3397
- var detectEdgeZone = (clientX, taskBarElement, edgeZoneWidth = 12) => {
3398
- const rect = taskBarElement.getBoundingClientRect();
3399
- const relativeX = Math.round(clientX - rect.left);
3400
- const width = Math.round(rect.width);
3401
- const effectiveZone = Math.max(
3402
- MIN_EDGE_ZONE_PX,
3403
- Math.min(edgeZoneWidth, (width - MIN_MOVE_ZONE_PX) / 2)
3404
- );
3405
- if (relativeX >= 0 && relativeX <= effectiveZone) {
3406
- return "left";
3407
- }
3408
- if (relativeX >= width - effectiveZone && relativeX <= width) {
3409
- return "right";
3410
- }
3411
- return "move";
3412
- };
3413
- var getCursorForPosition = (position) => {
3414
- switch (position) {
3415
- case "left":
3416
- case "right":
3417
- return "ew-resize";
3418
- case "move":
3419
- return "grab";
3420
- default:
3421
- return "default";
3422
- }
3423
- };
3424
- var calculateGridLines = (dateRange, dayWidth) => {
3425
- const lines = [];
3426
- for (let i = 0; i < dateRange.length; i++) {
3427
- const date = dateRange[i];
3428
- const x = Math.round(i * dayWidth);
3429
- const isMonthStart = i === 0 ? false : date.getUTCDate() === 1;
3430
- const isWeekStart = date.getUTCDay() === 1;
3431
- lines.push({ x, isMonthStart, isWeekStart });
3432
- }
3433
- if (dateRange.length > 0) {
3434
- lines.push({
3435
- x: Math.round(dateRange.length * dayWidth),
3436
- isMonthStart: false,
3437
- isWeekStart: false
3438
- });
3439
- }
3440
- return lines;
3441
- };
3442
- var calculateWeekendBlocks = (dateRange, dayWidth, isCustomWeekend) => {
3443
- const blocks = [];
3444
- let inWeekend = false;
3445
- let weekendStartIndex = -1;
3446
- for (let i = 0; i < dateRange.length; i++) {
3447
- const date = dateRange[i];
3448
- const isWeekend3 = isCustomWeekend ? isCustomWeekend(date) : date.getUTCDay() === 0 || date.getUTCDay() === 6;
3449
- if (isWeekend3 && !inWeekend) {
3450
- inWeekend = true;
3451
- weekendStartIndex = i;
3452
- } else if (!isWeekend3 && inWeekend) {
3453
- inWeekend = false;
3454
- const left = Math.round(weekendStartIndex * dayWidth);
3455
- const width = Math.round((i - weekendStartIndex) * dayWidth);
3456
- blocks.push({ left, width });
3457
- }
3458
- }
3459
- if (inWeekend && weekendStartIndex >= 0) {
3460
- const left = Math.round(weekendStartIndex * dayWidth);
3461
- const width = Math.round((dateRange.length - weekendStartIndex) * dayWidth);
3462
- blocks.push({ left, width });
3463
- }
3464
- return blocks;
3465
- };
3466
- var calculateBezierPath = (from, to) => {
3467
- const verticalDistance = Math.abs(to.y - from.y);
3468
- const cpOffset = Math.max(verticalDistance * 0.5, 20);
3469
- if (from.y === to.y) {
3470
- const arcHeight = 20;
3471
- const midX = (from.x + to.x) / 2;
3472
- return `M ${from.x} ${from.y} Q ${midX} ${from.y - arcHeight} ${to.x} ${to.y}`;
3473
- }
3474
- const cp1x = from.x;
3475
- const cp1y = from.y + (to.y > from.y ? cpOffset : -cpOffset);
3476
- const cp2x = to.x;
3477
- const cp2y = to.y - (to.y > from.y ? cpOffset : -cpOffset);
3478
- return `M ${Math.round(from.x)} ${Math.round(from.y)} C ${Math.round(cp1x)} ${Math.round(cp1y)}, ${Math.round(cp2x)} ${Math.round(cp2y)}, ${Math.round(to.x)} ${Math.round(to.y)}`;
3479
- };
3480
- var calculateDependencyPath = (from, to, arrivesFromRight) => {
3481
- const fx = Math.round(from.x);
3482
- const fy = Math.round(from.y);
3483
- const tx = Math.round(to.x);
3484
- const ty = Math.round(to.y);
3485
- if (fy === ty) {
3486
- return `M ${fx} ${fy} H ${tx}`;
3487
- }
3488
- if (fx === tx) {
3489
- return `M ${fx} ${fy} V ${ty}`;
3490
- }
3491
- const C = 2;
3492
- const goingDown = ty > fy;
3493
- const dirY = goingDown ? 1 : -1;
3494
- if (arrivesFromRight) {
3495
- const goingRight = tx >= fx;
3496
- const dirX = goingRight ? 1 : -1;
3497
- if (Math.abs(ty - fy) >= C && Math.abs(tx - fx) >= C) {
3498
- return [
3499
- `M ${fx} ${fy}`,
3500
- `H ${tx - dirX * C}`,
3501
- `L ${tx} ${fy + dirY * C}`,
3502
- `V ${ty}`
3503
- ].join(" ");
3504
- }
3505
- return `M ${fx} ${fy} H ${tx} V ${ty}`;
3506
- } else {
3507
- const goingRight = tx >= fx;
3508
- const dirX = goingRight ? 1 : -1;
3509
- if (Math.abs(ty - fy) >= C && Math.abs(tx - fx) >= C) {
3510
- return [
3511
- `M ${fx} ${fy}`,
3512
- `H ${tx - dirX * C}`,
3513
- `L ${tx} ${fy + dirY * C}`,
3514
- `V ${ty}`
3515
- ].join(" ");
3516
- }
3517
- return `M ${fx} ${fy} H ${tx} V ${ty}`;
3518
- }
3519
- };
3520
- var calculateWeekGridLines = (dateRange, dayWidth) => {
3521
- const blocks = getWeekBlocks(dateRange);
3522
- const lines = [];
3523
- let currentDayIndex = 0;
3524
- for (let i = 0; i < blocks.length; i++) {
3525
- const block = blocks[i];
3526
- currentDayIndex += block.days;
3527
- const blockEndMonth = dateRange[currentDayIndex - 1];
3528
- const nextBlockMonth = currentDayIndex < dateRange.length ? dateRange[currentDayIndex] : null;
3529
- const isMonthBoundary = nextBlockMonth && blockEndMonth.getUTCMonth() !== nextBlockMonth.getUTCMonth();
3530
- lines.push({
3531
- x: Math.round(currentDayIndex * dayWidth),
3532
- isMonthStart: isMonthBoundary || false
3533
- });
3534
- }
3535
- if (lines.length > 0 && lines[lines.length - 1].x === Math.round(dateRange.length * dayWidth)) {
3536
- lines.pop();
3537
- }
3538
- return lines;
3539
- };
3540
- var calculateMonthGridLines = (dateRange, dayWidth) => {
3541
- const lines = [];
3542
- for (let i = 1; i < dateRange.length; i++) {
3543
- const date = dateRange[i];
3544
- if (date.getUTCDate() === 1) {
3545
- const x = Math.round(i * dayWidth);
3546
- const isYearBoundary = date.getUTCMonth() === 0;
3547
- lines.push({ x, isMonthStart: isYearBoundary });
3777
+ style: markerColor && !isTodayDate ? {
3778
+ ["--gantt-marker-day-color"]: markerColor
3779
+ } : void 0,
3780
+ "aria-label": tooltipText,
3781
+ onMouseEnter: () => {
3782
+ if (!tooltipText) return;
3783
+ onTimelineHover?.({
3784
+ label: tooltipText,
3785
+ left: index * dayWidth,
3786
+ color: isTodayDate ? "#dc2626" : markerColor || "var(--gantt-timeline-marker-color, #dc2626)"
3787
+ });
3788
+ },
3789
+ onMouseLeave: onTimelineHoverEnd,
3790
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gantt-tsh-dayLabel", children: (0, import_date_fns.format)(day, "d") })
3791
+ },
3792
+ `day-${index}`
3793
+ );
3794
+ })
3795
+ )
3796
+ }
3797
+ )
3798
+ ]
3548
3799
  }
3549
- }
3550
- return lines;
3551
- };
3552
- var calculateOrthogonalPath = (from, to) => {
3553
- const fx = Math.round(from.x);
3554
- const fy = Math.round(from.y);
3555
- const tx = Math.round(to.x);
3556
- const ty = Math.round(to.y);
3557
- if (fy === ty) {
3558
- return `M ${fx} ${fy} H ${tx}`;
3559
- }
3560
- const C = 2;
3561
- const goingDown = ty > fy;
3562
- const goingRight = tx >= fx;
3563
- const dirY = goingDown ? 1 : -1;
3564
- const dirX = goingRight ? 1 : -1;
3565
- if (Math.abs(ty - fy) >= C && Math.abs(tx - fx) >= C) {
3566
- return [
3567
- `M ${fx} ${fy}`,
3568
- `H ${tx - dirX * C}`,
3569
- `L ${tx} ${fy + dirY * C}`,
3570
- `V ${ty}`
3571
- ].join(" ");
3572
- }
3573
- return `M ${fx} ${fy} H ${tx} V ${ty}`;
3800
+ );
3574
3801
  };
3802
+ var TimeScaleHeader_default = TimeScaleHeader;
3803
+
3804
+ // src/components/TaskRow/TaskRow.tsx
3805
+ var import_react3 = __toESM(require("react"));
3806
+ var import_react_dom = require("react-dom");
3575
3807
 
3576
3808
  // src/utils/expired.ts
3577
3809
  var isTaskExpired = (task, referenceDate = /* @__PURE__ */ new Date()) => {
@@ -4412,18 +4644,243 @@ var useTaskDrag = (options) => {
4412
4644
  };
4413
4645
  };
4414
4646
 
4647
+ // src/components/GanttChart/previewStore.ts
4648
+ function positionsEqual(left, right) {
4649
+ return left?.left === right?.left && left?.width === right?.width;
4650
+ }
4651
+ function createTaskPreviewPositionStore() {
4652
+ let positions = /* @__PURE__ */ new Map();
4653
+ const listenersByTaskId = /* @__PURE__ */ new Map();
4654
+ function notify(taskIds) {
4655
+ for (const taskId of taskIds) {
4656
+ const listeners = listenersByTaskId.get(taskId);
4657
+ if (!listeners) continue;
4658
+ for (const listener of listeners) {
4659
+ listener();
4660
+ }
4661
+ }
4662
+ }
4663
+ return {
4664
+ subscribeTask(taskId, listener) {
4665
+ const listeners = listenersByTaskId.get(taskId) ?? /* @__PURE__ */ new Set();
4666
+ listeners.add(listener);
4667
+ listenersByTaskId.set(taskId, listeners);
4668
+ return () => {
4669
+ listeners.delete(listener);
4670
+ if (listeners.size === 0) {
4671
+ listenersByTaskId.delete(taskId);
4672
+ }
4673
+ };
4674
+ },
4675
+ getTaskPosition(taskId) {
4676
+ return positions.get(taskId);
4677
+ },
4678
+ setPositions(nextPositions) {
4679
+ const changedTaskIds = /* @__PURE__ */ new Set();
4680
+ const mergedPositions = /* @__PURE__ */ new Map();
4681
+ for (const [taskId, nextPosition] of nextPositions) {
4682
+ const currentPosition = positions.get(taskId);
4683
+ if (positionsEqual(currentPosition, nextPosition)) {
4684
+ if (currentPosition) {
4685
+ mergedPositions.set(taskId, currentPosition);
4686
+ }
4687
+ continue;
4688
+ }
4689
+ changedTaskIds.add(taskId);
4690
+ mergedPositions.set(taskId, {
4691
+ left: nextPosition.left,
4692
+ width: nextPosition.width
4693
+ });
4694
+ }
4695
+ for (const taskId of positions.keys()) {
4696
+ if (!nextPositions.has(taskId)) {
4697
+ changedTaskIds.add(taskId);
4698
+ }
4699
+ }
4700
+ if (changedTaskIds.size === 0) {
4701
+ return;
4702
+ }
4703
+ positions = mergedPositions;
4704
+ notify(changedTaskIds);
4705
+ },
4706
+ clear() {
4707
+ if (positions.size === 0) {
4708
+ return;
4709
+ }
4710
+ const changedTaskIds = new Set(positions.keys());
4711
+ positions = /* @__PURE__ */ new Map();
4712
+ notify(changedTaskIds);
4713
+ }
4714
+ };
4715
+ }
4716
+ function createActivityPreviewStore() {
4717
+ let overridesByTask = /* @__PURE__ */ new Map();
4718
+ const listenersByTaskId = /* @__PURE__ */ new Map();
4719
+ function notify(taskIds) {
4720
+ for (const taskId of taskIds) {
4721
+ for (const listener of listenersByTaskId.get(taskId) ?? []) {
4722
+ listener();
4723
+ }
4724
+ }
4725
+ }
4726
+ return {
4727
+ subscribeTask(taskId, listener) {
4728
+ const listeners = listenersByTaskId.get(taskId) ?? /* @__PURE__ */ new Set();
4729
+ listeners.add(listener);
4730
+ listenersByTaskId.set(taskId, listeners);
4731
+ return () => {
4732
+ listeners.delete(listener);
4733
+ if (listeners.size === 0) {
4734
+ listenersByTaskId.delete(taskId);
4735
+ }
4736
+ };
4737
+ },
4738
+ getTaskOverrides(taskId) {
4739
+ return overridesByTask.get(taskId);
4740
+ },
4741
+ setOverrides(nextOverrides) {
4742
+ const changedTaskIds = new Set(nextOverrides.keys());
4743
+ for (const taskId of overridesByTask.keys()) {
4744
+ changedTaskIds.add(taskId);
4745
+ }
4746
+ if ([...changedTaskIds].every((taskId) => {
4747
+ const current = overridesByTask.get(taskId);
4748
+ const next = nextOverrides.get(taskId);
4749
+ if (!current || !next || current.size !== next.size) return false;
4750
+ return [...next].every(([activityId, delta]) => current.get(activityId) === delta);
4751
+ })) {
4752
+ return;
4753
+ }
4754
+ overridesByTask = new Map([...nextOverrides].map(([taskId, deltas]) => [taskId, new Map(deltas)]));
4755
+ notify(changedTaskIds);
4756
+ },
4757
+ clear() {
4758
+ if (overridesByTask.size === 0) {
4759
+ return;
4760
+ }
4761
+ const changedTaskIds = new Set(overridesByTask.keys());
4762
+ overridesByTask = /* @__PURE__ */ new Map();
4763
+ notify(changedTaskIds);
4764
+ }
4765
+ };
4766
+ }
4767
+ function createActivityBlockStore() {
4768
+ let blockersByTask = /* @__PURE__ */ new Map();
4769
+ const listenersByTaskId = /* @__PURE__ */ new Map();
4770
+ function notify(taskIds) {
4771
+ for (const taskId of taskIds) {
4772
+ for (const listener of listenersByTaskId.get(taskId) ?? []) {
4773
+ listener();
4774
+ }
4775
+ }
4776
+ }
4777
+ return {
4778
+ subscribeTask(taskId, listener) {
4779
+ const listeners = listenersByTaskId.get(taskId) ?? /* @__PURE__ */ new Set();
4780
+ listeners.add(listener);
4781
+ listenersByTaskId.set(taskId, listeners);
4782
+ return () => {
4783
+ listeners.delete(listener);
4784
+ if (listeners.size === 0) {
4785
+ listenersByTaskId.delete(taskId);
4786
+ }
4787
+ };
4788
+ },
4789
+ getTaskBlockers(taskId) {
4790
+ return blockersByTask.get(taskId);
4791
+ },
4792
+ setBlockers(nextBlockers) {
4793
+ const changedTaskIds = new Set(nextBlockers.keys());
4794
+ for (const taskId of blockersByTask.keys()) {
4795
+ changedTaskIds.add(taskId);
4796
+ }
4797
+ if ([...changedTaskIds].every((taskId) => {
4798
+ const current = blockersByTask.get(taskId);
4799
+ const next = nextBlockers.get(taskId);
4800
+ if (!current || !next || current.size !== next.size) return false;
4801
+ return [...next].every((activityId) => current.has(activityId));
4802
+ })) {
4803
+ return;
4804
+ }
4805
+ blockersByTask = new Map([...nextBlockers].map(([taskId, ids]) => [taskId, new Set(ids)]));
4806
+ notify(changedTaskIds);
4807
+ },
4808
+ clear() {
4809
+ if (blockersByTask.size === 0) {
4810
+ return;
4811
+ }
4812
+ const changedTaskIds = new Set(blockersByTask.keys());
4813
+ blockersByTask = /* @__PURE__ */ new Map();
4814
+ notify(changedTaskIds);
4815
+ }
4816
+ };
4817
+ }
4818
+ var activityOwnerKey = (taskId, activityId) => `${taskId}\0${activityId}`;
4819
+ function createActivityDragOwner() {
4820
+ let ownerKey = null;
4821
+ return {
4822
+ get: () => ownerKey,
4823
+ set: (nextOwnerKey) => {
4824
+ ownerKey = nextOwnerKey;
4825
+ }
4826
+ };
4827
+ }
4828
+ function createActivityActivationStore() {
4829
+ let activeKey = null;
4830
+ const listeners = /* @__PURE__ */ new Set();
4831
+ return {
4832
+ subscribe(listener) {
4833
+ listeners.add(listener);
4834
+ return () => {
4835
+ listeners.delete(listener);
4836
+ };
4837
+ },
4838
+ getActiveKey: () => activeKey,
4839
+ setActiveKey(nextKey) {
4840
+ if (activeKey === nextKey) return;
4841
+ activeKey = nextKey;
4842
+ for (const listener of listeners) listener();
4843
+ }
4844
+ };
4845
+ }
4846
+
4415
4847
  // src/components/TaskRow/TaskRow.tsx
4416
4848
  var import_jsx_runtime2 = require("react/jsx-runtime");
4849
+ var COMPOSITE_SEGMENT_CUT_MIN_PX = 10;
4850
+ var toIsoDay = (date) => date.toISOString().slice(0, 10);
4851
+ var shiftActivityDay = (value, days) => new Date(parseUTCDate(value).getTime() + days * DAY_MS);
4852
+ var activityDurationDays = (activity) => Math.max(1, Math.round((parseUTCDate(activity.endDate).getTime() - parseUTCDate(activity.startDate).getTime()) / DAY_MS) + 1);
4853
+ var ACTIVITY_TEXT_LUMINANCE_THRESHOLD = 0.55;
4854
+ var relativeLuminance = (hex) => {
4855
+ const channels = [1, 3, 5].map((index) => {
4856
+ const value = parseInt(hex.slice(index, index + 2), 16) / 255;
4857
+ return value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
4858
+ });
4859
+ return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2];
4860
+ };
4861
+ var activityTextColor = (background) => {
4862
+ const lightText = "var(--gantt-task-bar-text-color, #ffffff)";
4863
+ if (!background) return lightText;
4864
+ const value = background.trim();
4865
+ if (value.startsWith("#")) {
4866
+ return relativeLuminance(value) > ACTIVITY_TEXT_LUMINANCE_THRESHOLD ? "#111111" : "#ffffff";
4867
+ }
4868
+ const oklch = /oklch\(\s*([0-9.]+)/i.exec(value);
4869
+ if (oklch) {
4870
+ return Number(oklch[1]) > 0.66 ? "#111111" : "#ffffff";
4871
+ }
4872
+ return lightText;
4873
+ };
4417
4874
  var formatCompositePreviewDate = (date) => {
4418
4875
  const month = new Intl.DateTimeFormat("ru-RU", { month: "short", timeZone: "UTC" }).format(date).replace(/\.$/, "");
4419
4876
  const yearSuffix = date.getUTCFullYear() === (/* @__PURE__ */ new Date()).getFullYear() ? "" : ` ${String(date.getUTCFullYear()).slice(-2)}`;
4420
4877
  return `${date.getUTCDate()} ${month}${yearSuffix}`;
4421
4878
  };
4422
4879
  var arePropsEqual = (prevProps, nextProps) => {
4423
- return prevProps.task.id === nextProps.task.id && prevProps.task.name === nextProps.task.name && prevProps.task.startDate === nextProps.task.startDate && prevProps.task.endDate === nextProps.task.endDate && prevProps.task.baselineStartDate === nextProps.task.baselineStartDate && prevProps.task.baselineEndDate === nextProps.task.baselineEndDate && prevProps.task.type === nextProps.task.type && prevProps.task.color === nextProps.task.color && prevProps.task.progress === nextProps.task.progress && prevProps.task.accepted === nextProps.task.accepted && prevProps.monthStart.getTime() === nextProps.monthStart.getTime() && prevProps.dayWidth === nextProps.dayWidth && prevProps.rowHeight === nextProps.rowHeight && prevProps.overridePosition?.left === nextProps.overridePosition?.left && prevProps.overridePosition?.width === nextProps.overridePosition?.width && prevProps.previewPositionStore === nextProps.previewPositionStore && prevProps.allTasks === nextProps.allTasks && prevProps.disableConstraints === nextProps.disableConstraints && prevProps.deferCascadePreview === nextProps.deferCascadePreview && prevProps.task.locked === nextProps.task.locked && prevProps.task.synced === nextProps.task.synced && prevProps.task.divider === nextProps.task.divider && prevProps.highlightExpiredTasks === nextProps.highlightExpiredTasks && prevProps.isCritical === nextProps.isCritical && prevProps.showBaseline === nextProps.showBaseline && prevProps.onCompositeToggle === nextProps.onCompositeToggle && prevProps.compactDetail === nextProps.compactDetail && prevProps.compositeParentColor === nextProps.compositeParentColor && prevProps.compositeExpanded === nextProps.compositeExpanded && prevProps.isFilterMatch === nextProps.isFilterMatch && prevProps.businessDays === nextProps.businessDays && prevProps.customDays === nextProps.customDays && prevProps.isWeekend === nextProps.isWeekend && prevProps.disableTaskDrag === nextProps.disableTaskDrag && prevProps.disableDependencyEditing === nextProps.disableDependencyEditing && prevProps.isDependencyDragActive === nextProps.isDependencyDragActive && prevProps.viewMode === nextProps.viewMode && prevProps.showTaskDateLabels === nextProps.showTaskDateLabels && prevProps.showTaskNames === nextProps.showTaskNames;
4880
+ return prevProps.task.id === nextProps.task.id && prevProps.task.name === nextProps.task.name && prevProps.task.startDate === nextProps.task.startDate && prevProps.task.endDate === nextProps.task.endDate && prevProps.task.baselineStartDate === nextProps.task.baselineStartDate && prevProps.task.baselineEndDate === nextProps.task.baselineEndDate && prevProps.task.type === nextProps.task.type && prevProps.task.color === nextProps.task.color && prevProps.task.progress === nextProps.task.progress && prevProps.task.accepted === nextProps.task.accepted && prevProps.task.activities === nextProps.task.activities && prevProps.monthStart.getTime() === nextProps.monthStart.getTime() && prevProps.dayWidth === nextProps.dayWidth && prevProps.rowHeight === nextProps.rowHeight && prevProps.overridePosition?.left === nextProps.overridePosition?.left && prevProps.overridePosition?.width === nextProps.overridePosition?.width && prevProps.previewPositionStore === nextProps.previewPositionStore && prevProps.activityPreviewStore === nextProps.activityPreviewStore && prevProps.activityBlockStore === nextProps.activityBlockStore && prevProps.activityDragOwner === nextProps.activityDragOwner && prevProps.activityActivationStore === nextProps.activityActivationStore && prevProps.activityClickToDrag === nextProps.activityClickToDrag && prevProps.activityActivationMode === nextProps.activityActivationMode && prevProps.showActivityLinks === nextProps.showActivityLinks && prevProps.showActivityLag === nextProps.showActivityLag && prevProps.horizontalWindow?.startPx === nextProps.horizontalWindow?.startPx && prevProps.horizontalWindow?.endPx === nextProps.horizontalWindow?.endPx && prevProps.allTasks === nextProps.allTasks && prevProps.disableConstraints === nextProps.disableConstraints && prevProps.deferCascadePreview === nextProps.deferCascadePreview && prevProps.task.locked === nextProps.task.locked && prevProps.task.synced === nextProps.task.synced && prevProps.task.divider === nextProps.task.divider && prevProps.highlightExpiredTasks === nextProps.highlightExpiredTasks && prevProps.isCritical === nextProps.isCritical && prevProps.showBaseline === nextProps.showBaseline && prevProps.onCompositeToggle === nextProps.onCompositeToggle && prevProps.compactDetail === nextProps.compactDetail && prevProps.compositeParentColor === nextProps.compositeParentColor && prevProps.compositeExpanded === nextProps.compositeExpanded && prevProps.isFilterMatch === nextProps.isFilterMatch && prevProps.businessDays === nextProps.businessDays && prevProps.customDays === nextProps.customDays && prevProps.isWeekend === nextProps.isWeekend && prevProps.disableTaskDrag === nextProps.disableTaskDrag && prevProps.disableDependencyEditing === nextProps.disableDependencyEditing && prevProps.isDependencyDragActive === nextProps.isDependencyDragActive && prevProps.viewMode === nextProps.viewMode && prevProps.showTaskDateLabels === nextProps.showTaskDateLabels && prevProps.showTaskNames === nextProps.showTaskNames && prevProps.showCompositeSegments === nextProps.showCompositeSegments;
4424
4881
  };
4425
4882
  var TaskRow = import_react3.default.memo(
4426
- ({ task, monthStart, dayWidth, rowHeight, onTasksChange, onScheduleIntent, onDragStateChange, rowIndex, allTasks, enableAutoSchedule, disableConstraints, deferCascadePreview = false, overridePosition, previewPositionStore, onCascadeProgress, onCascade, divider, highlightExpiredTasks, isCritical = false, showBaseline = false, isFilterMatch = false, businessDays, customDays, isWeekend: isWeekend3, disableTaskDrag = false, disableDependencyEditing = false, onDependencyPortPointerDown, isDependencyDragActive = false, viewMode = "day", showTaskDateLabels = true, showTaskNames = true, onCompositeToggle, compactDetail = false, compositeParentColor, compositeExpanded = false }) => {
4883
+ ({ task, monthStart, dayWidth, rowHeight, onTasksChange, onScheduleIntent, onDragStateChange, rowIndex, allTasks, enableAutoSchedule, disableConstraints, deferCascadePreview = false, overridePosition, previewPositionStore, activityPreviewStore, activityBlockStore, activityDragOwner, activityActivationStore, activityClickToDrag = false, activityActivationMode = "none", showActivityLinks = true, showActivityLag = true, activityTooltip, horizontalWindow, onCascadeProgress, onCascade, divider, highlightExpiredTasks, isCritical = false, showBaseline = false, isFilterMatch = false, businessDays, customDays, isWeekend: isWeekend3, disableTaskDrag = false, disableDependencyEditing = false, onDependencyPortPointerDown, isDependencyDragActive = false, viewMode = "day", showTaskDateLabels = true, showTaskNames = true, showCompositeSegments: showCompositeSegmentsProp = true, onCompositeToggle, compactDetail = false, compositeParentColor, compositeExpanded = false }) => {
4427
4884
  const defaultParentBarColor = "#782FC4";
4428
4885
  const [showCompositePreview, setShowCompositePreview] = (0, import_react3.useState)(false);
4429
4886
  const [compositePreviewPosition, setCompositePreviewPosition] = (0, import_react3.useState)(null);
@@ -4594,35 +5051,380 @@ var TaskRow = import_react3.default.memo(
4594
5051
  const centerX = Math.round(displayLeft + dayWidth / 2);
4595
5052
  const halfSize = Math.round(milestoneGeometry.size / 2);
4596
5053
  return {
4597
- centerX,
4598
- left: centerX - halfSize,
4599
- right: centerX + halfSize,
4600
- size: milestoneGeometry.size
5054
+ centerX,
5055
+ left: centerX - halfSize,
5056
+ right: centerX + halfSize,
5057
+ size: milestoneGeometry.size
5058
+ };
5059
+ }, [displayLeft, dayWidth, milestoneGeometry.size]);
5060
+ const visualLeft = milestone ? displayMilestoneGeometry.left : displayLeft;
5061
+ const visualWidth = milestone ? displayMilestoneGeometry.size : displayWidth;
5062
+ const compositeSegments = (0, import_react3.useMemo)(() => {
5063
+ if (!task.composite || width <= 0) return [];
5064
+ const laneEnds = [];
5065
+ const segments2 = compositeChildren.map((child) => {
5066
+ const segment = calculateTaskBar(parseUTCDate(child.startDate), parseUTCDate(child.endDate), monthStart, dayWidth);
5067
+ let lane = Math.max(0, laneEnds.length - 1);
5068
+ if (laneEnds.length > 0 && segment.left < laneEnds[lane]) {
5069
+ lane = laneEnds.length;
5070
+ }
5071
+ laneEnds[lane] = segment.left + segment.width;
5072
+ return {
5073
+ id: child.id,
5074
+ name: child.name,
5075
+ left: (segment.left - left) / width * 100,
5076
+ width: segment.width / width * 100,
5077
+ lane,
5078
+ pxLeft: segment.left - left,
5079
+ cut: false
5080
+ };
5081
+ });
5082
+ const lastCutByLane = [];
5083
+ for (const segment of [...segments2].sort((a, b) => a.pxLeft - b.pxLeft)) {
5084
+ segment.cut = segment.pxLeft >= 1 && segment.pxLeft - (lastCutByLane[segment.lane] ?? 0) >= COMPOSITE_SEGMENT_CUT_MIN_PX;
5085
+ if (segment.cut) lastCutByLane[segment.lane] = segment.pxLeft;
5086
+ }
5087
+ return segments2;
5088
+ }, [task.composite, compositeChildren, monthStart, dayWidth, left, width, displayLeft]);
5089
+ const compositeLaneCount = Math.max(1, ...compositeSegments.map((segment) => segment.lane + 1));
5090
+ const shouldRenderCompositeSegments = showCompositeSegmentsProp && task.composite && compositeSegments.length > 0 && visualWidth / compositeSegments.length >= 22;
5091
+ const shouldRenderBaseline = showBaseline && baselineGeometry !== null;
5092
+ const hasPreviewPosition = isDragging || effectiveOverridePosition !== void 0;
5093
+ const activityLayout = (0, import_react3.useMemo)(
5094
+ () => task.activities && task.activities.length > 0 ? computeActivityLanes(task.activities, monthStart, dayWidth) : null,
5095
+ [task.activities, monthStart, dayWidth]
5096
+ );
5097
+ const renderActivities = activityLayout !== null && !compactDetail;
5098
+ const activityTopOffset = activityLayout ? Math.max(0, (rowHeight - activityLayout.laneCount * ACTIVITY_LANE_STEP) / 2) : 0;
5099
+ const activityDragEnabled = !task.locked && !disableTaskDrag;
5100
+ const [activityDrag, setActivityDrag] = (0, import_react3.useState)(null);
5101
+ const [activityTipId, setActivityTipId] = (0, import_react3.useState)(null);
5102
+ const [activityTipPosition, setActivityTipPosition] = (0, import_react3.useState)(null);
5103
+ const activityTipAnchor = (0, import_react3.useRef)(null);
5104
+ const activityDragStart = (0, import_react3.useRef)(null);
5105
+ const activityConstraintRef = (0, import_react3.useRef)(null);
5106
+ const [activityBlockedBy, setActivityBlockedBy] = (0, import_react3.useState)(null);
5107
+ const visibleActivitySegments = (0, import_react3.useMemo)(() => {
5108
+ if (!activityLayout) return [];
5109
+ const window2 = horizontalWindow;
5110
+ if (!window2) return activityLayout.segments;
5111
+ const draggedId = activityDrag?.id;
5112
+ return activityLayout.segments.filter((segment) => segment.id === draggedId || segment.left <= window2.endPx && segment.left + segment.width >= window2.startPx);
5113
+ }, [activityLayout, horizontalWindow, activityDrag?.id]);
5114
+ const subscribeActivityOverrides = (0, import_react3.useCallback)(
5115
+ (listener) => activityPreviewStore?.subscribeTask(task.id, listener) ?? (() => {
5116
+ }),
5117
+ [activityPreviewStore, task.id]
5118
+ );
5119
+ const getActivityOverridesSnapshot = (0, import_react3.useCallback)(
5120
+ () => activityPreviewStore?.getTaskOverrides(task.id),
5121
+ [activityPreviewStore, task.id]
5122
+ );
5123
+ const activityOverrides = (0, import_react3.useSyncExternalStore)(
5124
+ subscribeActivityOverrides,
5125
+ getActivityOverridesSnapshot,
5126
+ () => void 0
5127
+ );
5128
+ const subscribeActivityBlock = (0, import_react3.useCallback)(
5129
+ (listener) => activityBlockStore?.subscribeTask(task.id, listener) ?? (() => {
5130
+ }),
5131
+ [activityBlockStore, task.id]
5132
+ );
5133
+ const getActivityBlockSnapshot = (0, import_react3.useCallback)(
5134
+ () => activityBlockStore?.getTaskBlockers(task.id),
5135
+ [activityBlockStore, task.id]
5136
+ );
5137
+ const blockedActivityIds = (0, import_react3.useSyncExternalStore)(
5138
+ subscribeActivityBlock,
5139
+ getActivityBlockSnapshot,
5140
+ () => void 0
5141
+ );
5142
+ const subscribeActivation = (0, import_react3.useCallback)(
5143
+ (listener) => activityActivationStore?.subscribe(listener) ?? (() => {
5144
+ }),
5145
+ [activityActivationStore]
5146
+ );
5147
+ const getActivationSnapshot = (0, import_react3.useCallback)(
5148
+ () => activityActivationStore?.getActiveKey() ?? null,
5149
+ [activityActivationStore]
5150
+ );
5151
+ const activeActivityKey = (0, import_react3.useSyncExternalStore)(
5152
+ subscribeActivation,
5153
+ getActivationSnapshot,
5154
+ () => null
5155
+ );
5156
+ const activeWork = (0, import_react3.useMemo)(() => {
5157
+ if (!activeActivityKey) return null;
5158
+ const [activeTaskId, activeActivityId] = activeActivityKey.split("\0");
5159
+ const source = (allTasks ?? []).find((candidate) => candidate.id === activeTaskId);
5160
+ const activity = source?.activities?.find((candidate) => candidate.id === activeActivityId);
5161
+ return { id: activeActivityId, name: activity?.name ?? null };
5162
+ }, [activeActivityKey, allTasks]);
5163
+ const activationVisualEnabled = activityClickToDrag || activityActivationMode !== "none";
5164
+ const dimOthers = activityActivationMode === "dim";
5165
+ const highlightChain = activityActivationMode === "highlight";
5166
+ const chainEndDeltaDays = (mode, dayDelta, durationDays2) => {
5167
+ if (mode === "resize-left") return 0;
5168
+ if (mode === "resize-right") return Math.max(dayDelta, -(durationDays2 - 1));
5169
+ return dayDelta;
5170
+ };
5171
+ const publishChainPreview = (0, import_react3.useCallback)((draggedId, mode, dayDelta) => {
5172
+ if (!activityPreviewStore) return;
5173
+ if (!task.activityChain) {
5174
+ activityPreviewStore.clear();
5175
+ return;
5176
+ }
5177
+ let shifts;
5178
+ if (task.activityChain === "push") {
5179
+ let delta = dayDelta;
5180
+ if (mode !== "resize-right") {
5181
+ const minDelta = activityConstraintRef.current?.minStartDelta ?? null;
5182
+ if (minDelta !== null) delta = Math.max(delta, minDelta);
5183
+ }
5184
+ shifts = delta === 0 ? [] : pushActivityChain(allTasks ?? [], task.id, draggedId, delta).shifts;
5185
+ } else {
5186
+ const endDelta = chainEndDeltaDays(mode, dayDelta, activityDragStart.current?.durationDays ?? 1);
5187
+ shifts = endDelta === 0 ? [] : shiftActivityChain(allTasks ?? [], task.id, draggedId, endDelta);
5188
+ }
5189
+ if (shifts.length === 0) {
5190
+ activityPreviewStore.clear();
5191
+ return;
5192
+ }
5193
+ const overrides = /* @__PURE__ */ new Map();
5194
+ for (const shift of shifts) {
5195
+ const deltas = /* @__PURE__ */ new Map();
5196
+ for (const [id, d] of shift.deltas) {
5197
+ if (d !== 0) deltas.set(id, d);
5198
+ }
5199
+ if (deltas.size > 0) overrides.set(shift.taskId, deltas);
5200
+ }
5201
+ activityPreviewStore.setOverrides(overrides);
5202
+ }, [activityPreviewStore, allTasks, task.activityChain, task.id]);
5203
+ (0, import_react3.useEffect)(() => {
5204
+ if (!activityDrag) return;
5205
+ const startInfo = activityDragStart.current;
5206
+ if (!startInfo) return;
5207
+ const publishBlockers = (blocked) => {
5208
+ const constraint = activityConstraintRef.current;
5209
+ if (!activityBlockStore) return;
5210
+ if (!blocked || !constraint) {
5211
+ activityBlockStore.clear();
5212
+ setActivityBlockedBy((current) => current === null ? current : null);
5213
+ return;
5214
+ }
5215
+ const blockers = /* @__PURE__ */ new Map();
5216
+ const names = [];
5217
+ for (const blocker of constraint.blockers) {
5218
+ const set = blockers.get(blocker.taskId) ?? /* @__PURE__ */ new Set();
5219
+ set.add(blocker.activityId);
5220
+ blockers.set(blocker.taskId, set);
5221
+ const source = (allTasks ?? []).find((candidate) => candidate.id === blocker.taskId);
5222
+ const work = source?.activities?.find((activity) => activity.id === blocker.activityId);
5223
+ const workName = work?.name ?? source?.name ?? blocker.activityId;
5224
+ names.push(blocker.taskId === task.id ? workName : `${source?.name ?? blocker.taskId} \xB7 ${workName}`);
5225
+ }
5226
+ activityBlockStore.setBlockers(blockers);
5227
+ setActivityBlockedBy((current) => current && current.lag === constraint.lag && current.names.length === names.length && current.names.every((name, index) => name === names[index]) ? current : { names, lag: constraint.lag });
5228
+ };
5229
+ const onMove = (event) => {
5230
+ const rawDelta = Math.round((event.clientX - startInfo.x) / dayWidth);
5231
+ let requested = rawDelta;
5232
+ if (activityDrag.mode === "resize-left") requested = Math.min(rawDelta, startInfo.durationDays - 1);
5233
+ if (activityDrag.mode === "resize-right") requested = Math.max(rawDelta, -(startInfo.durationDays - 1));
5234
+ const constraint = activityConstraintRef.current;
5235
+ let clamped = requested;
5236
+ let blocked = false;
5237
+ if (constraint && activityDrag.mode !== "resize-right" && requested < constraint.minStartDelta) {
5238
+ clamped = constraint.minStartDelta;
5239
+ blocked = true;
5240
+ }
5241
+ setActivityDrag((current) => current ? { ...current, dayDelta: clamped } : current);
5242
+ publishBlockers(blocked);
5243
+ const anchor = activityTipAnchor.current;
5244
+ if (anchor) {
5245
+ const shiftPx = activityDrag.mode === "resize-right" ? 0 : clamped * dayWidth;
5246
+ setActivityTipPosition({
5247
+ left: Math.max(8, Math.min(anchor.left + shiftPx, window.innerWidth - 160)),
5248
+ top: anchor.top,
5249
+ below: anchor.below
5250
+ });
5251
+ }
5252
+ publishChainPreview(activityDrag.id, activityDrag.mode, clamped);
5253
+ };
5254
+ const onUp = () => {
5255
+ activityPreviewStore?.clear();
5256
+ activityBlockStore?.clear();
5257
+ activityDragOwner?.set(null);
5258
+ setActivityBlockedBy(null);
5259
+ setActivityTipId(null);
5260
+ activityTipAnchor.current = null;
5261
+ setActivityTipPosition(null);
5262
+ if (activityDrag.dayDelta !== 0) {
5263
+ const chainPush = task.activityChain === "push";
5264
+ let delta = activityDrag.dayDelta;
5265
+ if (chainPush && activityDrag.mode !== "resize-right") {
5266
+ const minDelta = activityConstraintRef.current?.minStartDelta ?? null;
5267
+ if (minDelta !== null) delta = Math.max(delta, minDelta);
5268
+ }
5269
+ const clampLeft = Math.min(delta, startInfo.durationDays - 1);
5270
+ const clampRight = Math.max(delta, -(startInfo.durationDays - 1));
5271
+ const updatedActivities = (task.activities ?? []).map((activity) => {
5272
+ if (activity.id !== activityDrag.id) return activity;
5273
+ if (activityDrag.mode === "move") {
5274
+ return {
5275
+ ...activity,
5276
+ startDate: toIsoDay(shiftActivityDay(activity.startDate, delta)),
5277
+ endDate: toIsoDay(shiftActivityDay(activity.endDate, delta))
5278
+ };
5279
+ }
5280
+ if (activityDrag.mode === "resize-left") {
5281
+ return { ...activity, startDate: toIsoDay(shiftActivityDay(activity.startDate, clampLeft)) };
5282
+ }
5283
+ return { ...activity, endDate: toIsoDay(shiftActivityDay(activity.endDate, clampRight)) };
5284
+ });
5285
+ const chainShifts = chainPush ? delta !== 0 ? pushActivityChain(allTasks ?? [], task.id, activityDrag.id, delta).shifts : [] : (() => {
5286
+ const endDeltaDays = chainEndDeltaDays(activityDrag.mode, delta, startInfo.durationDays);
5287
+ return endDeltaDays !== 0 ? shiftActivityChain(allTasks ?? [], task.id, activityDrag.id, endDeltaDays) : [];
5288
+ })();
5289
+ const changedTasks = [];
5290
+ let hostHandled = false;
5291
+ for (const shift of chainShifts) {
5292
+ if (shift.taskId === task.id) {
5293
+ hostHandled = true;
5294
+ changedTasks.push({
5295
+ ...normalizedTask,
5296
+ activities: shift.activities.map((activity) => activity.id === activityDrag.id ? updatedActivities.find((item) => item.id === activityDrag.id) ?? activity : activity)
5297
+ });
5298
+ } else {
5299
+ const source = (allTasks ?? []).find((candidate) => candidate.id === shift.taskId);
5300
+ changedTasks.push({ ...source ?? { id: shift.taskId }, activities: shift.activities });
5301
+ }
5302
+ }
5303
+ if (!hostHandled) {
5304
+ changedTasks.unshift({ ...normalizedTask, activities: updatedActivities });
5305
+ }
5306
+ onTasksChange?.(changedTasks);
5307
+ }
5308
+ setActivityDrag(null);
5309
+ activityDragStart.current = null;
5310
+ activityConstraintRef.current = null;
5311
+ document.body.style.cursor = "";
5312
+ };
5313
+ document.body.style.cursor = activityDrag.mode === "move" ? "grabbing" : "ew-resize";
5314
+ window.addEventListener("mousemove", onMove);
5315
+ window.addEventListener("mouseup", onUp);
5316
+ return () => {
5317
+ window.removeEventListener("mousemove", onMove);
5318
+ window.removeEventListener("mouseup", onUp);
5319
+ document.body.style.cursor = "";
5320
+ };
5321
+ }, [activityDrag, activityPreviewStore, activityBlockStore, activityDragOwner, allTasks, dayWidth, normalizedTask, onTasksChange, publishChainPreview, task.activities]);
5322
+ const anchorActivityTip = (rect) => {
5323
+ const below = rect.top < 34;
5324
+ const anchor = {
5325
+ left: Math.max(8, Math.min(rect.left, window.innerWidth - 160)),
5326
+ // Вплотную к полосе (1px зазора), сверху или снизу при нехватке места.
5327
+ top: below ? rect.bottom + 1 : rect.top - 1,
5328
+ below
5329
+ };
5330
+ activityTipAnchor.current = anchor;
5331
+ return anchor;
5332
+ };
5333
+ const handleActivityPointerDown = (segment, mode) => (event) => {
5334
+ if (!activityDragEnabled) return;
5335
+ event.preventDefault();
5336
+ event.stopPropagation();
5337
+ activityDragStart.current = {
5338
+ x: event.clientX,
5339
+ durationDays: Math.max(1, Math.round(segment.width / dayWidth) - 1)
5340
+ };
5341
+ activityConstraintRef.current = task.activityChain === "push" && mode !== "resize-right" ? activityStartConstraint(allTasks ?? [], task.id, segment.id) : null;
5342
+ activityDragOwner?.set(activityOwnerKey(task.id, segment.id));
5343
+ setActivityBlockedBy(null);
5344
+ setActivityTipPosition(anchorActivityTip(event.currentTarget.getBoundingClientRect()));
5345
+ setActivityTipId(segment.id);
5346
+ setActivityDrag({ id: segment.id, mode, dayDelta: 0 });
5347
+ };
5348
+ const liveActivityGeometry = (segment) => {
5349
+ if (!activityDrag || activityDrag.id !== segment.id || activityDrag.dayDelta === 0) return segment;
5350
+ if (activityDrag.mode === "move") {
5351
+ return { left: segment.left + activityDrag.dayDelta * dayWidth, width: segment.width };
5352
+ }
5353
+ if (activityDrag.mode === "resize-left") {
5354
+ return { left: segment.left + activityDrag.dayDelta * dayWidth, width: segment.width - activityDrag.dayDelta * dayWidth };
5355
+ }
5356
+ return { left: segment.left, width: segment.width + activityDrag.dayDelta * dayWidth };
5357
+ };
5358
+ const activityLinks = (0, import_react3.useMemo)(() => {
5359
+ const gaps = [];
5360
+ const elbows = [];
5361
+ if (!showActivityLinks || !renderActivities || !activityLayout) return { gaps, elbows };
5362
+ const activities = task.activities ?? [];
5363
+ const segmentById = new Map(activityLayout.segments.map((segment) => [segment.id, segment]));
5364
+ const visibleIds = new Set(visibleActivitySegments.map((segment) => segment.id));
5365
+ const laneTopY = (lane) => activityTopOffset + lane * ACTIVITY_LANE_STEP + (ACTIVITY_LANE_STEP - ACTIVITY_LANE_BAR_HEIGHT) / 2;
5366
+ const liveRect = (segment) => {
5367
+ const geometry = liveActivityGeometry(segment);
5368
+ const chainDelta = activityOverrides?.get(segment.id) ?? 0;
5369
+ const left2 = geometry.left + (chainDelta !== 0 ? chainDelta * dayWidth : 0);
5370
+ return { left: left2, right: left2 + geometry.width };
5371
+ };
5372
+ for (let index = 0; index < activities.length - 1; index += 1) {
5373
+ const predecessor = segmentById.get(activities[index].id);
5374
+ const successor = segmentById.get(activities[index + 1].id);
5375
+ if (!predecessor || !successor) continue;
5376
+ if (!visibleIds.has(predecessor.id) || !visibleIds.has(successor.id)) continue;
5377
+ const lag = activities[index + 1].lag ?? 0;
5378
+ const predRect = liveRect(predecessor);
5379
+ const succRect = liveRect(successor);
5380
+ if (predecessor.lane === successor.lane && succRect.left >= predRect.right) {
5381
+ const width2 = succRect.left - predRect.right;
5382
+ if (width2 >= 1) {
5383
+ gaps.push({ key: `${predecessor.id}->${successor.id}`, left: predRect.right, width: width2, lane: predecessor.lane, lag });
5384
+ }
5385
+ continue;
5386
+ }
5387
+ const predecessorLaneTop = laneTopY(predecessor.lane);
5388
+ const successorLaneTop = laneTopY(successor.lane);
5389
+ const sameLane = predecessor.lane === successor.lane;
5390
+ const reverseOrder = !sameLane && successorLaneTop < predecessorLaneTop;
5391
+ const fromY = reverseOrder ? predecessorLaneTop + 6 : predecessorLaneTop + ACTIVITY_LANE_BAR_HEIGHT - 6;
5392
+ const toY = reverseOrder ? successorLaneTop + ACTIVITY_LANE_BAR_HEIGHT - 6 : successorLaneTop + (sameLane ? ACTIVITY_LANE_STEP : 0) + 6;
5393
+ const fromX = predRect.right;
5394
+ const toX = succRect.left;
5395
+ elbows.push({
5396
+ key: `${predecessor.id}->${successor.id}`,
5397
+ d: calculateDependencyPath({ x: fromX, y: fromY }, { x: toX, y: toY }, false),
5398
+ lagX: toX - 14,
5399
+ lagY: reverseOrder ? fromY - 4 : fromY + 12,
5400
+ lag
5401
+ });
5402
+ }
5403
+ return { gaps, elbows };
5404
+ }, [showActivityLinks, renderActivities, activityLayout, visibleActivitySegments, activityTopOffset, activityOverrides, activityDrag, dayWidth, task.activities]);
5405
+ const activityTipData = (0, import_react3.useMemo)(() => {
5406
+ if (!activityTipId || !task.activities) return null;
5407
+ const activity = task.activities.find((item) => item.id === activityTipId);
5408
+ if (!activity) return null;
5409
+ let startDate = parseUTCDate(activity.startDate);
5410
+ let endDate = parseUTCDate(activity.endDate);
5411
+ if (activityDrag && activityDrag.id === activity.id && activityDrag.dayDelta !== 0) {
5412
+ startDate = shiftActivityDay(startDate, activityDrag.dayDelta);
5413
+ endDate = shiftActivityDay(endDate, activityDrag.dayDelta);
5414
+ }
5415
+ return {
5416
+ floor: task.name,
5417
+ name: activity.name,
5418
+ fields: activity.tooltipFields ?? [],
5419
+ context: {
5420
+ task,
5421
+ activity,
5422
+ startDate,
5423
+ endDate,
5424
+ durationDays: activityDurationDays({ startDate, endDate })
5425
+ }
4601
5426
  };
4602
- }, [displayLeft, dayWidth, milestoneGeometry.size]);
4603
- const visualLeft = milestone ? displayMilestoneGeometry.left : displayLeft;
4604
- const visualWidth = milestone ? displayMilestoneGeometry.size : displayWidth;
4605
- const compositeSegments = (0, import_react3.useMemo)(() => {
4606
- if (!task.composite || width <= 0) return [];
4607
- const laneEnds = [];
4608
- return compositeChildren.map((child) => {
4609
- const segment = calculateTaskBar(parseUTCDate(child.startDate), parseUTCDate(child.endDate), monthStart, dayWidth);
4610
- let lane = laneEnds.findIndex((end) => end <= segment.left);
4611
- if (lane < 0) lane = laneEnds.length;
4612
- laneEnds[lane] = segment.left + segment.width;
4613
- return {
4614
- id: child.id,
4615
- name: child.name,
4616
- left: (segment.left - left) / width * 100,
4617
- width: segment.width / width * 100,
4618
- lane
4619
- };
4620
- });
4621
- }, [task.composite, compositeChildren, monthStart, dayWidth, left, width, displayLeft]);
4622
- const compositeLaneCount = Math.max(1, ...compositeSegments.map((segment) => segment.lane + 1));
4623
- const showCompositeSegments = task.composite && compositeSegments.length > 0 && visualWidth / compositeSegments.length >= 22;
4624
- const shouldRenderBaseline = showBaseline && baselineGeometry !== null;
4625
- const hasPreviewPosition = isDragging || effectiveOverridePosition !== void 0;
5427
+ }, [activityDrag, activityTipId, task, task.activities, task.name]);
4626
5428
  const currentStartDate = hasPreviewPosition ? pixelsToDate(displayLeft, monthStart, dayWidth) : taskStartDate;
4627
5429
  const currentEndDate = hasPreviewPosition ? milestone ? pixelsToDate(displayLeft, monthStart, dayWidth) : pixelsToDate(displayLeft + displayWidth - dayWidth, monthStart, dayWidth) : taskEndDate;
4628
5430
  const dateRangeLabel = formatDateRangeLabel(currentStartDate, currentEndDate);
@@ -4655,7 +5457,7 @@ var TaskRow = import_react3.default.memo(
4655
5457
  children: [
4656
5458
  taskDivider === "top" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "gantt-tr-divider gantt-tr-divider-top" }),
4657
5459
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gantt-tr-taskContainer", children: [
4658
- shouldRenderBaseline && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5460
+ shouldRenderBaseline && !renderActivities && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
4659
5461
  "div",
4660
5462
  {
4661
5463
  className: `gantt-tr-baseline ${isParent ? "gantt-tr-baseline-parent" : ""} ${milestone ? "gantt-tr-baseline-milestone" : ""}`,
@@ -4665,7 +5467,114 @@ var TaskRow = import_react3.default.memo(
4665
5467
  }
4666
5468
  }
4667
5469
  ),
4668
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
5470
+ renderActivities && activityLayout && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gantt-tr-activityLanes", children: [
5471
+ activityLinks.elbows.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { className: "gantt-tr-activityLinksSvg", width: "100%", height: "100%", "aria-hidden": "true", children: [
5472
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("marker", { id: "arrowhead", markerWidth: "8", markerHeight: "6", markerUnits: "userSpaceOnUse", refX: "7", refY: "3", orient: "auto", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polygon", { points: "0 0, 8 3, 0 6", fill: "var(--gantt-dependency-line-color, #666666)" }) }) }),
5473
+ activityLinks.elbows.map((link) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { children: [
5474
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { className: "gantt-dependency-path", d: link.d, markerEnd: "url(#arrowhead)" }),
5475
+ showActivityLag && link.lag !== 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5476
+ "text",
5477
+ {
5478
+ className: "gantt-dependency-lag-label",
5479
+ x: link.lagX,
5480
+ y: link.lagY,
5481
+ textAnchor: "middle",
5482
+ fontSize: "10",
5483
+ fill: "var(--gantt-dependency-line-color, #666666)",
5484
+ children: link.lag > 0 ? `+${link.lag}` : `${link.lag}`
5485
+ }
5486
+ )
5487
+ ] }, link.key))
5488
+ ] }),
5489
+ visibleActivitySegments.map((segment) => {
5490
+ const live = liveActivityGeometry(segment);
5491
+ const chainDelta = activityOverrides?.get(segment.id) ?? 0;
5492
+ const liveLeft = live.left + (chainDelta !== 0 ? chainDelta * dayWidth : 0);
5493
+ const isDraggingActivity = activityDrag?.id === segment.id;
5494
+ const isBlockingActivity = blockedActivityIds?.has(segment.id) ?? false;
5495
+ const isAtLimit = isDraggingActivity && activityBlockedBy !== null;
5496
+ const isActiveActivity = activationVisualEnabled && activeActivityKey === activityOwnerKey(task.id, segment.id);
5497
+ const isSameWorkType = activeWork !== null && (segment.id === activeWork.id || activeWork.name !== null && segment.name === activeWork.name);
5498
+ const isDimmed = dimOthers && activeWork !== null && !isSameWorkType;
5499
+ const isChainHighlighted = highlightChain && activeWork !== null && isSameWorkType;
5500
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
5501
+ "div",
5502
+ {
5503
+ "data-activity-id": segment.id,
5504
+ className: `gantt-tr-activityBar${isDraggingActivity ? " gantt-tr-activityBar-dragging" : ""}${isBlockingActivity ? " gantt-tr-activityBar-blocking" : ""}${isAtLimit ? " gantt-tr-activityBar-atLimit" : ""}${isActiveActivity ? " gantt-tr-activityBar-active" : ""}${isDimmed ? " gantt-tr-activityBar-dimmed" : ""}${isChainHighlighted ? " gantt-tr-activityBar-chain" : ""}${activityDragEnabled ? "" : " gantt-tr-activityBar-locked"}`,
5505
+ style: {
5506
+ left: `${liveLeft}px`,
5507
+ width: `${live.width}px`,
5508
+ top: `${activityTopOffset + segment.lane * ACTIVITY_LANE_STEP + (ACTIVITY_LANE_STEP - ACTIVITY_LANE_BAR_HEIGHT) / 2}px`,
5509
+ height: `${ACTIVITY_LANE_BAR_HEIGHT}px`,
5510
+ backgroundColor: segment.color || "var(--gantt-task-bar-default-color)"
5511
+ },
5512
+ onMouseDown: (event) => {
5513
+ if (!activityDragEnabled) return;
5514
+ const activationKey = activityOwnerKey(task.id, segment.id);
5515
+ const isActive = activityActivationStore?.getActiveKey() === activationKey;
5516
+ if (activationVisualEnabled && !isActive) {
5517
+ activityActivationStore?.setActiveKey(activationKey);
5518
+ }
5519
+ if (activityClickToDrag && !isActive) return;
5520
+ const target = event.target;
5521
+ const mode = target.closest(".gantt-tr-resizeHandleLeft") ? "resize-left" : target.closest(".gantt-tr-resizeHandleRight") ? "resize-right" : "move";
5522
+ handleActivityPointerDown(segment, mode)(event);
5523
+ },
5524
+ onMouseEnter: (event) => {
5525
+ if (activityDrag || activityDragOwner?.get()) return;
5526
+ setActivityTipPosition(anchorActivityTip(event.currentTarget.getBoundingClientRect()));
5527
+ setActivityTipId(segment.id);
5528
+ },
5529
+ onMouseLeave: () => {
5530
+ if (!activityDrag) {
5531
+ setActivityTipId((current) => current === segment.id ? null : current);
5532
+ activityTipAnchor.current = null;
5533
+ setActivityTipPosition(null);
5534
+ }
5535
+ },
5536
+ children: [
5537
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-activityName", style: { color: activityTextColor(segment.color) }, children: segment.name }),
5538
+ activityDragEnabled && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
5539
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "gantt-tr-resizeHandle gantt-tr-resizeHandleLeft" }),
5540
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "gantt-tr-resizeHandle gantt-tr-resizeHandleRight" })
5541
+ ] })
5542
+ ]
5543
+ },
5544
+ segment.id
5545
+ );
5546
+ }),
5547
+ activityLinks.gaps.map((gap) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react3.default.Fragment, { children: [
5548
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5549
+ "span",
5550
+ {
5551
+ className: "gantt-tr-activityLink",
5552
+ "aria-hidden": "true",
5553
+ style: {
5554
+ left: `${gap.left}px`,
5555
+ width: `${gap.width}px`,
5556
+ top: `${activityTopOffset + gap.lane * ACTIVITY_LANE_STEP + ACTIVITY_LANE_STEP / 2}px`
5557
+ }
5558
+ }
5559
+ ),
5560
+ showActivityLag && gap.lag > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
5561
+ "span",
5562
+ {
5563
+ className: "gantt-tr-activityLag",
5564
+ title: `\u0417\u0430\u0437\u043E\u0440 ${gap.lag} \u0434`,
5565
+ style: {
5566
+ left: `${gap.left + gap.width - 14}px`,
5567
+ top: `${activityTopOffset + gap.lane * ACTIVITY_LANE_STEP + ACTIVITY_LANE_STEP / 2}px`
5568
+ },
5569
+ children: [
5570
+ "+",
5571
+ gap.lag
5572
+ ]
5573
+ }
5574
+ )
5575
+ ] }, gap.key))
5576
+ ] }),
5577
+ !renderActivities && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
4669
5578
  "div",
4670
5579
  {
4671
5580
  "data-taskbar": true,
@@ -4749,19 +5658,33 @@ var TaskRow = import_react3.default.memo(
4749
5658
  }
4750
5659
  ),
4751
5660
  !milestone && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "gantt-tr-resizeHandle gantt-tr-resizeHandleLeft" }),
4752
- showCompositeSegments && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-compositeSegments", "aria-hidden": "true", children: compositeSegments.map((segment) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
4753
- "span",
4754
- {
4755
- className: "gantt-tr-compositeSegment",
4756
- style: {
4757
- left: `${segment.left}%`,
4758
- width: `${segment.width}%`,
4759
- top: `${segment.lane / compositeLaneCount * 100}%`,
4760
- height: `${100 / compositeLaneCount}%`
4761
- }
4762
- },
4763
- segment.id
4764
- )) }),
5661
+ shouldRenderCompositeSegments && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "gantt-tr-compositeSegments", "aria-hidden": "true", children: [
5662
+ compositeSegments.map((segment) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5663
+ "span",
5664
+ {
5665
+ className: "gantt-tr-compositeSegment",
5666
+ style: {
5667
+ left: `${segment.left}%`,
5668
+ width: `${segment.width}%`,
5669
+ top: `${segment.lane / compositeLaneCount * 100}%`,
5670
+ height: `${100 / compositeLaneCount}%`
5671
+ }
5672
+ },
5673
+ segment.id
5674
+ )),
5675
+ compositeSegments.map((segment) => segment.cut ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5676
+ "span",
5677
+ {
5678
+ className: "gantt-tr-compositeSegmentCut",
5679
+ style: {
5680
+ left: `${segment.left}%`,
5681
+ top: `${segment.lane / compositeLaneCount * 100}%`,
5682
+ height: `${100 / compositeLaneCount}%`
5683
+ }
5684
+ },
5685
+ `${segment.id}-cut`
5686
+ ) : null)
5687
+ ] }),
4765
5688
  task.composite && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-compositeHoverChevron", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: `gantt-tr-compositeChevron${compositeExpanded ? " gantt-tr-compositeChevron-open" : ""}`, viewBox: "0 0 20 20", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m7 4 6 6-6 6", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }),
4766
5689
  showDurationInside && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-taskDuration", children: isVisualParent ? getChildCountLabel(childCount) : `${durationDays} \u0434` }),
4767
5690
  progressWidth > 0 && showProgressInside && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "gantt-tr-progressText", children: [
@@ -4807,7 +5730,33 @@ var TaskRow = import_react3.default.memo(
4807
5730
  ),
4808
5731
  document.body
4809
5732
  ),
4810
- !disableDependencyEditing && onDependencyPortPointerDown && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
5733
+ activityTipData && activityTipPosition && typeof document !== "undefined" && (0, import_react_dom.createPortal)(
5734
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
5735
+ "div",
5736
+ {
5737
+ className: `gantt-tr-activityTip${activityTipPosition.below ? " gantt-tr-activityTip-below" : ""}`,
5738
+ style: { left: `${activityTipPosition.left}px`, top: `${activityTipPosition.top}px` },
5739
+ role: "tooltip",
5740
+ children: [
5741
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-activityTipName", children: activityTipData.floor }),
5742
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-activityTipDot", "aria-hidden": "true", children: "\xB7" }),
5743
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-activityTipWork", children: activityTipData.name }),
5744
+ activityTooltip && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "gantt-tr-activityTipExtra", children: activityTooltip(activityTipData.context) }),
5745
+ activityTipData.fields.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "gantt-tr-activityTipFields", children: activityTipData.fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gantt-tr-activityTipField", children: [
5746
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-activityTipFieldLabel", children: field.label }),
5747
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-activityTipFieldValue", children: field.value })
5748
+ ] }, field.label)) }),
5749
+ activityBlockedBy && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "gantt-tr-activityTipReason", children: [
5750
+ "\xB7 \u0411\u043B\u043E\u043A: ",
5751
+ activityBlockedBy.names.join(", "),
5752
+ activityBlockedBy.lag > 0 ? ` \xB7 \u0437\u0430\u0437\u043E\u0440 ${activityBlockedBy.lag} \u0434` : ""
5753
+ ] })
5754
+ ]
5755
+ }
5756
+ ),
5757
+ document.body
5758
+ ),
5759
+ !disableDependencyEditing && onDependencyPortPointerDown && !renderActivities && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
4811
5760
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
4812
5761
  "button",
4813
5762
  {
@@ -4837,7 +5786,7 @@ var TaskRow = import_react3.default.memo(
4837
5786
  }
4838
5787
  )
4839
5788
  ] }),
4840
- showTaskDateLabels && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5789
+ showTaskDateLabels && !renderActivities && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
4841
5790
  "div",
4842
5791
  {
4843
5792
  className: `gantt-tr-leftLabels ${task.locked ? "gantt-tr-leftLabels-locked" : ""}`,
@@ -4847,7 +5796,7 @@ var TaskRow = import_react3.default.memo(
4847
5796
  children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-dateLabel gantt-tr-dateLabelLeft", children: dateRangeLabel })
4848
5797
  }
4849
5798
  ),
4850
- task.locked && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5799
+ task.locked && !renderActivities && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
4851
5800
  "svg",
4852
5801
  {
4853
5802
  className: "gantt-tr-lockIcon",
@@ -4868,7 +5817,7 @@ var TaskRow = import_react3.default.memo(
4868
5817
  children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zM12 17c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z" })
4869
5818
  }
4870
5819
  ),
4871
- !showDurationInside && !milestone && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5820
+ !showDurationInside && !milestone && !renderActivities && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
4872
5821
  "span",
4873
5822
  {
4874
5823
  className: `gantt-tr-externalDuration gantt-tr-durationBeforeBar${compactDetail ? " gantt-tr-compositeDuration" : ""}`,
@@ -4876,7 +5825,7 @@ var TaskRow = import_react3.default.memo(
4876
5825
  children: isVisualParent ? getChildCountLabel(childCount) : `${durationDays} \u0434`
4877
5826
  }
4878
5827
  ),
4879
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
5828
+ !renderActivities && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
4880
5829
  "div",
4881
5830
  {
4882
5831
  className: "gantt-tr-rightLabels",
@@ -5012,10 +5961,10 @@ var import_react6 = __toESM(require("react"));
5012
5961
  var import_jsx_runtime5 = require("react/jsx-runtime");
5013
5962
  var arePropsEqual2 = (prevProps, nextProps) => {
5014
5963
  return prevProps.dayWidth === nextProps.dayWidth && prevProps.dateRange.length === nextProps.dateRange.length && prevProps.totalHeight === nextProps.totalHeight && // skip re-render only when totalHeight unchanged
5015
- prevProps.viewMode === nextProps.viewMode && prevProps.isCustomWeekend === nextProps.isCustomWeekend;
5964
+ prevProps.viewMode === nextProps.viewMode && prevProps.isCustomWeekend === nextProps.isCustomWeekend && prevProps.showWeekendBlocks === nextProps.showWeekendBlocks && prevProps.showGridLines === nextProps.showGridLines && prevProps.horizontalWindow?.startPx === nextProps.horizontalWindow?.startPx && prevProps.horizontalWindow?.endPx === nextProps.horizontalWindow?.endPx;
5016
5965
  };
5017
5966
  var GridBackground = import_react6.default.memo(
5018
- ({ dateRange, dayWidth, totalHeight, viewMode = "day", isCustomWeekend, className, showWeekendBlocks = true, showGridLines = true }) => {
5967
+ ({ dateRange, dayWidth, totalHeight, viewMode = "day", isCustomWeekend, className, showWeekendBlocks = true, showGridLines = true, horizontalWindow }) => {
5019
5968
  const weekGridLines = (0, import_react6.useMemo)(() => {
5020
5969
  if (viewMode !== "week") return [];
5021
5970
  return calculateWeekGridLines(dateRange, dayWidth);
@@ -5032,6 +5981,23 @@ var GridBackground = import_react6.default.memo(
5032
5981
  if (viewMode === "week" || viewMode === "month") return [];
5033
5982
  return calculateWeekendBlocks(dateRange, dayWidth, isCustomWeekend);
5034
5983
  }, [dateRange, dayWidth, viewMode, isCustomWeekend]);
5984
+ const inHorizontalWindow = (left, width = 1) => !horizontalWindow || left <= horizontalWindow.endPx && left + width >= horizontalWindow.startPx;
5985
+ const visibleWeekendBlocks = (0, import_react6.useMemo)(
5986
+ () => weekendBlocks.filter((block) => inHorizontalWindow(block.left, block.width)),
5987
+ [weekendBlocks, horizontalWindow?.startPx, horizontalWindow?.endPx]
5988
+ );
5989
+ const visibleWeekGridLines = (0, import_react6.useMemo)(
5990
+ () => weekGridLines.filter((line) => inHorizontalWindow(line.x)),
5991
+ [weekGridLines, horizontalWindow?.startPx, horizontalWindow?.endPx]
5992
+ );
5993
+ const visibleMonthGridLines = (0, import_react6.useMemo)(
5994
+ () => monthGridLines.filter((line) => inHorizontalWindow(line.x)),
5995
+ [monthGridLines, horizontalWindow?.startPx, horizontalWindow?.endPx]
5996
+ );
5997
+ const visibleGridLines = (0, import_react6.useMemo)(
5998
+ () => gridLines.filter((line) => inHorizontalWindow(line.x)),
5999
+ [gridLines, horizontalWindow?.startPx, horizontalWindow?.endPx]
6000
+ );
5035
6001
  const gridWidth = (0, import_react6.useMemo)(() => {
5036
6002
  return Math.round(dateRange.length * dayWidth);
5037
6003
  }, [dateRange.length, dayWidth]);
@@ -5044,7 +6010,7 @@ var GridBackground = import_react6.default.memo(
5044
6010
  height: `${totalHeight}px`
5045
6011
  },
5046
6012
  children: [
5047
- showWeekendBlocks && weekendBlocks.map((block, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
6013
+ showWeekendBlocks && visibleWeekendBlocks.map((block, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
5048
6014
  "div",
5049
6015
  {
5050
6016
  className: "gantt-gb-weekendBlock",
@@ -5057,7 +6023,7 @@ var GridBackground = import_react6.default.memo(
5057
6023
  )),
5058
6024
  showGridLines && (viewMode === "week" ? (
5059
6025
  // Week-view: one line per week column boundary
5060
- weekGridLines.map((line, index) => {
6026
+ visibleWeekGridLines.map((line, index) => {
5061
6027
  const lineClass = line.isMonthStart ? "gantt-gb-monthSeparator" : "gantt-gb-weekSeparator";
5062
6028
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
5063
6029
  "div",
@@ -5070,7 +6036,7 @@ var GridBackground = import_react6.default.memo(
5070
6036
  })
5071
6037
  ) : viewMode === "month" ? (
5072
6038
  // Month-view: thin line at each month boundary, thick at year boundary
5073
- monthGridLines.map((line, index) => {
6039
+ visibleMonthGridLines.map((line, index) => {
5074
6040
  const lineClass = line.isMonthStart ? "gantt-gb-monthSeparator" : "gantt-gb-weekSeparator";
5075
6041
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
5076
6042
  "div",
@@ -5082,8 +6048,8 @@ var GridBackground = import_react6.default.memo(
5082
6048
  );
5083
6049
  })
5084
6050
  ) : (
5085
- // Day-view: existing code (unchanged)
5086
- gridLines.map((line, index) => {
6051
+ // Day-view: render only the lines inside the horizontal window.
6052
+ visibleGridLines.map((line, index) => {
5087
6053
  const lineClass = line.isMonthStart ? "gantt-gb-monthSeparator" : line.isWeekStart ? "gantt-gb-weekSeparator" : "gantt-gb-dayLine";
5088
6054
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
5089
6055
  "div",
@@ -5195,7 +6161,8 @@ var DependencyLines = import_react7.default.memo(({
5195
6161
  criticalTaskIds,
5196
6162
  businessDays = true,
5197
6163
  weekendPredicate,
5198
- onDependencyClick
6164
+ onDependencyClick,
6165
+ horizontalWindow
5199
6166
  }) => {
5200
6167
  const [hoveredLineId, setHoveredLineId] = (0, import_react7.useState)(null);
5201
6168
  const tasksForPositions = allTasks ?? tasks;
@@ -5359,6 +6326,10 @@ var DependencyLines = import_react7.default.memo(({
5359
6326
  }
5360
6327
  return lines2;
5361
6328
  }, [tasks, allTasks, taskPositions, taskIndices, cycleInfo, collapsedParentIds, criticalTaskIds]);
6329
+ const visibleLines = (0, import_react7.useMemo)(() => {
6330
+ if (!horizontalWindow) return lines;
6331
+ return lines.filter((line) => Math.max(line.fromX, line.toX) >= horizontalWindow.startPx && Math.min(line.fromX, line.toX) <= horizontalWindow.endPx);
6332
+ }, [lines, horizontalWindow?.startPx, horizontalWindow?.endPx]);
5362
6333
  const svgHeight = totalHeight ?? tasks.length * rowHeight;
5363
6334
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
5364
6335
  "svg",
@@ -5466,7 +6437,7 @@ var DependencyLines = import_react7.default.memo(({
5466
6437
  }
5467
6438
  )
5468
6439
  ] }),
5469
- lines.map(({ id, predecessorId, successorId, linkType, path, hasCycle, lag, fromX, toX, fromY, reverseOrder, isVirtual, isCritical }) => {
6440
+ visibleLines.map(({ id, predecessorId, successorId, linkType, path, hasCycle, lag, fromX, toX, fromY, reverseOrder, isVirtual, isCritical }) => {
5470
6441
  const isHovered = hoveredLineId === id;
5471
6442
  const isSelected = selectedDep != null && id === `${selectedDep.predecessorId}-${selectedDep.successorId}-${selectedDep.linkType}`;
5472
6443
  let pathClassName = "gantt-dependency-path";
@@ -6306,7 +7277,7 @@ var LINK_TYPE_LABELS = {
6306
7277
  };
6307
7278
 
6308
7279
  // src/components/TaskList/defaultTaskDates.ts
6309
- var DAY_MS2 = 24 * 60 * 60 * 1e3;
7280
+ var DAY_MS3 = 24 * 60 * 60 * 1e3;
6310
7281
  var DEFAULT_TASK_DURATION_DAYS = 5;
6311
7282
  function toISODate(date) {
6312
7283
  return date.toISOString().split("T")[0];
@@ -6335,25 +7306,25 @@ function buildDefaultTaskDateRange(startDate, {
6335
7306
  }
6336
7307
  return {
6337
7308
  startDate: toISODate(start),
6338
- endDate: toISODate(new Date(start.getTime() + (durationDays - 1) * DAY_MS2))
7309
+ endDate: toISODate(new Date(start.getTime() + (durationDays - 1) * DAY_MS3))
6339
7310
  };
6340
7311
  }
6341
7312
 
6342
7313
  // src/components/TaskList/TaskListRow.tsx
6343
7314
  var import_jsx_runtime14 = require("react/jsx-runtime");
6344
- var DAY_MS3 = 24 * 60 * 60 * 1e3;
7315
+ var DAY_MS4 = 24 * 60 * 60 * 1e3;
6345
7316
  var LINK_TYPE_ORDER = ["FS", "SS", "FF", "SF"];
6346
7317
  var getInclusiveDurationDays = (startDate, endDate) => {
6347
7318
  const start = parseUTCDate(startDate);
6348
7319
  const end = parseUTCDate(endDate);
6349
7320
  return Math.max(
6350
7321
  1,
6351
- Math.round((end.getTime() - start.getTime()) / DAY_MS3) + 1
7322
+ Math.round((end.getTime() - start.getTime()) / DAY_MS4) + 1
6352
7323
  );
6353
7324
  };
6354
7325
  var getEndDateFromDuration = (startDate, durationDays) => {
6355
7326
  const start = parseUTCDate(startDate);
6356
- return new Date(start.getTime() + (durationDays - 1) * DAY_MS3).toISOString().split("T")[0];
7327
+ return new Date(start.getTime() + (durationDays - 1) * DAY_MS4).toISOString().split("T")[0];
6357
7328
  };
6358
7329
  var TrashIcon = () => /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
6359
7330
  "svg",
@@ -10459,7 +11430,11 @@ var TaskList = ({
10459
11430
  onActiveCustomCellChange: setActiveCustomCell,
10460
11431
  taskListMenuCommands,
10461
11432
  hideTaskListRowActions,
10462
- rowClassName: [task.parentId && compositeParentIds.has(task.parentId) ? "gantt-tl-row-composite-detail" : "", getTaskListRowClassName?.(task)].filter(Boolean).join(" "),
11433
+ rowClassName: [
11434
+ task.parentId && compositeParentIds.has(task.parentId) ? "gantt-tl-row-composite-detail" : "",
11435
+ (task.activities?.length ?? 0) > 0 && (rowHeights?.[index] ?? rowHeight) > rowHeight ? "gantt-tl-row-activities" : "",
11436
+ getTaskListRowClassName?.(task)
11437
+ ].filter(Boolean).join(" "),
10463
11438
  compositeParentColor: task.parentId ? compositeParentsById.get(task.parentId)?.color : void 0,
10464
11439
  getTaskListNamePrefixIcon,
10465
11440
  taskDateChangeMode,
@@ -12437,7 +13412,7 @@ var import_react17 = require("react");
12437
13412
  var import_jsx_runtime19 = require("react/jsx-runtime");
12438
13413
  var AUTO_COLUMN_MIN_WIDTH = 72;
12439
13414
  var AUTO_COLUMN_MAX_WIDTH = 180;
12440
- var DAY_MS4 = 24 * 60 * 60 * 1e3;
13415
+ var DAY_MS5 = 24 * 60 * 60 * 1e3;
12441
13416
  function joinClasses(...values) {
12442
13417
  return values.filter(Boolean).join(" ");
12443
13418
  }
@@ -12472,8 +13447,8 @@ function getOverlayWidthPercent(column, overlayDateMs) {
12472
13447
  if (overlayDateMs >= endMs) {
12473
13448
  return 100;
12474
13449
  }
12475
- const totalDays = Math.max(1, Math.round((endMs - startMs) / DAY_MS4) + 1);
12476
- const elapsedDays = Math.min(totalDays, Math.max(0, Math.round((overlayDateMs - startMs) / DAY_MS4) + 1));
13450
+ const totalDays = Math.max(1, Math.round((endMs - startMs) / DAY_MS5) + 1);
13451
+ const elapsedDays = Math.min(totalDays, Math.max(0, Math.round((overlayDateMs - startMs) / DAY_MS5) + 1));
12477
13452
  return elapsedDays / totalDays * 100;
12478
13453
  }
12479
13454
  function isOverlayDateInColumn(column, overlayDateMs) {
@@ -12803,7 +13778,7 @@ function TableMatrix({
12803
13778
  // src/components/PlanFactMatrix/PlanFactMatrix.tsx
12804
13779
  var import_react18 = __toESM(require("react"));
12805
13780
  var import_jsx_runtime20 = require("react/jsx-runtime");
12806
- var DAY_MS5 = 24 * 60 * 60 * 1e3;
13781
+ var DAY_MS6 = 24 * 60 * 60 * 1e3;
12807
13782
  function joinClasses2(...values) {
12808
13783
  return values.filter(Boolean).join(" ");
12809
13784
  }
@@ -12833,8 +13808,8 @@ function getPlannedIndexRange(task, rangeStartMs, rangeLength) {
12833
13808
  if (rangeLength <= 0) return null;
12834
13809
  const start = parseUTCDate(task.startDate);
12835
13810
  const end = parseUTCDate(task.endDate);
12836
- const startIndex = Math.ceil((getDateOnlyMs(start) - rangeStartMs) / DAY_MS5);
12837
- const endIndex = Math.floor((getDateOnlyMs(end) - rangeStartMs) / DAY_MS5);
13811
+ const startIndex = Math.ceil((getDateOnlyMs(start) - rangeStartMs) / DAY_MS6);
13812
+ const endIndex = Math.floor((getDateOnlyMs(end) - rangeStartMs) / DAY_MS6);
12838
13813
  const clampedStartIndex = Math.max(0, startIndex);
12839
13814
  const clampedEndIndex = Math.min(rangeLength - 1, endIndex);
12840
13815
  if (clampedStartIndex > clampedEndIndex) return null;
@@ -14203,80 +15178,13 @@ async function printGanttChart({
14203
15178
  }
14204
15179
  }
14205
15180
 
14206
- // src/components/GanttChart/previewStore.ts
14207
- function positionsEqual(left, right) {
14208
- return left?.left === right?.left && left?.width === right?.width;
14209
- }
14210
- function createTaskPreviewPositionStore() {
14211
- let positions = /* @__PURE__ */ new Map();
14212
- const listenersByTaskId = /* @__PURE__ */ new Map();
14213
- function notify(taskIds) {
14214
- for (const taskId of taskIds) {
14215
- const listeners = listenersByTaskId.get(taskId);
14216
- if (!listeners) continue;
14217
- for (const listener of listeners) {
14218
- listener();
14219
- }
14220
- }
14221
- }
14222
- return {
14223
- subscribeTask(taskId, listener) {
14224
- const listeners = listenersByTaskId.get(taskId) ?? /* @__PURE__ */ new Set();
14225
- listeners.add(listener);
14226
- listenersByTaskId.set(taskId, listeners);
14227
- return () => {
14228
- listeners.delete(listener);
14229
- if (listeners.size === 0) {
14230
- listenersByTaskId.delete(taskId);
14231
- }
14232
- };
14233
- },
14234
- getTaskPosition(taskId) {
14235
- return positions.get(taskId);
14236
- },
14237
- setPositions(nextPositions) {
14238
- const changedTaskIds = /* @__PURE__ */ new Set();
14239
- const mergedPositions = /* @__PURE__ */ new Map();
14240
- for (const [taskId, nextPosition] of nextPositions) {
14241
- const currentPosition = positions.get(taskId);
14242
- if (positionsEqual(currentPosition, nextPosition)) {
14243
- if (currentPosition) {
14244
- mergedPositions.set(taskId, currentPosition);
14245
- }
14246
- continue;
14247
- }
14248
- changedTaskIds.add(taskId);
14249
- mergedPositions.set(taskId, {
14250
- left: nextPosition.left,
14251
- width: nextPosition.width
14252
- });
14253
- }
14254
- for (const taskId of positions.keys()) {
14255
- if (!nextPositions.has(taskId)) {
14256
- changedTaskIds.add(taskId);
14257
- }
14258
- }
14259
- if (changedTaskIds.size === 0) {
14260
- return;
14261
- }
14262
- positions = mergedPositions;
14263
- notify(changedTaskIds);
14264
- },
14265
- clear() {
14266
- if (positions.size === 0) {
14267
- return;
14268
- }
14269
- const changedTaskIds = new Set(positions.keys());
14270
- positions = /* @__PURE__ */ new Map();
14271
- notify(changedTaskIds);
14272
- }
14273
- };
14274
- }
14275
-
14276
15181
  // src/components/GanttChart/GanttChart.tsx
14277
15182
  var import_jsx_runtime21 = require("react/jsx-runtime");
14278
15183
  var SCROLL_TO_ROW_CONTEXT_ROWS = 2;
14279
15184
  var TASK_ROW_OVERSCAN = 8;
15185
+ var SCROLL_TOP_BUCKET_PX = 64;
15186
+ var HORIZONTAL_WINDOW_BUCKET_PX = 480;
15187
+ var HORIZONTAL_WINDOW_OVERSCAN_PX = 960;
14280
15188
  var PLAN_FACT_COLUMN_OVERSCAN = 24;
14281
15189
  var PLAN_FACT_COLUMN_WINDOW_STEP = 14;
14282
15190
  var DEFAULT_INITIAL_VIEWPORT_HEIGHT = 768;
@@ -14469,6 +15377,12 @@ function TaskGanttChartInner(props, ref) {
14469
15377
  skeletonStartJitterDays = 0,
14470
15378
  showTaskDateLabels = true,
14471
15379
  showTaskNames = true,
15380
+ showCompositeSegments = true,
15381
+ activityClickToDrag = false,
15382
+ activityActivationMode = "none",
15383
+ showActivityLinks = true,
15384
+ showActivityLag = true,
15385
+ activityTooltip,
14472
15386
  timelineMarkers,
14473
15387
  additionalColumns,
14474
15388
  hiddenTaskListColumns,
@@ -14502,17 +15416,41 @@ function TaskGanttChartInner(props, ref) {
14502
15416
  const clearSelectedTaskTimeoutRef = (0, import_react19.useRef)(null);
14503
15417
  const hasAutoScrolledToTodayRef = (0, import_react19.useRef)(false);
14504
15418
  const previewPositionStoreRef = (0, import_react19.useRef)(null);
15419
+ const activityPreviewStoreRef = (0, import_react19.useRef)(null);
15420
+ const activityBlockStoreRef = (0, import_react19.useRef)(null);
15421
+ const activityDragOwnerRef = (0, import_react19.useRef)(null);
15422
+ const activityActivationStoreRef = (0, import_react19.useRef)(null);
14505
15423
  const renderedTaskIdsRef = (0, import_react19.useRef)(/* @__PURE__ */ new Set());
14506
15424
  if (previewPositionStoreRef.current === null) {
14507
15425
  previewPositionStoreRef.current = createTaskPreviewPositionStore();
14508
15426
  }
15427
+ if (activityPreviewStoreRef.current === null) {
15428
+ activityPreviewStoreRef.current = createActivityPreviewStore();
15429
+ }
15430
+ if (activityBlockStoreRef.current === null) {
15431
+ activityBlockStoreRef.current = createActivityBlockStore();
15432
+ }
15433
+ if (activityDragOwnerRef.current === null) {
15434
+ activityDragOwnerRef.current = createActivityDragOwner();
15435
+ }
15436
+ if (activityActivationStoreRef.current === null) {
15437
+ activityActivationStoreRef.current = createActivityActivationStore();
15438
+ }
14509
15439
  const previewPositionStore = previewPositionStoreRef.current;
15440
+ const activityPreviewStore = activityPreviewStoreRef.current;
15441
+ const activityBlockStore = activityBlockStoreRef.current;
15442
+ const activityDragOwner = activityDragOwnerRef.current;
15443
+ const activityActivationStore = activityActivationStoreRef.current;
14510
15444
  const [selectedTaskId, setSelectedTaskId] = (0, import_react19.useState)(null);
14511
15445
  const [taskListHasRightShadow, setTaskListHasRightShadow] = (0, import_react19.useState)(false);
14512
15446
  const [internalTaskDateChangeMode, setInternalTaskDateChangeMode] = (0, import_react19.useState)("preserve-duration");
14513
15447
  const [scrollViewport, setScrollViewport] = (0, import_react19.useState)(() => ({
14514
15448
  scrollTop: 0,
14515
- viewportHeight: getInitialScrollViewportHeight(containerHeight, headerHeight + 1)
15449
+ viewportHeight: getInitialScrollViewportHeight(containerHeight, headerHeight + 1),
15450
+ // Ширина видимой области графика и «бакетированный» горизонтальный скролл —
15451
+ // основа горизонтального окна рендера (см. horizontalWindow).
15452
+ viewportWidth: 0,
15453
+ chartScrollLeft: 0
14516
15454
  }));
14517
15455
  const [forceFullRenderForPrint, setForceFullRenderForPrint] = (0, import_react19.useState)(false);
14518
15456
  const [planFactDateWindow, setPlanFactDateWindow] = (0, import_react19.useState)({ start: 0, end: 0 });
@@ -14690,8 +15628,8 @@ function TaskGanttChartInner(props, ref) {
14690
15628
  const resolvedRowContentLines = isPlanFactMode ? Math.max(2, Math.floor(rowContentLines)) : Math.max(1, Math.floor(rowContentLines));
14691
15629
  const shouldFillParentRowsInTaskList = fillParentRowsInTaskList ?? isPlanFactMode;
14692
15630
  const effectiveRowHeight = (0, import_react19.useMemo)(
14693
- () => Math.max(rowHeight, 10 + resolvedRowContentLines * 18),
14694
- [resolvedRowContentLines, rowHeight]
15631
+ () => rowContentLines === 0 && !isPlanFactMode ? rowHeight : Math.max(rowHeight, 10 + resolvedRowContentLines * 18),
15632
+ [isPlanFactMode, resolvedRowContentLines, rowContentLines, rowHeight]
14695
15633
  );
14696
15634
  const normalizedTasks = (0, import_react19.useMemo)(() => {
14697
15635
  const ordered = normalizeHierarchyTasks(tasks);
@@ -14750,14 +15688,21 @@ function TaskGanttChartInner(props, ref) {
14750
15688
  if (isPlanFactMode) {
14751
15689
  return getPlanFactRangeTasks(normalizedTasks);
14752
15690
  }
14753
- if (!showBaseline) {
14754
- return normalizedTasks;
15691
+ const rangeTasks = [];
15692
+ for (const task of normalizedTasks) {
15693
+ if (showBaseline) {
15694
+ rangeTasks.push({
15695
+ startDate: task.baselineStartDate && parseUTCDate(task.baselineStartDate).getTime() < parseUTCDate(task.startDate).getTime() ? task.baselineStartDate : task.startDate,
15696
+ endDate: task.baselineEndDate && parseUTCDate(task.baselineEndDate).getTime() > parseUTCDate(task.endDate).getTime() ? task.baselineEndDate : task.endDate
15697
+ });
15698
+ } else {
15699
+ rangeTasks.push(task);
15700
+ }
15701
+ for (const activity of task.activities ?? []) {
15702
+ rangeTasks.push({ startDate: activity.startDate, endDate: activity.endDate });
15703
+ }
14755
15704
  }
14756
- return normalizedTasks.map((task) => ({
14757
- ...task,
14758
- startDate: task.baselineStartDate && parseUTCDate(task.baselineStartDate).getTime() < parseUTCDate(task.startDate).getTime() ? task.baselineStartDate : task.startDate,
14759
- endDate: task.baselineEndDate && parseUTCDate(task.baselineEndDate).getTime() > parseUTCDate(task.endDate).getTime() ? task.baselineEndDate : task.endDate
14760
- }));
15705
+ return rangeTasks;
14761
15706
  }, [isPlanFactMode, normalizedTasks, showBaseline]);
14762
15707
  const dateRange = (0, import_react19.useMemo)(
14763
15708
  () => {
@@ -14802,6 +15747,25 @@ function TaskGanttChartInner(props, ref) {
14802
15747
  }
14803
15748
  return tasks2;
14804
15749
  }, [normalizedTasks, collapsedParentIds, filterMode, taskFilter, criticalPathMode, criticalTaskIds]);
15750
+ const monthStart = (0, import_react19.useMemo)(() => {
15751
+ if (dateRange.length === 0) {
15752
+ return new Date(Date.UTC((/* @__PURE__ */ new Date()).getUTCFullYear(), (/* @__PURE__ */ new Date()).getUTCMonth(), 1));
15753
+ }
15754
+ const firstDay = dateRange[0];
15755
+ if (explicitDateRange && !isPlanFactMode) {
15756
+ return new Date(firstDay);
15757
+ }
15758
+ return new Date(Date.UTC(firstDay.getUTCFullYear(), firstDay.getUTCMonth(), 1));
15759
+ }, [dateRange, explicitDateRange, isPlanFactMode]);
15760
+ const activityLaneCounts = (0, import_react19.useMemo)(() => {
15761
+ const counts = /* @__PURE__ */ new Map();
15762
+ for (const task of visibleTasks) {
15763
+ if (task.activities && task.activities.length > 0) {
15764
+ counts.set(task.id, computeActivityLanes(task.activities, monthStart, dayWidth).laneCount);
15765
+ }
15766
+ }
15767
+ return counts;
15768
+ }, [visibleTasks, monthStart, dayWidth]);
14805
15769
  const compositeRowLayout = (0, import_react19.useMemo)(() => {
14806
15770
  const compositeIds = new Set(normalizedTasks.filter((task) => task.composite).map((task) => task.id));
14807
15771
  const tops = [];
@@ -14809,12 +15773,12 @@ function TaskGanttChartInner(props, ref) {
14809
15773
  let top = 0;
14810
15774
  for (const task of visibleTasks) {
14811
15775
  tops.push(top);
14812
- const height = task.parentId && compositeIds.has(task.parentId) ? Math.min(effectiveRowHeight, 22) : effectiveRowHeight;
15776
+ const height = task.parentId && compositeIds.has(task.parentId) ? Math.min(effectiveRowHeight, 22) : Math.max(effectiveRowHeight, activityLanesExtraHeight(activityLaneCounts.get(task.id) ?? 0));
14813
15777
  heights.push(height);
14814
15778
  top += height;
14815
15779
  }
14816
15780
  return { tops, heights, totalHeight: top };
14817
- }, [normalizedTasks, visibleTasks, effectiveRowHeight]);
15781
+ }, [normalizedTasks, visibleTasks, effectiveRowHeight, activityLaneCounts]);
14818
15782
  const compositeParentsById = (0, import_react19.useMemo)(
14819
15783
  () => new Map(normalizedTasks.filter((task) => task.composite).map((task) => [task.id, task])),
14820
15784
  [normalizedTasks]
@@ -14853,16 +15817,6 @@ function TaskGanttChartInner(props, ref) {
14853
15817
  }
14854
15818
  return `calc(${containerHeight} - ${timelineHeaderHeight}px)`;
14855
15819
  }, [containerHeight, isPlanFactMode, isTableMatrixMode, timelineHeaderHeight]);
14856
- const monthStart = (0, import_react19.useMemo)(() => {
14857
- if (dateRange.length === 0) {
14858
- return new Date(Date.UTC((/* @__PURE__ */ new Date()).getUTCFullYear(), (/* @__PURE__ */ new Date()).getUTCMonth(), 1));
14859
- }
14860
- const firstDay = dateRange[0];
14861
- if (explicitDateRange && !isPlanFactMode) {
14862
- return new Date(firstDay);
14863
- }
14864
- return new Date(Date.UTC(firstDay.getUTCFullYear(), firstDay.getUTCMonth(), 1));
14865
- }, [dateRange, explicitDateRange, isPlanFactMode]);
14866
15820
  const todayIndex = (0, import_react19.useMemo)(() => {
14867
15821
  const today = getTodayLocalUtcDate();
14868
15822
  return dateRange.findIndex((day) => day.getTime() === today.getTime());
@@ -14905,12 +15859,23 @@ function TaskGanttChartInner(props, ref) {
14905
15859
  const nextScrollTop = container.scrollTop;
14906
15860
  const nextScrollLeft = container.scrollLeft;
14907
15861
  const nextChartScrollLeft = Math.max(0, nextScrollLeft);
15862
+ const bucketedScrollTop = Math.floor(nextScrollTop / SCROLL_TOP_BUCKET_PX) * SCROLL_TOP_BUCKET_PX;
15863
+ const bucketedChartScrollLeft = Math.floor(nextChartScrollLeft / HORIZONTAL_WINDOW_BUCKET_PX) * HORIZONTAL_WINDOW_BUCKET_PX;
14908
15864
  setTaskListHasRightShadow(
14909
15865
  (previous) => previous === nextHasRightShadow ? previous : nextHasRightShadow
14910
15866
  );
14911
- setScrollViewport(
14912
- (previous) => previous.viewportHeight > 0 && nextViewportHeight === 0 ? { scrollTop: nextScrollTop, viewportHeight: previous.viewportHeight } : previous.scrollTop === nextScrollTop && previous.viewportHeight === nextViewportHeight ? previous : { scrollTop: nextScrollTop, viewportHeight: nextViewportHeight }
14913
- );
15867
+ setScrollViewport((previous) => {
15868
+ const resolvedViewportHeight = previous.viewportHeight > 0 && nextViewportHeight === 0 ? previous.viewportHeight : nextViewportHeight;
15869
+ if (previous.scrollTop === bucketedScrollTop && previous.viewportHeight === resolvedViewportHeight && previous.viewportWidth === nextViewportWidth && previous.chartScrollLeft === bucketedChartScrollLeft) {
15870
+ return previous;
15871
+ }
15872
+ return {
15873
+ scrollTop: bucketedScrollTop,
15874
+ viewportHeight: resolvedViewportHeight,
15875
+ viewportWidth: nextViewportWidth,
15876
+ chartScrollLeft: bucketedChartScrollLeft
15877
+ };
15878
+ });
14914
15879
  setPlanFactDateWindow((previous) => {
14915
15880
  if (!isPlanFactMode || dateRange.length === 0 || nextViewportWidth <= 0) {
14916
15881
  return previous;
@@ -15026,11 +15991,15 @@ function TaskGanttChartInner(props, ref) {
15026
15991
  return;
15027
15992
  }
15028
15993
  previewPositionStore.clear();
15994
+ activityPreviewStore.clear();
15995
+ activityBlockStore.clear();
15996
+ activityDragOwner.set(null);
15997
+ activityActivationStore.setActiveKey(null);
15029
15998
  setCascadeOverrides((current) => current.size === 0 ? current : /* @__PURE__ */ new Map());
15030
15999
  setPreviewTasksById((current) => current.size === 0 ? current : /* @__PURE__ */ new Map());
15031
16000
  setDraggedTaskOverride((current) => current === null ? current : null);
15032
16001
  setDragGuideLines((current) => current === null ? current : null);
15033
- }, [controlledScheduleIdentity, previewPositionStore]);
16002
+ }, [controlledScheduleIdentity, previewPositionStore, activityPreviewStore, activityBlockStore, activityDragOwner, activityActivationStore]);
15034
16003
  (0, import_react19.useEffect)(() => {
15035
16004
  const result = validateDependencies(tasks);
15036
16005
  setValidationResult(result);
@@ -15041,7 +16010,11 @@ function TaskGanttChartInner(props, ref) {
15041
16010
  window.clearTimeout(clearSelectedTaskTimeoutRef.current);
15042
16011
  }
15043
16012
  previewPositionStore.clear();
15044
- }, [previewPositionStore]);
16013
+ activityPreviewStore.clear();
16014
+ activityBlockStore.clear();
16015
+ activityDragOwner.set(null);
16016
+ activityActivationStore.setActiveKey(null);
16017
+ }, [previewPositionStore, activityPreviewStore, activityBlockStore, activityDragOwner, activityActivationStore]);
15045
16018
  const handleTaskChange = (0, import_react19.useCallback)((updatedTasks, durationEdit) => {
15046
16019
  let updatedTask = updatedTasks[0];
15047
16020
  if (!updatedTask) return;
@@ -15277,6 +16250,15 @@ function TaskGanttChartInner(props, ref) {
15277
16250
  }
15278
16251
  return ids;
15279
16252
  }, [draggedTaskOverride]);
16253
+ const horizontalWindow = (0, import_react19.useMemo)(() => {
16254
+ const width = scrollViewport.viewportWidth;
16255
+ if (width <= 0) return void 0;
16256
+ const left = scrollViewport.chartScrollLeft;
16257
+ return {
16258
+ startPx: Math.max(0, left - HORIZONTAL_WINDOW_OVERSCAN_PX),
16259
+ endPx: left + width + HORIZONTAL_WINDOW_OVERSCAN_PX
16260
+ };
16261
+ }, [scrollViewport.chartScrollLeft, scrollViewport.viewportWidth]);
15280
16262
  const visibleTaskWindowIndices = (0, import_react19.useMemo)(() => {
15281
16263
  const totalTasks = visibleTasks.length;
15282
16264
  if (totalTasks === 0) {
@@ -15611,6 +16593,9 @@ function TaskGanttChartInner(props, ref) {
15611
16593
  const handlePanStart = (0, import_react19.useCallback)((e) => {
15612
16594
  if (e.button !== 0) return;
15613
16595
  const target = e.target;
16596
+ if (!target.closest(".gantt-tr-activityBar")) {
16597
+ activityActivationStore.setActiveKey(null);
16598
+ }
15614
16599
  if (target.closest("[data-taskbar]")) return;
15615
16600
  if (target.closest("input, button, textarea, [contenteditable]")) return;
15616
16601
  if (target.closest(".gantt-tl-overlay")) return;
@@ -15631,7 +16616,7 @@ function TaskGanttChartInner(props, ref) {
15631
16616
  }
15632
16617
  container.style.cursor = "grabbing";
15633
16618
  e.preventDefault();
15634
- }, []);
16619
+ }, [activityActivationStore]);
15635
16620
  (0, import_react19.useEffect)(() => {
15636
16621
  const flushPanMove = () => {
15637
16622
  const pan = panStateRef.current;
@@ -15861,7 +16846,8 @@ function TaskGanttChartInner(props, ref) {
15861
16846
  dayWidth,
15862
16847
  totalHeight: totalGridHeight,
15863
16848
  viewMode,
15864
- isCustomWeekend
16849
+ isCustomWeekend,
16850
+ horizontalWindow
15865
16851
  }
15866
16852
  ),
15867
16853
  todayInRange && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
@@ -15902,6 +16888,7 @@ function TaskGanttChartInner(props, ref) {
15902
16888
  selectedDep: selectedChip,
15903
16889
  businessDays,
15904
16890
  weekendPredicate: isCustomWeekend,
16891
+ horizontalWindow,
15905
16892
  onDependencyClick: handleDependencyLineClick,
15906
16893
  criticalTaskIds: criticalPathMode ? criticalTaskIds : void 0
15907
16894
  }
@@ -16008,6 +16995,7 @@ function TaskGanttChartInner(props, ref) {
16008
16995
  setDragGuideLines((current) => current === null ? current : null);
16009
16996
  setDraggedTaskOverride((current) => current === null ? current : null);
16010
16997
  previewPositionStore.clear();
16998
+ activityPreviewStore.clear();
16011
16999
  setCascadeOverrides((current) => current.size === 0 ? current : /* @__PURE__ */ new Map());
16012
17000
  setPreviewTasksById((current) => current.size === 0 ? current : /* @__PURE__ */ new Map());
16013
17001
  }
@@ -16018,6 +17006,16 @@ function TaskGanttChartInner(props, ref) {
16018
17006
  disableConstraints: disableConstraints ?? false,
16019
17007
  deferCascadePreview: normalizedTasks.length >= 1e3,
16020
17008
  previewPositionStore,
17009
+ activityPreviewStore,
17010
+ activityBlockStore,
17011
+ activityDragOwner,
17012
+ activityActivationStore,
17013
+ activityClickToDrag,
17014
+ activityActivationMode,
17015
+ showActivityLinks,
17016
+ showActivityLag,
17017
+ activityTooltip,
17018
+ horizontalWindow,
16021
17019
  onCascadeProgress: handleCascadeProgress,
16022
17020
  onCascade: handleCascade,
16023
17021
  onScheduleIntent,
@@ -16035,6 +17033,7 @@ function TaskGanttChartInner(props, ref) {
16035
17033
  viewMode,
16036
17034
  showTaskDateLabels,
16037
17035
  showTaskNames,
17036
+ showCompositeSegments,
16038
17037
  onCompositeToggle: task.composite ? (source) => handleCompositeBarToggle(task.id, source) : void 0,
16039
17038
  compositeExpanded: task.composite ? !collapsedParentIds.has(task.id) : void 0
16040
17039
  }
@@ -16124,6 +17123,9 @@ var nameContains = (substring, caseSensitive = false) => (task) => {
16124
17123
  };
16125
17124
  // Annotate the CommonJS export names for ESM import in node:
16126
17125
  0 && (module.exports = {
17126
+ ACTIVITY_LANES_PADDING,
17127
+ ACTIVITY_LANE_BAR_HEIGHT,
17128
+ ACTIVITY_LANE_STEP,
16127
17129
  Button,
16128
17130
  Calendar,
16129
17131
  DAY_MS,
@@ -16145,6 +17147,10 @@ var nameContains = (substring, caseSensitive = false) => (task) => {
16145
17147
  TaskRow,
16146
17148
  TimeScaleHeader,
16147
17149
  TodayIndicator,
17150
+ activityChainMinStartDelta,
17151
+ activityLanesExtraHeight,
17152
+ activitySpanMs,
17153
+ activityStartConstraint,
16148
17154
  addBusinessDays,
16149
17155
  alignToWorkingDay,
16150
17156
  and,
@@ -16167,6 +17173,7 @@ var nameContains = (substring, caseSensitive = false) => (task) => {
16167
17173
  cascadeByLinks,
16168
17174
  clampDateRangeForIncomingFS,
16169
17175
  clampTaskRangeForIncomingFS,
17176
+ computeActivityLanes,
16170
17177
  computeCriticalPath,
16171
17178
  computeLagFromDates,
16172
17179
  computeParentDates,
@@ -16221,10 +17228,12 @@ var nameContains = (substring, caseSensitive = false) => (task) => {
16221
17228
  normalizeUTCDate,
16222
17229
  not,
16223
17230
  or,
17231
+ packIntervals,
16224
17232
  parseDateOnly,
16225
17233
  parseUTCDate,
16226
17234
  pixelsToDate,
16227
17235
  progressInRange,
17236
+ pushActivityChain,
16228
17237
  recalculateIncomingLags,
16229
17238
  recalculateProjectSchedule,
16230
17239
  recalculateTaskFromDependencies,
@@ -16234,6 +17243,8 @@ var nameContains = (substring, caseSensitive = false) => (task) => {
16234
17243
  resolveDateRangeFromPixels,
16235
17244
  resolveTaskHorizontalGeometry,
16236
17245
  scaleTaskSubtreeDuration,
17246
+ shiftActivityChain,
17247
+ shiftActivityDate,
16237
17248
  shiftBusinessDayOffset,
16238
17249
  subtractBusinessDays,
16239
17250
  universalCascade,