gantt-lib 0.110.1 → 0.112.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.
@@ -1,2 +1,2 @@
1
- export { D as DAY_MS, e as DependencyError, L as LinkType, a7 as ScheduleCommandOptions, a8 as ScheduleCommandResult, a9 as ScheduleDependency, aa as ScheduleTask, ab as ScheduleTaskUpdate, T as Task, ac as TaskDependency, V as ValidationResult, ad as addBusinessDays, n as alignToWorkingDay, o as areTasksHierarchicallyRelated, p as buildAdjacencyList, q as buildTaskRangeFromEnd, r as buildTaskRangeFromStart, s as calculateSuccessorDate, t as cascadeByLinks, u as clampTaskRangeForIncomingFS, v as computeLagFromDates, w as computeParentDates, x as computeParentProgress, y as detectCycles, z as findParentId, A as getAllDependencyEdges, C as getAllDescendants, E as getBusinessDayOffset, ae as getBusinessDaysCount, F as getChildren, H as getDependencyLag, I as getSuccessorChain, J as getTaskDuration, K as getTransitiveCascadeChain, N as isAncestorTask, O as isTaskParent, P as moveTaskRange, Q as moveTaskWithCascade, S as normalizeDependencyLag, U as normalizePredecessorDates, X as normalizeTaskDependencyLags, Y as normalizeUTCDate, Z as parseDateOnly, _ as recalculateIncomingLags, $ as recalculateProjectSchedule, a0 as recalculateTaskFromDependencies, a1 as reflowTasksOnModeSwitch, a2 as removeDependenciesBetweenTasks, a3 as resizeTaskWithCascade, a4 as shiftBusinessDayOffset, af as subtractBusinessDays, a5 as universalCascade, a6 as validateDependencies } from '../../index-Ci6WiKhW.mjs';
1
+ export { a7 as CascadeContext, D as DAY_MS, e as DependencyError, L as LinkType, a8 as ScheduleCommandOptions, a9 as ScheduleCommandResult, aa as ScheduleDependency, ab as ScheduleTask, ac as ScheduleTaskUpdate, T as Task, ad as TaskDependency, V as ValidationResult, ae as addBusinessDays, n as alignToWorkingDay, o as areTasksHierarchicallyRelated, p as buildAdjacencyList, q as buildTaskRangeFromEnd, r as buildTaskRangeFromStart, s as calculateSuccessorDate, t as cascadeByLinks, u as clampTaskRangeForIncomingFS, v as computeLagFromDates, w as computeParentDates, x as computeParentProgress, af as createCascadeContext, y as detectCycles, z as findParentId, A as getAllDependencyEdges, C as getAllDescendants, E as getBusinessDayOffset, ag as getBusinessDaysCount, F as getChildren, H as getDependencyLag, I as getSuccessorChain, J as getTaskDuration, K as getTransitiveCascadeChain, N as isAncestorTask, O as isTaskParent, P as moveTaskRange, Q as moveTaskWithCascade, S as normalizeDependencyLag, U as normalizePredecessorDates, X as normalizeTaskDependencyLags, Y as normalizeUTCDate, Z as parseDateOnly, _ as recalculateIncomingLags, $ as recalculateProjectSchedule, a0 as recalculateTaskFromDependencies, a1 as reflowTasksOnModeSwitch, a2 as removeDependenciesBetweenTasks, a3 as resizeTaskWithCascade, a4 as shiftBusinessDayOffset, ah as subtractBusinessDays, a5 as universalCascade, a6 as validateDependencies } from '../../index-D1s_8MxS.mjs';
2
2
  import 'react';
