gantt-lib 0.145.0 → 0.145.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -192,6 +192,8 @@ interface TaskActivityDependency {
192
192
  lag?: number;
193
193
  }
194
194
  interface TaskActivity {
195
+ /** Visual branch identity: keep compatible activities together across gaps; never changes dates. */
196
+ laneGroup?: string;
195
197
  /** Unique identifier within the host task */
196
198
  id: string;
197
199
  /** Display name rendered inside the activity bar */
@@ -192,6 +192,8 @@ interface TaskActivityDependency {
192
192
  lag?: number;
193
193
  }
194
194
  interface TaskActivity {
195
+ /** Visual branch identity: keep compatible activities together across gaps; never changes dates. */
196
+ laneGroup?: string;
195
197
  /** Unique identifier within the host task */
196
198
  id: string;
197
199
  /** Display name rendered inside the activity bar */
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import React$1, { ReactNode } from 'react';
2
- import { T as Task$1, R as ResourceTimelineItem, G as GanttScheduleIntent, V as ValidationResult, a as GanttDateRange, b as TaskActivityDependency, A as ActivityDependencyHighlight, c as ActivityTooltipContext, d as TimelineMarker, e as TaskDateChangeMode, S as ScaleTaskSubtreeResult, f as ResourcePlannerChartProps, g as TaskActivity$1, h as ResourceTimelineResource } from './index-Hg8BjZ_h.mjs';
3
- export { B as BuiltInResourceTableColumnId, C as CriticalPathOptions, D as DAY_MS, i as DependencyError, j as GanttChartMode, k as GridConfig, l as GridLine, L as LinkType, M as MonthSpan, m as ResourceTableColumnId, n as ResourceTableColumnWidthMap, o as ResourceTimelineMove, p as ResourceTimelineResourceMenuCommand, q as ScaleTaskSubtreeAnchor, r as ScaleTaskSubtreeErrorCode, s as ScaleTaskSubtreeExternalPolicy, t as ScaleTaskSubtreeOptions, u as ScaleTaskSubtreeWarning, v as TaskActivityTooltipField, w as TaskBarGeometry, W as WeekendBlock, x as alignToWorkingDay, y as areTasksHierarchicallyRelated, z as buildAdjacencyList, E as buildTaskRangeFromEnd, F as buildTaskRangeFromStart, H as calculateSuccessorDate, I as cascadeByLinks, J as clampTaskRangeForIncomingFS, K as computeCriticalPath, N as computeLagFromDates, O as computeParentDates, P as computeParentProgress, Q as detectCycles, U as extendCriticalIdsToParents, X as findParentId, Y as getAllDependencyEdges, Z as getAllDescendants, _ as getBusinessDayOffset, $ as getChildren, a0 as getDependencyLag, a1 as getSuccessorChain, a2 as getTaskDuration, a3 as getTransitiveCascadeChain, a4 as isAncestorTask, a5 as isTaskParent, a6 as moveTaskRange, a7 as moveTaskWithCascade, a8 as normalizeDependencyLag, a9 as normalizePredecessorDates, aa as normalizeTaskDependencyLags, ab as normalizeUTCDate, ac as parseDateOnly, ad as recalculateIncomingLags, ae as recalculateProjectSchedule, af as recalculateTaskFromDependencies, ag as reflowTasksOnModeSwitch, ah as removeDependenciesBetweenTasks, ai as resizeTaskWithCascade, aj as scaleTaskSubtreeDuration, ak as shiftBusinessDayOffset, al as universalCascade, am as validateDependencies } from './index-Hg8BjZ_h.mjs';
2
+ import { T as Task$1, R as ResourceTimelineItem, G as GanttScheduleIntent, V as ValidationResult, a as GanttDateRange, b as TaskActivityDependency, A as ActivityDependencyHighlight, c as ActivityTooltipContext, d as TimelineMarker, e as TaskDateChangeMode, S as ScaleTaskSubtreeResult, f as ResourcePlannerChartProps, g as TaskActivity$1, h as ResourceTimelineResource } from './index-Cue-aV4x.mjs';
3
+ export { B as BuiltInResourceTableColumnId, C as CriticalPathOptions, D as DAY_MS, i as DependencyError, j as GanttChartMode, k as GridConfig, l as GridLine, L as LinkType, M as MonthSpan, m as ResourceTableColumnId, n as ResourceTableColumnWidthMap, o as ResourceTimelineMove, p as ResourceTimelineResourceMenuCommand, q as ScaleTaskSubtreeAnchor, r as ScaleTaskSubtreeErrorCode, s as ScaleTaskSubtreeExternalPolicy, t as ScaleTaskSubtreeOptions, u as ScaleTaskSubtreeWarning, v as TaskActivityTooltipField, w as TaskBarGeometry, W as WeekendBlock, x as alignToWorkingDay, y as areTasksHierarchicallyRelated, z as buildAdjacencyList, E as buildTaskRangeFromEnd, F as buildTaskRangeFromStart, H as calculateSuccessorDate, I as cascadeByLinks, J as clampTaskRangeForIncomingFS, K as computeCriticalPath, N as computeLagFromDates, O as computeParentDates, P as computeParentProgress, Q as detectCycles, U as extendCriticalIdsToParents, X as findParentId, Y as getAllDependencyEdges, Z as getAllDescendants, _ as getBusinessDayOffset, $ as getChildren, a0 as getDependencyLag, a1 as getSuccessorChain, a2 as getTaskDuration, a3 as getTransitiveCascadeChain, a4 as isAncestorTask, a5 as isTaskParent, a6 as moveTaskRange, a7 as moveTaskWithCascade, a8 as normalizeDependencyLag, a9 as normalizePredecessorDates, aa as normalizeTaskDependencyLags, ab as normalizeUTCDate, ac as parseDateOnly, ad as recalculateIncomingLags, ae as recalculateProjectSchedule, af as recalculateTaskFromDependencies, ag as reflowTasksOnModeSwitch, ah as removeDependenciesBetweenTasks, ai as resizeTaskWithCascade, aj as scaleTaskSubtreeDuration, ak as shiftBusinessDayOffset, al as universalCascade, am as validateDependencies } from './index-Cue-aV4x.mjs';
4
4
  import * as react_jsx_runtime from 'react/jsx-runtime';
5
5
  import * as RadixPopover from '@radix-ui/react-popover';
6
6
 
@@ -530,6 +530,8 @@ interface TaskActivityTooltipField {
530
530
  value: React$1.ReactNode;
531
531
  }
532
532
  interface TaskActivity {
533
+ /** Visual branch identity: keep compatible activities together across gaps; never changes dates. */
534
+ laneGroup?: string;
533
535
  /** Unique identifier within the host task */
534
536
  id: string;
535
537
  /** Display name rendered inside the activity bar */
@@ -1526,14 +1528,14 @@ interface ActivityLanesLayout {
1526
1528
  laneCount: number;
1527
1529
  }
1528
1530
  /**
1529
- * Pack intervals into the smallest number of lanes greedily (first fit):
1530
- * a sequential work lands in the first lane free at its start, a concurrent
1531
- * one opens a new lane below. Lane count therefore equals the maximum number
1532
- * of simultaneous activities.
1531
+ * Without laneGroup, first-fit packing uses the peak concurrency count.
1532
+ * Grouped intervals reserve compatible lanes across gaps to retain branch
1533
+ * continuity; that visual preference may use more lanes than peak concurrency.
1533
1534
  */
1534
1535
  declare function packIntervals<T extends {
1535
1536
  start: number;
1536
1537
  end: number;
1538
+ laneGroup?: string;
1537
1539
  }>(intervals: T[]): Array<T & {
1538
1540
  lane: number;
1539
1541
  }>;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import React$1, { ReactNode } from 'react';
2
- import { T as Task$1, R as ResourceTimelineItem, G as GanttScheduleIntent, V as ValidationResult, a as GanttDateRange, b as TaskActivityDependency, A as ActivityDependencyHighlight, c as ActivityTooltipContext, d as TimelineMarker, e as TaskDateChangeMode, S as ScaleTaskSubtreeResult, f as ResourcePlannerChartProps, g as TaskActivity$1, h as ResourceTimelineResource } from './index-Hg8BjZ_h.js';
3
- export { B as BuiltInResourceTableColumnId, C as CriticalPathOptions, D as DAY_MS, i as DependencyError, j as GanttChartMode, k as GridConfig, l as GridLine, L as LinkType, M as MonthSpan, m as ResourceTableColumnId, n as ResourceTableColumnWidthMap, o as ResourceTimelineMove, p as ResourceTimelineResourceMenuCommand, q as ScaleTaskSubtreeAnchor, r as ScaleTaskSubtreeErrorCode, s as ScaleTaskSubtreeExternalPolicy, t as ScaleTaskSubtreeOptions, u as ScaleTaskSubtreeWarning, v as TaskActivityTooltipField, w as TaskBarGeometry, W as WeekendBlock, x as alignToWorkingDay, y as areTasksHierarchicallyRelated, z as buildAdjacencyList, E as buildTaskRangeFromEnd, F as buildTaskRangeFromStart, H as calculateSuccessorDate, I as cascadeByLinks, J as clampTaskRangeForIncomingFS, K as computeCriticalPath, N as computeLagFromDates, O as computeParentDates, P as computeParentProgress, Q as detectCycles, U as extendCriticalIdsToParents, X as findParentId, Y as getAllDependencyEdges, Z as getAllDescendants, _ as getBusinessDayOffset, $ as getChildren, a0 as getDependencyLag, a1 as getSuccessorChain, a2 as getTaskDuration, a3 as getTransitiveCascadeChain, a4 as isAncestorTask, a5 as isTaskParent, a6 as moveTaskRange, a7 as moveTaskWithCascade, a8 as normalizeDependencyLag, a9 as normalizePredecessorDates, aa as normalizeTaskDependencyLags, ab as normalizeUTCDate, ac as parseDateOnly, ad as recalculateIncomingLags, ae as recalculateProjectSchedule, af as recalculateTaskFromDependencies, ag as reflowTasksOnModeSwitch, ah as removeDependenciesBetweenTasks, ai as resizeTaskWithCascade, aj as scaleTaskSubtreeDuration, ak as shiftBusinessDayOffset, al as universalCascade, am as validateDependencies } from './index-Hg8BjZ_h.js';
2
+ import { T as Task$1, R as ResourceTimelineItem, G as GanttScheduleIntent, V as ValidationResult, a as GanttDateRange, b as TaskActivityDependency, A as ActivityDependencyHighlight, c as ActivityTooltipContext, d as TimelineMarker, e as TaskDateChangeMode, S as ScaleTaskSubtreeResult, f as ResourcePlannerChartProps, g as TaskActivity$1, h as ResourceTimelineResource } from './index-Cue-aV4x.js';
3
+ export { B as BuiltInResourceTableColumnId, C as CriticalPathOptions, D as DAY_MS, i as DependencyError, j as GanttChartMode, k as GridConfig, l as GridLine, L as LinkType, M as MonthSpan, m as ResourceTableColumnId, n as ResourceTableColumnWidthMap, o as ResourceTimelineMove, p as ResourceTimelineResourceMenuCommand, q as ScaleTaskSubtreeAnchor, r as ScaleTaskSubtreeErrorCode, s as ScaleTaskSubtreeExternalPolicy, t as ScaleTaskSubtreeOptions, u as ScaleTaskSubtreeWarning, v as TaskActivityTooltipField, w as TaskBarGeometry, W as WeekendBlock, x as alignToWorkingDay, y as areTasksHierarchicallyRelated, z as buildAdjacencyList, E as buildTaskRangeFromEnd, F as buildTaskRangeFromStart, H as calculateSuccessorDate, I as cascadeByLinks, J as clampTaskRangeForIncomingFS, K as computeCriticalPath, N as computeLagFromDates, O as computeParentDates, P as computeParentProgress, Q as detectCycles, U as extendCriticalIdsToParents, X as findParentId, Y as getAllDependencyEdges, Z as getAllDescendants, _ as getBusinessDayOffset, $ as getChildren, a0 as getDependencyLag, a1 as getSuccessorChain, a2 as getTaskDuration, a3 as getTransitiveCascadeChain, a4 as isAncestorTask, a5 as isTaskParent, a6 as moveTaskRange, a7 as moveTaskWithCascade, a8 as normalizeDependencyLag, a9 as normalizePredecessorDates, aa as normalizeTaskDependencyLags, ab as normalizeUTCDate, ac as parseDateOnly, ad as recalculateIncomingLags, ae as recalculateProjectSchedule, af as recalculateTaskFromDependencies, ag as reflowTasksOnModeSwitch, ah as removeDependenciesBetweenTasks, ai as resizeTaskWithCascade, aj as scaleTaskSubtreeDuration, ak as shiftBusinessDayOffset, al as universalCascade, am as validateDependencies } from './index-Cue-aV4x.js';
4
4
  import * as react_jsx_runtime from 'react/jsx-runtime';
5
5
  import * as RadixPopover from '@radix-ui/react-popover';
6
6
 
@@ -530,6 +530,8 @@ interface TaskActivityTooltipField {
530
530
  value: React$1.ReactNode;
531
531
  }
532
532
  interface TaskActivity {
533
+ /** Visual branch identity: keep compatible activities together across gaps; never changes dates. */
534
+ laneGroup?: string;
533
535
  /** Unique identifier within the host task */
534
536
  id: string;
535
537
  /** Display name rendered inside the activity bar */
@@ -1526,14 +1528,14 @@ interface ActivityLanesLayout {
1526
1528
  laneCount: number;
1527
1529
  }
1528
1530
  /**
1529
- * Pack intervals into the smallest number of lanes greedily (first fit):
1530
- * a sequential work lands in the first lane free at its start, a concurrent
1531
- * one opens a new lane below. Lane count therefore equals the maximum number
1532
- * of simultaneous activities.
1531
+ * Without laneGroup, first-fit packing uses the peak concurrency count.
1532
+ * Grouped intervals reserve compatible lanes across gaps to retain branch
1533
+ * continuity; that visual preference may use more lanes than peak concurrency.
1533
1534
  */
1534
1535
  declare function packIntervals<T extends {
1535
1536
  start: number;
1536
1537
  end: number;
1538
+ laneGroup?: string;
1537
1539
  }>(intervals: T[]): Array<T & {
1538
1540
  lane: number;
1539
1541
  }>;
package/dist/index.js CHANGED
@@ -896,6 +896,29 @@ var ACTIVITY_LANES_PADDING = 2;
896
896
  var DAY_MS2 = 24 * 60 * 60 * 1e3;
897
897
  function packIntervals(intervals) {
898
898
  const sorted = [...intervals].sort((a, b) => a.start - b.start);
899
+ if (intervals.some((item) => item.laneGroup !== void 0)) {
900
+ const groups = /* @__PURE__ */ new Map();
901
+ for (const item of intervals) {
902
+ const key = item.laneGroup ?? item;
903
+ const group = groups.get(key) ?? [];
904
+ group.push(item);
905
+ groups.set(key, group);
906
+ }
907
+ const lanes = [];
908
+ const assigned = /* @__PURE__ */ new Map();
909
+ for (const group of groups.values()) {
910
+ const compatible = lanes.findIndex((lane) => group.every((item) => !lane.some((other) => item.start < other.end && other.start < item.end)));
911
+ const preferred = compatible < 0 ? lanes.length : compatible;
912
+ for (const item of [...group].sort((a, b) => a.start - b.start)) {
913
+ const free = (lane2) => !lanes[lane2]?.some((other) => item.start < other.end && other.start < item.end);
914
+ let lane = free(preferred) ? preferred : lanes.findIndex((_, index) => free(index));
915
+ if (lane < 0) lane = lanes.length;
916
+ (lanes[lane] ?? (lanes[lane] = [])).push(item);
917
+ assigned.set(item, lane);
918
+ }
919
+ }
920
+ return sorted.map((item) => ({ ...item, lane: assigned.get(item) }));
921
+ }
899
922
  const laneEnds = [];
900
923
  return sorted.map((item) => {
901
924
  let lane = 0;
@@ -914,6 +937,7 @@ function computeActivityLanes(activities, monthStart, dayWidth) {
914
937
  const bar = calculateTaskBar(parseUTCDate(activity.startDate), parseUTCDate(activity.endDate), monthStart, dayWidth);
915
938
  return {
916
939
  id: activity.id,
940
+ laneGroup: activity.laneGroup,
917
941
  name: activity.name,
918
942
  color: activity.color,
919
943
  lag: activity.lag,
@@ -2209,8 +2233,8 @@ function recalculateProjectSchedule(snapshot, options) {
2209
2233
  }
2210
2234
  }
2211
2235
  const queue = snapshot.filter((task) => (indegree.get(task.id) ?? 0) === 0).map((task) => task.id);
2212
- while (queue.length > 0) {
2213
- const currentId = queue.shift();
2236
+ for (let queueIndex = 0; queueIndex < queue.length; queueIndex++) {
2237
+ const currentId = queue[queueIndex];
2214
2238
  for (const successorId of successorIdsByTask.get(currentId) ?? []) {
2215
2239
  const nextIndegree = (indegree.get(successorId) ?? 0) - 1;
2216
2240
  indegree.set(successorId, nextIndegree);
@@ -2261,7 +2285,13 @@ function recalculateProjectSchedule(snapshot, options) {
2261
2285
  queue.push(successorId);
2262
2286
  }
2263
2287
  }
2264
- const parentsByDepth = snapshot.filter((task) => isTaskParent(task.id, snapshot)).map((task) => {
2288
+ const childrenByParent = /* @__PURE__ */ new Map();
2289
+ for (const task of snapshot) if (task.parentId) {
2290
+ const children = childrenByParent.get(task.parentId) ?? [];
2291
+ children.push(task.id);
2292
+ childrenByParent.set(task.parentId, children);
2293
+ }
2294
+ const parentsByDepth = snapshot.filter((task) => childrenByParent.has(task.id)).map((task) => {
2265
2295
  let depth = 0;
2266
2296
  let current = task.parentId ? workingMap.get(task.parentId) : void 0;
2267
2297
  while (current) {
@@ -2270,17 +2300,21 @@ function recalculateProjectSchedule(snapshot, options) {
2270
2300
  }
2271
2301
  return { taskId: task.id, depth };
2272
2302
  }).sort((left, right) => right.depth - left.depth);
2273
- const workingTasks = () => Array.from(workingMap.values());
2274
2303
  for (const { taskId } of parentsByDepth) {
2275
2304
  const parent = workingMap.get(taskId);
2276
2305
  if (!parent || parent.locked) {
2277
2306
  continue;
2278
2307
  }
2279
- const { startDate, endDate } = computeParentDates(taskId, workingTasks());
2308
+ let startTime = Infinity, endTime = -Infinity;
2309
+ for (const childId of childrenByParent.get(taskId)) {
2310
+ const child = workingMap.get(childId);
2311
+ startTime = Math.min(startTime, new Date(child.startDate).getTime());
2312
+ endTime = Math.max(endTime, new Date(child.endDate).getTime());
2313
+ }
2280
2314
  workingMap.set(taskId, {
2281
2315
  ...parent,
2282
- startDate: toIsoDate(startDate),
2283
- endDate: toIsoDate(endDate)
2316
+ startDate: toIsoDate(new Date(startTime)),
2317
+ endDate: toIsoDate(new Date(endTime))
2284
2318
  });
2285
2319
  }
2286
2320
  return createChangedResult(snapshot, Array.from(workingMap.values()));