@@ -1,2 +1,2 @@
1
- export { D as DAY_MS, e as DependencyError, L as LinkType, a7 as ScheduleCommandOptions, a8 as ScheduleCommandResult, a9 as ScheduleDependency, aa as ScheduleTask, ab as ScheduleTaskUpdate, T as Task, ac as TaskDependency, V as ValidationResult, ad as addBusinessDays, n as alignToWorkingDay, o as areTasksHierarchicallyRelated, p as buildAdjacencyList, q as buildTaskRangeFromEnd, r as buildTaskRangeFromStart, s as calculateSuccessorDate, t as cascadeByLinks, u as clampTaskRangeForIncomingFS, v as computeLagFromDates, w as computeParentDates, x as computeParentProgress, y as detectCycles, z as findParentId, A as getAllDependencyEdges, C as getAllDescendants, E as getBusinessDayOffset, ae as getBusinessDaysCount, F as getChildren, H as getDependencyLag, I as getSuccessorChain, J as getTaskDuration, K as getTransitiveCascadeChain, N as isAncestorTask, O as isTaskParent, P as moveTaskRange, Q as moveTaskWithCascade, S as normalizeDependencyLag, U as normalizePredecessorDates, X as normalizeTaskDependencyLags, Y as normalizeUTCDate, Z as parseDateOnly, _ as recalculateIncomingLags, $ as recalculateProjectSchedule, a0 as recalculateTaskFromDependencies, a1 as reflowTasksOnModeSwitch, a2 as removeDependenciesBetweenTasks, a3 as resizeTaskWithCascade, a4 as shiftBusinessDayOffset, af as subtractBusinessDays, a5 as universalCascade, a6 as validateDependencies } from '../../index-Ci6WiKhW.js';
1
+ export { a7 as CascadeContext, D as DAY_MS, e as DependencyError, L as LinkType, a8 as ScheduleCommandOptions, a9 as ScheduleCommandResult, aa as ScheduleDependency, ab as ScheduleTask, ac as ScheduleTaskUpdate, T as Task, ad as TaskDependency, V as ValidationResult, ae as addBusinessDays, n as alignToWorkingDay, o as areTasksHierarchicallyRelated, p as buildAdjacencyList, q as buildTaskRangeFromEnd, r as buildTaskRangeFromStart, s as calculateSuccessorDate, t as cascadeByLinks, u as clampTaskRangeForIncomingFS, v as computeLagFromDates, w as computeParentDates, x as computeParentProgress, af as createCascadeContext, y as detectCycles, z as findParentId, A as getAllDependencyEdges, C as getAllDescendants, E as getBusinessDayOffset, ag as getBusinessDaysCount, F as getChildren, H as getDependencyLag, I as getSuccessorChain, J as getTaskDuration, K as getTransitiveCascadeChain, N as isAncestorTask, O as isTaskParent, P as moveTaskRange, Q as moveTaskWithCascade, S as normalizeDependencyLag, U as normalizePredecessorDates, X as normalizeTaskDependencyLags, Y as normalizeUTCDate, Z as parseDateOnly, _ as recalculateIncomingLags, $ as recalculateProjectSchedule, a0 as recalculateTaskFromDependencies, a1 as reflowTasksOnModeSwitch, a2 as removeDependenciesBetweenTasks, a3 as resizeTaskWithCascade, a4 as shiftBusinessDayOffset, ah as subtractBusinessDays, a5 as universalCascade, a6 as validateDependencies } from '../../index-D1s_8MxS.js';
2
2
  import 'react';
@@ -33,6 +33,7 @@ __export(scheduling_exports, {
33
33
  computeLagFromDates: () => computeLagFromDates,
34
34
  computeParentDates: () => computeParentDates,
35
35
  computeParentProgress: () => computeParentProgress,
36
+ createCascadeContext: () => createCascadeContext,
36
37
  detectCycles: () => detectCycles,
37
38
  findParentId: () => findParentId,
38
39
  getAllDependencyEdges: () => getAllDependencyEdges,
@@ -276,126 +277,6 @@ function calculateSuccessorDate(predecessorStart, predecessorEnd, linkType, lag
276
277
  return shiftBusinessDayOffset(anchorDate, offset, weekendPredicate);
277
278
  }
278
279
 
279
- // src/core/scheduling/hierarchy.ts
280
- function getChildren(parentId, tasks) {
281
- return tasks.filter((t) => t.parentId === parentId);
282
- }
283
- function isTaskParent(taskId, tasks) {
284
- return tasks.some((t) => t.parentId === taskId);
285
- }
286
- function computeParentDates(parentId, tasks) {
287
- const children = getChildren(parentId, tasks);
288
- if (children.length === 0) {
289
- const parent = tasks.find((t) => t.id === parentId);
290
- const start = parent ? new Date(parent.startDate) : /* @__PURE__ */ new Date();
291
- const end = parent ? new Date(parent.endDate) : /* @__PURE__ */ new Date();
292
- return { startDate: start, endDate: end };
293
- }
294
- const startDates = children.map((c) => new Date(c.startDate));
295
- const endDates = children.map((c) => new Date(c.endDate));
296
- const minTime = Math.min(...startDates.map((d) => d.getTime()));
297
- const maxTime = Math.max(...endDates.map((d) => d.getTime()));
298
- return {
299
- startDate: new Date(minTime),
300
- endDate: new Date(maxTime)
301
- };
302
- }
303
- function computeParentProgress(parentId, tasks) {
304
- const children = getChildren(parentId, tasks);
305
- if (children.length === 0) {
306
- return 0;
307
- }
308
- const DAY_MS2 = 24 * 60 * 60 * 1e3;
309
- let totalWeight = 0;
310
- let weightedSum = 0;
311
- for (const child of children) {
312
- const start = new Date(child.startDate).getTime();
313
- const end = new Date(child.endDate).getTime();
314
- const duration = (end - start + DAY_MS2) / DAY_MS2;
315
- const progress = child.progress ?? 0;
316
- totalWeight += duration;
317
- weightedSum += duration * progress;
318
- }
319
- if (totalWeight === 0) {
320
- return 0;
321
- }
322
- return Math.round(weightedSum / totalWeight * 10) / 10;
323
- }
324
- function getAllDescendants(parentId, tasks) {
325
- const descendants = [];
326
- const visited = /* @__PURE__ */ new Set();
327
- function collectChildren(taskId) {
328
- if (visited.has(taskId)) return;
329
- visited.add(taskId);
330
- const children = getChildren(taskId, tasks);
331
- for (const child of children) {
332
- descendants.push(child);
333
- collectChildren(child.id);
334
- }
335
- }
336
- collectChildren(parentId);
337
- return descendants;
338
- }
339
- function getAllDependencyEdges(tasks) {
340
- const edges = [];
341
- for (const task of tasks) {
342
- if (task.dependencies) {
343
- for (const dep of task.dependencies) {
344
- edges.push({
345
- predecessorId: dep.taskId,
346
- successorId: task.id,
347
- type: dep.type,
348
- lag: dep.lag ?? 0
349
- });
350
- }
351
- }
352
- }
353
- return edges;
354
- }
355
- function removeDependenciesBetweenTasks(taskId1, taskId2, tasks) {
356
- return tasks.map((task) => {
357
- if (task.id === taskId1 || task.id === taskId2) {
358
- if (!task.dependencies) return task;
359
- const otherTaskId = task.id === taskId1 ? taskId2 : taskId1;
360
- const filteredDependencies = task.dependencies.filter((dep) => dep.taskId !== otherTaskId);
361
- if (filteredDependencies.length === task.dependencies.length) {
362
- return task;
363
- }
364
- return {
365
- ...task,
366
- dependencies: filteredDependencies.length > 0 ? filteredDependencies : void 0
367
- };
368
- }
369
- return task;
370
- });
371
- }
372
- function findParentId(taskId, tasks) {
373
- const task = tasks.find((t) => t.id === taskId);
374
- return task?.parentId;
375
- }
376
- function isAncestorTask(ancestorId, taskId, tasks) {
377
- const taskById = new Map(tasks.map((task) => [task.id, task]));
378
- const visited = /* @__PURE__ */ new Set();
379
- let current = taskById.get(taskId);
380
- while (current?.parentId) {
381
- if (current.parentId === ancestorId) {
382
- return true;
383
- }
384
- if (visited.has(current.parentId)) {
385
- return false;
386
- }
387
- visited.add(current.parentId);
388
- current = taskById.get(current.parentId);
389
- }
390
- return false;
391
- }
392
- function areTasksHierarchicallyRelated(taskId1, taskId2, tasks) {
393
- if (taskId1 === taskId2) {
394
- return true;
395
- }
396
- return isAncestorTask(taskId1, taskId2, tasks) || isAncestorTask(taskId2, taskId1, tasks);
397
- }
398
-
399
280
  // src/core/scheduling/commands.ts
400
281
  function buildTaskRangeFromStart(startDate, duration, businessDays = false, weekendPredicate, snapDirection = 1) {
401
282
  const normalizedStart = businessDays && weekendPredicate ? alignToWorkingDay(startDate, snapDirection, weekendPredicate) : normalizeUTCDate(startDate);
@@ -434,7 +315,7 @@ function moveTaskRange(originalStart, originalEnd, proposedStart, businessDays =
434
315
  snapDirection
435
316
  );
436
317
  }
437
- function clampTaskRangeForIncomingFS(task, proposedStart, proposedEnd, allTasks, businessDays = false, weekendPredicate) {
318
+ function clampTaskRangeForIncomingFS(task, proposedStart, proposedEnd, allTasks, businessDays = false, weekendPredicate, taskById) {
438
319
  if (!task.dependencies?.length) {
439
320
  return { start: proposedStart, end: proposedEnd };
440
321
  }
@@ -443,7 +324,7 @@ function clampTaskRangeForIncomingFS(task, proposedStart, proposedEnd, allTasks,
443
324
  if (dep.type !== "FS") {
444
325
  continue;
445
326
  }
446
- const predecessor = allTasks.find((candidate) => candidate.id === dep.taskId);
327
+ const predecessor = taskById?.get(dep.taskId) ?? allTasks.find((candidate) => candidate.id === dep.taskId);
447
328
  if (!predecessor) {
448
329
  continue;
449
330
  }
@@ -476,10 +357,10 @@ function clampTaskRangeForIncomingFS(task, proposedStart, proposedEnd, allTasks,
476
357
  weekendPredicate
477
358
  );
478
359
  }
479
- function recalculateIncomingLags(task, newStartDate, newEndDate, allTasks, businessDays = false, weekendPredicate) {
360
+ function recalculateIncomingLags(task, newStartDate, newEndDate, allTasks, businessDays = false, weekendPredicate, taskById) {
480
361
  if (!task.dependencies) return [];
481
362
  return task.dependencies.map((dep) => {
482
- const predecessor = allTasks.find((candidate) => candidate.id === dep.taskId);
363
+ const predecessor = taskById?.get(dep.taskId) ?? allTasks.find((candidate) => candidate.id === dep.taskId);
483
364
  if (!predecessor) {
484
365
  return { ...dep, lag: getDependencyLag(dep) };
485
366
  }
@@ -507,35 +388,45 @@ function parseCascadeDateInput(date) {
507
388
  }
508
389
  return normalizeUTCDate(/* @__PURE__ */ new Date(`${date.split("T")[0]}T00:00:00.000Z`));
509
390
  }
510
- function getSuccessorChain(draggedTaskId, allTasks, linkTypes = ["FS"]) {
511
- const successorMap = /* @__PURE__ */ new Map();
512
- for (const task of allTasks) {
513
- successorMap.set(task.id, []);
514
- }
391
+ function createCascadeContext(allTasks) {
392
+ const childrenByParentId = /* @__PURE__ */ new Map();
393
+ const dependentsByPredecessorId = /* @__PURE__ */ new Map();
394
+ const taskById = /* @__PURE__ */ new Map();
515
395
  for (const task of allTasks) {
516
- if (!task.dependencies) continue;
517
- for (const dep of task.dependencies) {
518
- if (linkTypes.includes(dep.type)) {
519
- const list = successorMap.get(dep.taskId) ?? [];
520
- list.push(task.id);
521
- successorMap.set(dep.taskId, list);
522
- }
396
+ taskById.set(task.id, task);
397
+ if (task.parentId) {
398
+ const children = childrenByParentId.get(task.parentId) ?? [];
399
+ children.push(task);
400
+ childrenByParentId.set(task.parentId, children);
523
401
  }
402
+ if (!task.dependencies) continue;
403
+ task.dependencies.forEach((dependency, dependencyIndex) => {
404
+ const dependents = dependentsByPredecessorId.get(dependency.taskId) ?? [];
405
+ dependents.push({ task, dependencyIndex });
406
+ dependentsByPredecessorId.set(dependency.taskId, dependents);
407
+ });
524
408
  }
525
- const taskById = new Map(allTasks.map((t) => [t.id, t]));
409
+ return { childrenByParentId, dependentsByPredecessorId, taskById };
410
+ }
411
+ function getSuccessorChain(draggedTaskId, allTasks, linkTypes = ["FS"]) {
412
+ const { dependentsByPredecessorId, taskById } = createCascadeContext(allTasks);
526
413
  const visited = /* @__PURE__ */ new Set();
527
414
  const queue = [draggedTaskId];
528
415
  const chain = [];
529
416
  visited.add(draggedTaskId);
530
417
  while (queue.length > 0) {
531
418
  const current = queue.shift();
532
- const successors = successorMap.get(current) ?? [];
533
- for (const sid of successors) {
419
+ const successors = dependentsByPredecessorId.get(current) ?? [];
420
+ for (const { task } of successors) {
421
+ if (!linkTypes.includes(task.dependencies?.find((dependency) => dependency.taskId === current)?.type ?? "FS")) {
422
+ continue;
423
+ }
424
+ const sid = task.id;
534
425
  if (!visited.has(sid)) {
535
426
  visited.add(sid);
536
- const t = taskById.get(sid);
537
- if (t) {
538
- chain.push(t);
427
+ const successorTask = taskById.get(sid);
428
+ if (successorTask) {
429
+ chain.push(successorTask);
539
430
  queue.push(sid);
540
431
  }
541
432
  }
@@ -544,7 +435,7 @@ function getSuccessorChain(draggedTaskId, allTasks, linkTypes = ["FS"]) {
544
435
  return chain;
545
436
  }
546
437
  function cascadeByLinks(movedTaskId, newStart, newEnd, allTasks, skipChildCascade = false) {
547
- const taskById = new Map(allTasks.map((t) => [t.id, t]));
438
+ const { childrenByParentId, dependentsByPredecessorId, taskById } = createCascadeContext(allTasks);
548
439
  const updatedDates = /* @__PURE__ */ new Map();
549
440
  updatedDates.set(movedTaskId, { start: newStart, end: newEnd });
550
441
  const result = [];
@@ -554,7 +445,7 @@ function cascadeByLinks(movedTaskId, newStart, newEnd, allTasks, skipChildCascad
554
445
  const currentId = queue.shift();
555
446
  const { start: predStart, end: predEnd } = updatedDates.get(currentId);
556
447
  if (!skipChildCascade) {
557
- const children = getChildren(currentId, allTasks);
448
+ const children = childrenByParentId.get(currentId) ?? [];
558
449
  for (const child of children) {
559
450
  if (visited.has(child.id) || child.locked) continue;
560
451
  const origStart = new Date(child.startDate);
@@ -577,64 +468,52 @@ function cascadeByLinks(movedTaskId, newStart, newEnd, allTasks, skipChildCascad
577
468
  queue.push(child.id);
578
469
  }
579
470
  }
580
- for (const task of allTasks) {
471
+ const dependents = dependentsByPredecessorId.get(currentId) ?? [];
472
+ for (const { task, dependencyIndex } of dependents) {
581
473
  if (visited.has(task.id) || !task.dependencies || task.locked) continue;
582
- for (const dep of task.dependencies) {
583
- if (dep.taskId !== currentId) continue;
584
- const orig = taskById.get(task.id);
585
- const origStart = new Date(orig.startDate);
586
- const origEnd = new Date(orig.endDate);
587
- const duration = getTaskDuration(origStart, origEnd);
588
- const currentTask = taskById.get(currentId);
589
- const { predStart: normalizedPredStart, predEnd: normalizedPredEnd } = normalizePredecessorDates(
590
- {
591
- startDate: predStart,
592
- endDate: predEnd,
593
- type: currentTask.type
594
- },
595
- parseCascadeDateInput
596
- );
597
- const constraintDate = calculateSuccessorDate(
598
- normalizedPredStart,
599
- normalizedPredEnd,
600
- dep.type,
601
- getDependencyLag(dep)
602
- );
603
- let newSuccStart;
604
- let newSuccEnd;
605
- if (dep.type === "FS" || dep.type === "SS") {
606
- ({ start: newSuccStart, end: newSuccEnd } = buildTaskRangeFromStart(constraintDate, duration));
607
- } else {
608
- ({ start: newSuccStart, end: newSuccEnd } = buildTaskRangeFromEnd(constraintDate, duration));
609
- }
610
- visited.add(task.id);
611
- updatedDates.set(task.id, { start: newSuccStart, end: newSuccEnd });
612
- result.push(normalizeTaskDependencyLags({
613
- ...task,
614
- startDate: newSuccStart.toISOString().split("T")[0],
615
- endDate: newSuccEnd.toISOString().split("T")[0]
616
- }));
617
- queue.push(task.id);
618
- break;
474
+ const dep = task.dependencies[dependencyIndex];
475
+ if (!dep) continue;
476
+ const orig = taskById.get(task.id);
477
+ const origStart = new Date(orig.startDate);
478
+ const origEnd = new Date(orig.endDate);
479
+ const duration = getTaskDuration(origStart, origEnd);
480
+ const currentTask = taskById.get(currentId);
481
+ const { predStart: normalizedPredStart, predEnd: normalizedPredEnd } = normalizePredecessorDates(
482
+ {
483
+ startDate: predStart,
484
+ endDate: predEnd,
485
+ type: currentTask.type
486
+ },
487
+ parseCascadeDateInput
488
+ );
489
+ const constraintDate = calculateSuccessorDate(
490
+ normalizedPredStart,
491
+ normalizedPredEnd,
492
+ dep.type,
493
+ getDependencyLag(dep)
494
+ );
495
+ let newSuccStart;
496
+ let newSuccEnd;
497
+ if (dep.type === "FS" || dep.type === "SS") {
498
+ ({ start: newSuccStart, end: newSuccEnd } = buildTaskRangeFromStart(constraintDate, duration));
499
+ } else {
500
+ ({ start: newSuccStart, end: newSuccEnd } = buildTaskRangeFromEnd(constraintDate, duration));
619
501
  }
502
+ visited.add(task.id);
503
+ updatedDates.set(task.id, { start: newSuccStart, end: newSuccEnd });
504
+ result.push(normalizeTaskDependencyLags({
505
+ ...task,
506
+ startDate: newSuccStart.toISOString().split("T")[0],
507
+ endDate: newSuccEnd.toISOString().split("T")[0]
508
+ }));
509
+ queue.push(task.id);
620
510
  }
621
511
  }
622
512
  return result;
623
513
  }
624
514
  function getTransitiveCascadeChain(changedTaskId, allTasks, firstLevelLinkTypes) {
625
- const allTypesSuccessorMap = /* @__PURE__ */ new Map();
626
- for (const task of allTasks) {
627
- allTypesSuccessorMap.set(task.id, []);
628
- }
629
- for (const task of allTasks) {
630
- if (!task.dependencies) continue;
631
- for (const dep of task.dependencies) {
632
- const list = allTypesSuccessorMap.get(dep.taskId) ?? [];
633
- list.push(task);
634
- allTypesSuccessorMap.set(dep.taskId, list);
635
- }
636
- }
637
- const directChildren = getChildren(changedTaskId, allTasks);
515
+ const { childrenByParentId, dependentsByPredecessorId } = createCascadeContext(allTasks);
516
+ const directChildren = childrenByParentId.get(changedTaskId) ?? [];
638
517
  const directSuccessors = getSuccessorChain(changedTaskId, allTasks, firstLevelLinkTypes);
639
518
  const initialChain = [...directChildren, ...directSuccessors].filter(
640
519
  (task, index, arr) => arr.findIndex((candidate) => candidate.id === task.id) === index
@@ -644,7 +523,7 @@ function getTransitiveCascadeChain(changedTaskId, allTasks, firstLevelLinkTypes)
644
523
  const queue = [...initialChain];
645
524
  while (queue.length > 0) {
646
525
  const current = queue.shift();
647
- const children = getChildren(current.id, allTasks);
526
+ const children = childrenByParentId.get(current.id) ?? [];
648
527
  for (const child of children) {
649
528
  if (!visited.has(child.id)) {
650
529
  visited.add(child.id);
@@ -652,8 +531,8 @@ function getTransitiveCascadeChain(changedTaskId, allTasks, firstLevelLinkTypes)
652
531
  queue.push(child);
653
532
  }
654
533
  }
655
- const successors = allTypesSuccessorMap.get(current.id) ?? [];
656
- for (const successor of successors) {
534
+ const successors = dependentsByPredecessorId.get(current.id) ?? [];
535
+ for (const { task: successor } of successors) {
657
536
  if (!visited.has(successor.id)) {
658
537
  visited.add(successor.id);
659
538
  chain.push(successor);
@@ -663,16 +542,20 @@ function getTransitiveCascadeChain(changedTaskId, allTasks, firstLevelLinkTypes)
663
542
  }
664
543
  return chain;
665
544
  }
666
- function universalCascade(movedTask, newStart, newEnd, allTasks, businessDays = false, weekendPredicate) {
667
- const taskById = new Map(allTasks.map((t) => [t.id, t]));
668
- const updatedDates = /* @__PURE__ */ new Map();
669
- updatedDates.set(movedTask.id, { start: newStart, end: newEnd });
670
- const resultMap = /* @__PURE__ */ new Map();
671
- resultMap.set(movedTask.id, normalizeTaskDependencyLags({
545
+ function universalCascade(movedTask, newStart, newEnd, allTasks, businessDays = false, weekendPredicate, context = createCascadeContext(allTasks)) {
546
+ const { childrenByParentId, dependentsByPredecessorId, taskById } = context;
547
+ const normalizedMovedTask = normalizeTaskDependencyLags({
672
548
  ...movedTask,
673
549
  startDate: newStart.toISOString().split("T")[0],
674
550
  endDate: newEnd.toISOString().split("T")[0]
675
- }));
551
+ });
552
+ if (!movedTask.parentId && !childrenByParentId.has(movedTask.id) && !dependentsByPredecessorId.has(movedTask.id)) {
553
+ return [normalizedMovedTask];
554
+ }
555
+ const updatedDates = /* @__PURE__ */ new Map();
556
+ updatedDates.set(movedTask.id, { start: newStart, end: newEnd });
557
+ const resultMap = /* @__PURE__ */ new Map();
558
+ resultMap.set(movedTask.id, normalizedMovedTask);
676
559
  const queue = [[movedTask.id, "direct"]];
677
560
  const childShifted = /* @__PURE__ */ new Set();
678
561
  let iterations = 0;
@@ -683,7 +566,7 @@ function universalCascade(movedTask, newStart, newEnd, allTasks, businessDays =
683
566
  const { start: currStart, end: currEnd } = updatedDates.get(currentId);
684
567
  const currentOriginal = taskById.get(currentId);
685
568
  if (arrivalMode !== "parent-recalc") {
686
- const children = getChildren(currentId, allTasks);
569
+ const children = childrenByParentId.get(currentId) ?? [];
687
570
  for (const child of children) {
688
571
  if (childShifted.has(child.id) || child.locked) continue;
689
572
  const parentOrigStart = new Date(currentOriginal.startDate);
@@ -729,7 +612,7 @@ function universalCascade(movedTask, newStart, newEnd, allTasks, businessDays =
729
612
  if (parentId) {
730
613
  const parent = taskById.get(parentId);
731
614
  if (parent && !parent.locked) {
732
- const siblings = getChildren(parentId, allTasks);
615
+ const siblings = childrenByParentId.get(parentId) ?? [];
733
616
  const siblingPositions = siblings.map((sib) => {
734
617
  if (updatedDates.has(sib.id)) return updatedDates.get(sib.id);
735
618
  return { start: new Date(sib.startDate), end: new Date(sib.endDate) };
@@ -748,9 +631,10 @@ function universalCascade(movedTask, newStart, newEnd, allTasks, businessDays =
748
631
  }
749
632
  }
750
633
  }
751
- for (const task of allTasks) {
634
+ const dependents = dependentsByPredecessorId.get(currentId) ?? [];
635
+ for (const { task, dependencyIndex } of dependents) {
752
636
  if (task.locked || !task.dependencies) continue;
753
- const dep = task.dependencies.find((d) => d.taskId === currentId);
637
+ const dep = task.dependencies[dependencyIndex];
754
638
  if (!dep) continue;
755
639
  const origStart = new Date(task.startDate);
756
640
  const origEnd = new Date(task.endDate);
@@ -804,6 +688,126 @@ function universalCascade(movedTask, newStart, newEnd, allTasks, businessDays =
804
688
  return Array.from(resultMap.values());
805
689
  }
806
690
 
691
+ // src/core/scheduling/hierarchy.ts
692
+ function getChildren(parentId, tasks) {
693
+ return tasks.filter((t) => t.parentId === parentId);
694
+ }
695
+ function isTaskParent(taskId, tasks) {
696
+ return tasks.some((t) => t.parentId === taskId);
697
+ }
698
+ function computeParentDates(parentId, tasks) {
699
+ const children = getChildren(parentId, tasks);
700
+ if (children.length === 0) {
701
+ const parent = tasks.find((t) => t.id === parentId);
702
+ const start = parent ? new Date(parent.startDate) : /* @__PURE__ */ new Date();
703
+ const end = parent ? new Date(parent.endDate) : /* @__PURE__ */ new Date();
704
+ return { startDate: start, endDate: end };
705
+ }
706
+ const startDates = children.map((c) => new Date(c.startDate));
707
+ const endDates = children.map((c) => new Date(c.endDate));
708
+ const minTime = Math.min(...startDates.map((d) => d.getTime()));
709
+ const maxTime = Math.max(...endDates.map((d) => d.getTime()));
710
+ return {
711
+ startDate: new Date(minTime),
712
+ endDate: new Date(maxTime)
713
+ };
714
+ }
715
+ function computeParentProgress(parentId, tasks) {
716
+ const children = getChildren(parentId, tasks);
717
+ if (children.length === 0) {
718
+ return 0;
719
+ }
720
+ const DAY_MS2 = 24 * 60 * 60 * 1e3;
721
+ let totalWeight = 0;
722
+ let weightedSum = 0;
723
+ for (const child of children) {
724
+ const start = new Date(child.startDate).getTime();
725
+ const end = new Date(child.endDate).getTime();
726
+ const duration = (end - start + DAY_MS2) / DAY_MS2;
727
+ const progress = child.progress ?? 0;
728
+ totalWeight += duration;
729
+ weightedSum += duration * progress;
730
+ }
731
+ if (totalWeight === 0) {
732
+ return 0;
733
+ }
734
+ return Math.round(weightedSum / totalWeight * 10) / 10;
735
+ }
736
+ function getAllDescendants(parentId, tasks) {
737
+ const descendants = [];
738
+ const visited = /* @__PURE__ */ new Set();
739
+ function collectChildren(taskId) {
740
+ if (visited.has(taskId)) return;
741
+ visited.add(taskId);
742
+ const children = getChildren(taskId, tasks);
743
+ for (const child of children) {
744
+ descendants.push(child);
745
+ collectChildren(child.id);
746
+ }
747
+ }
748
+ collectChildren(parentId);
749
+ return descendants;
750
+ }
751
+ function getAllDependencyEdges(tasks) {
752
+ const edges = [];
753
+ for (const task of tasks) {
754
+ if (task.dependencies) {
755
+ for (const dep of task.dependencies) {
756
+ edges.push({
757
+ predecessorId: dep.taskId,
758
+ successorId: task.id,
759
+ type: dep.type,
760
+ lag: dep.lag ?? 0
761
+ });
762
+ }
763
+ }
764
+ }
765
+ return edges;
766
+ }
767
+ function removeDependenciesBetweenTasks(taskId1, taskId2, tasks) {
768
+ return tasks.map((task) => {
769
+ if (task.id === taskId1 || task.id === taskId2) {
770
+ if (!task.dependencies) return task;
771
+ const otherTaskId = task.id === taskId1 ? taskId2 : taskId1;
772
+ const filteredDependencies = task.dependencies.filter((dep) => dep.taskId !== otherTaskId);
773
+ if (filteredDependencies.length === task.dependencies.length) {
774
+ return task;
775
+ }
776
+ return {
777
+ ...task,
778
+ dependencies: filteredDependencies.length > 0 ? filteredDependencies : void 0
779
+ };
780
+ }
781
+ return task;
782
+ });
783
+ }
784
+ function findParentId(taskId, tasks) {
785
+ const task = tasks.find((t) => t.id === taskId);
786
+ return task?.parentId;
787
+ }
788
+ function isAncestorTask(ancestorId, taskId, tasks) {
789
+ const taskById = new Map(tasks.map((task) => [task.id, task]));
790
+ const visited = /* @__PURE__ */ new Set();
791
+ let current = taskById.get(taskId);
792
+ while (current?.parentId) {
793
+ if (current.parentId === ancestorId) {
794
+ return true;
795
+ }
796
+ if (visited.has(current.parentId)) {
797
+ return false;
798
+ }
799
+ visited.add(current.parentId);
800
+ current = taskById.get(current.parentId);
801
+ }
802
+ return false;
803
+ }
804
+ function areTasksHierarchicallyRelated(taskId1, taskId2, tasks) {
805
+ if (taskId1 === taskId2) {
806
+ return true;
807
+ }
808
+ return isAncestorTask(taskId1, taskId2, tasks) || isAncestorTask(taskId2, taskId1, tasks);
809
+ }
810
+
807
811
  // src/core/scheduling/execute.ts
808
812
  function toIsoDate(date) {
809
813
  return date.toISOString().split("T")[0];
@@ -1259,6 +1263,7 @@ function validateDependencies(tasks) {
1259
1263
  computeLagFromDates,
1260
1264
  computeParentDates,
1261
1265
  computeParentProgress,
1266
+ createCascadeContext,
1262
1267
  detectCycles,
1263
1268
  findParentId,
1264
1269
  getAllDependencyEdges,