gantt-lib 0.60.2 → 0.61.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.
@@ -421,62 +421,6 @@ function recalculateIncomingLags(task, newStartDate, newEndDate, allTasks, busin
421
421
  return { ...dep, lag: nextLag };
422
422
  });
423
423
  }
424
- function resolveDateRangeFromPixels(mode, left, width, monthStart, dayWidth, task, businessDays, weekendPredicate) {
425
- const dayOffset = Math.round(left / dayWidth);
426
- const rawStartDate = new Date(Date.UTC(
427
- monthStart.getUTCFullYear(),
428
- monthStart.getUTCMonth(),
429
- monthStart.getUTCDate() + dayOffset
430
- ));
431
- const rawEndOffset = dayOffset + Math.round(width / dayWidth) - 1;
432
- const rawEndDate = new Date(Date.UTC(
433
- monthStart.getUTCFullYear(),
434
- monthStart.getUTCMonth(),
435
- monthStart.getUTCDate() + rawEndOffset
436
- ));
437
- if (!(businessDays && weekendPredicate)) {
438
- return { start: rawStartDate, end: rawEndDate };
439
- }
440
- if (mode === "move") {
441
- const originalStart2 = new Date(task.startDate);
442
- const snapDirection2 = rawStartDate.getTime() >= originalStart2.getTime() ? 1 : -1;
443
- return moveTaskRange(
444
- task.startDate,
445
- task.endDate,
446
- rawStartDate,
447
- true,
448
- weekendPredicate,
449
- snapDirection2
450
- );
451
- }
452
- if (mode === "resize-right") {
453
- const fixedStart = new Date(task.startDate);
454
- const originalEnd = new Date(task.endDate);
455
- const snapDirection2 = rawEndDate.getTime() >= originalEnd.getTime() ? 1 : -1;
456
- const alignedEnd = alignToWorkingDay(rawEndDate, snapDirection2, weekendPredicate);
457
- const duration2 = Math.max(1, getBusinessDaysCount(fixedStart, alignedEnd, weekendPredicate));
458
- return buildTaskRangeFromStart(fixedStart, duration2, true, weekendPredicate);
459
- }
460
- const fixedEnd = new Date(task.endDate);
461
- const originalStart = new Date(task.startDate);
462
- const snapDirection = rawStartDate.getTime() >= originalStart.getTime() ? 1 : -1;
463
- const alignedStart = alignToWorkingDay(rawStartDate, snapDirection, weekendPredicate);
464
- const duration = Math.max(1, getBusinessDaysCount(alignedStart, fixedEnd, weekendPredicate));
465
- return buildTaskRangeFromEnd(fixedEnd, duration, true, weekendPredicate);
466
- }
467
- function clampDateRangeForIncomingFS(task, range, allTasks, mode, businessDays, weekendPredicate) {
468
- if (mode === "resize-right") {
469
- return range;
470
- }
471
- return clampTaskRangeForIncomingFS(
472
- task,
473
- range.start,
474
- range.end,
475
- allTasks,
476
- businessDays,
477
- weekendPredicate
478
- );
479
- }
480
424
 
481
425
  // src/core/scheduling/cascade.ts
482
426
  function getSuccessorChain(draggedTaskId, allTasks, linkTypes = ["FS"]) {
@@ -793,6 +737,312 @@ function reflowTasksOnModeSwitch(sourceTasks, toBusinessDays, weekendPredicate)
793
737
  return tasks;
794
738
  }
795
739
 
740
+ // src/core/scheduling/execute.ts
741
+ function toIsoDate(date) {
742
+ return date.toISOString().split("T")[0];
743
+ }
744
+ function createChangedResult(snapshot, nextTasks) {
745
+ const originalById = new Map(snapshot.map((task) => [task.id, task]));
746
+ const changedTasks = nextTasks.filter((task) => JSON.stringify(originalById.get(task.id)) !== JSON.stringify(task));
747
+ return {
748
+ changedTasks,
749
+ changedIds: changedTasks.map((task) => task.id)
750
+ };
751
+ }
752
+ function moveTaskWithCascade(taskId, newStart, snapshot, options) {
753
+ const task = snapshot.find((t) => t.id === taskId);
754
+ if (!task) {
755
+ return { changedTasks: [], changedIds: [] };
756
+ }
757
+ const businessDays = options?.businessDays ?? false;
758
+ const weekendPredicate = options?.weekendPredicate;
759
+ const newRange = moveTaskRange(
760
+ task.startDate,
761
+ task.endDate,
762
+ newStart,
763
+ businessDays,
764
+ weekendPredicate
765
+ );
766
+ const updatedDependencies = recalculateIncomingLags(
767
+ task,
768
+ newRange.start,
769
+ newRange.end,
770
+ snapshot,
771
+ businessDays,
772
+ weekendPredicate
773
+ );
774
+ const movedTask = {
775
+ ...task,
776
+ startDate: newRange.start.toISOString().split("T")[0],
777
+ endDate: newRange.end.toISOString().split("T")[0],
778
+ dependencies: updatedDependencies
779
+ };
780
+ if (options?.skipCascade) {
781
+ return {
782
+ changedTasks: [movedTask],
783
+ changedIds: [movedTask.id]
784
+ };
785
+ }
786
+ const cascadeResult = universalCascade(
787
+ movedTask,
788
+ newRange.start,
789
+ newRange.end,
790
+ snapshot,
791
+ businessDays,
792
+ weekendPredicate
793
+ );
794
+ const resultMap = /* @__PURE__ */ new Map();
795
+ resultMap.set(movedTask.id, movedTask);
796
+ for (const t of cascadeResult) {
797
+ resultMap.set(t.id, t);
798
+ }
799
+ const changedTasks = Array.from(resultMap.values());
800
+ return {
801
+ changedTasks,
802
+ changedIds: changedTasks.map((t) => t.id)
803
+ };
804
+ }
805
+ function resizeTaskWithCascade(taskId, anchor, newDate, snapshot, options) {
806
+ const task = snapshot.find((t) => t.id === taskId);
807
+ if (!task) {
808
+ return { changedTasks: [], changedIds: [] };
809
+ }
810
+ const businessDays = options?.businessDays ?? false;
811
+ const weekendPredicate = options?.weekendPredicate;
812
+ const originalStart = parseDateOnly(task.startDate);
813
+ const originalEnd = parseDateOnly(task.endDate);
814
+ let newRange;
815
+ if (anchor === "end") {
816
+ newRange = { start: originalStart, end: newDate };
817
+ } else {
818
+ newRange = { start: newDate, end: originalEnd };
819
+ }
820
+ const updatedDependencies = recalculateIncomingLags(
821
+ task,
822
+ newRange.start,
823
+ newRange.end,
824
+ snapshot,
825
+ businessDays,
826
+ weekendPredicate
827
+ );
828
+ const resizedTask = {
829
+ ...task,
830
+ startDate: newRange.start.toISOString().split("T")[0],
831
+ endDate: newRange.end.toISOString().split("T")[0],
832
+ dependencies: updatedDependencies
833
+ };
834
+ if (options?.skipCascade) {
835
+ return {
836
+ changedTasks: [resizedTask],
837
+ changedIds: [resizedTask.id]
838
+ };
839
+ }
840
+ const cascadeResult = universalCascade(
841
+ resizedTask,
842
+ newRange.start,
843
+ newRange.end,
844
+ snapshot,
845
+ businessDays,
846
+ weekendPredicate
847
+ );
848
+ const resultMap = /* @__PURE__ */ new Map();
849
+ resultMap.set(resizedTask.id, resizedTask);
850
+ for (const t of cascadeResult) {
851
+ resultMap.set(t.id, t);
852
+ }
853
+ const changedTasks = Array.from(resultMap.values());
854
+ return {
855
+ changedTasks,
856
+ changedIds: changedTasks.map((t) => t.id)
857
+ };
858
+ }
859
+ function recalculateTaskFromDependencies(taskId, snapshot, options) {
860
+ const task = snapshot.find((t) => t.id === taskId);
861
+ if (!task) {
862
+ return { changedTasks: [], changedIds: [] };
863
+ }
864
+ const businessDays = options?.businessDays ?? false;
865
+ const weekendPredicate = options?.weekendPredicate;
866
+ if (!task.dependencies || task.dependencies.length === 0) {
867
+ return {
868
+ changedTasks: [task],
869
+ changedIds: [task.id]
870
+ };
871
+ }
872
+ let constrainedStart = null;
873
+ let constrainedEnd = null;
874
+ for (const dep of task.dependencies) {
875
+ const predecessor = snapshot.find((t) => t.id === dep.taskId);
876
+ if (!predecessor) continue;
877
+ const predStart = parseDateOnly(predecessor.startDate);
878
+ const predEnd = parseDateOnly(predecessor.endDate);
879
+ const constraintDate = calculateSuccessorDate(
880
+ predStart,
881
+ predEnd,
882
+ dep.type,
883
+ getDependencyLag(dep),
884
+ businessDays,
885
+ weekendPredicate
886
+ );
887
+ const duration = getTaskDuration(
888
+ parseDateOnly(task.startDate),
889
+ parseDateOnly(task.endDate),
890
+ businessDays,
891
+ weekendPredicate
892
+ );
893
+ let range;
894
+ if (dep.type === "FS" || dep.type === "SS") {
895
+ range = buildTaskRangeFromStart(constraintDate, duration, businessDays, weekendPredicate);
896
+ } else {
897
+ range = buildTaskRangeFromEnd(constraintDate, duration, businessDays, weekendPredicate);
898
+ }
899
+ if (!constrainedStart || range.start.getTime() > constrainedStart.getTime()) {
900
+ constrainedStart = range.start;
901
+ constrainedEnd = range.end;
902
+ }
903
+ }
904
+ if (!constrainedStart || !constrainedEnd) {
905
+ return {
906
+ changedTasks: [task],
907
+ changedIds: [task.id]
908
+ };
909
+ }
910
+ const updatedDependencies = recalculateIncomingLags(
911
+ task,
912
+ constrainedStart,
913
+ constrainedEnd,
914
+ snapshot,
915
+ businessDays,
916
+ weekendPredicate
917
+ );
918
+ const recalculatedTask = {
919
+ ...task,
920
+ startDate: constrainedStart.toISOString().split("T")[0],
921
+ endDate: constrainedEnd.toISOString().split("T")[0],
922
+ dependencies: updatedDependencies
923
+ };
924
+ if (options?.skipCascade) {
925
+ return {
926
+ changedTasks: [recalculatedTask],
927
+ changedIds: [recalculatedTask.id]
928
+ };
929
+ }
930
+ const cascadeResult = universalCascade(
931
+ recalculatedTask,
932
+ constrainedStart,
933
+ constrainedEnd,
934
+ snapshot,
935
+ businessDays,
936
+ weekendPredicate
937
+ );
938
+ const resultMap = /* @__PURE__ */ new Map();
939
+ resultMap.set(recalculatedTask.id, recalculatedTask);
940
+ for (const t of cascadeResult) {
941
+ resultMap.set(t.id, t);
942
+ }
943
+ const changedTasks = Array.from(resultMap.values());
944
+ return {
945
+ changedTasks,
946
+ changedIds: changedTasks.map((t) => t.id)
947
+ };
948
+ }
949
+ function recalculateProjectSchedule(snapshot, options) {
950
+ const businessDays = options?.businessDays ?? false;
951
+ const weekendPredicate = options?.weekendPredicate;
952
+ const workingMap = new Map(snapshot.map((task) => [task.id, { ...task }]));
953
+ const indegree = /* @__PURE__ */ new Map();
954
+ const successorIdsByTask = /* @__PURE__ */ new Map();
955
+ for (const task of snapshot) {
956
+ indegree.set(task.id, 0);
957
+ successorIdsByTask.set(task.id, []);
958
+ }
959
+ for (const task of snapshot) {
960
+ for (const dep of task.dependencies ?? []) {
961
+ if (!workingMap.has(dep.taskId)) {
962
+ continue;
963
+ }
964
+ indegree.set(task.id, (indegree.get(task.id) ?? 0) + 1);
965
+ successorIdsByTask.get(dep.taskId)?.push(task.id);
966
+ }
967
+ }
968
+ const queue = snapshot.filter((task) => (indegree.get(task.id) ?? 0) === 0).map((task) => task.id);
969
+ while (queue.length > 0) {
970
+ const currentId = queue.shift();
971
+ for (const successorId of successorIdsByTask.get(currentId) ?? []) {
972
+ const nextIndegree = (indegree.get(successorId) ?? 0) - 1;
973
+ indegree.set(successorId, nextIndegree);
974
+ if (nextIndegree !== 0) {
975
+ continue;
976
+ }
977
+ const currentTask = workingMap.get(successorId);
978
+ if (!currentTask || currentTask.locked || !currentTask.dependencies?.length) {
979
+ queue.push(successorId);
980
+ continue;
981
+ }
982
+ const duration = getTaskDuration(
983
+ parseDateOnly(currentTask.startDate),
984
+ parseDateOnly(currentTask.endDate),
985
+ businessDays,
986
+ weekendPredicate
987
+ );
988
+ let constrainedRange = null;
989
+ for (const dep of currentTask.dependencies) {
990
+ const predecessor = workingMap.get(dep.taskId);
991
+ if (!predecessor) {
992
+ continue;
993
+ }
994
+ const predecessorStart = parseDateOnly(predecessor.startDate);
995
+ const predecessorEnd = parseDateOnly(predecessor.endDate);
996
+ const constraintDate = calculateSuccessorDate(
997
+ predecessorStart,
998
+ predecessorEnd,
999
+ dep.type,
1000
+ getDependencyLag(dep),
1001
+ businessDays,
1002
+ weekendPredicate
1003
+ );
1004
+ const candidateRange = dep.type === "FS" || dep.type === "SS" ? buildTaskRangeFromStart(constraintDate, duration, businessDays, weekendPredicate) : buildTaskRangeFromEnd(constraintDate, duration, businessDays, weekendPredicate);
1005
+ if (!constrainedRange || candidateRange.start.getTime() > constrainedRange.start.getTime() || candidateRange.start.getTime() === constrainedRange.start.getTime() && candidateRange.end.getTime() > constrainedRange.end.getTime()) {
1006
+ constrainedRange = candidateRange;
1007
+ }
1008
+ }
1009
+ if (!constrainedRange) {
1010
+ queue.push(successorId);
1011
+ continue;
1012
+ }
1013
+ workingMap.set(successorId, {
1014
+ ...currentTask,
1015
+ startDate: toIsoDate(constrainedRange.start),
1016
+ endDate: toIsoDate(constrainedRange.end)
1017
+ });
1018
+ queue.push(successorId);
1019
+ }
1020
+ }
1021
+ const parentsByDepth = snapshot.filter((task) => isTaskParent(task.id, snapshot)).map((task) => {
1022
+ let depth = 0;
1023
+ let current = task.parentId ? workingMap.get(task.parentId) : void 0;
1024
+ while (current) {
1025
+ depth++;
1026
+ current = current.parentId ? workingMap.get(current.parentId) : void 0;
1027
+ }
1028
+ return { taskId: task.id, depth };
1029
+ }).sort((left, right) => right.depth - left.depth);
1030
+ const workingTasks = () => Array.from(workingMap.values());
1031
+ for (const { taskId } of parentsByDepth) {
1032
+ const parent = workingMap.get(taskId);
1033
+ if (!parent || parent.locked) {
1034
+ continue;
1035
+ }
1036
+ const { startDate, endDate } = computeParentDates(taskId, workingTasks());
1037
+ workingMap.set(taskId, {
1038
+ ...parent,
1039
+ startDate: toIsoDate(startDate),
1040
+ endDate: toIsoDate(endDate)
1041
+ });
1042
+ }
1043
+ return createChangedResult(snapshot, Array.from(workingMap.values()));
1044
+ }
1045
+
796
1046
  // src/core/scheduling/validation.ts
797
1047
  function buildAdjacencyList(tasks) {
798
1048
  const graph = /* @__PURE__ */ new Map();
@@ -891,6 +1141,64 @@ function validateDependencies(tasks) {
891
1141
  errors
892
1142
  };
893
1143
  }
1144
+
1145
+ // src/adapters/scheduling/drag.ts
1146
+ function resolveDateRangeFromPixels(mode, left, width, monthStart, dayWidth, task, businessDays, weekendPredicate) {
1147
+ const dayOffset = Math.round(left / dayWidth);
1148
+ const rawStartDate = new Date(Date.UTC(
1149
+ monthStart.getUTCFullYear(),
1150
+ monthStart.getUTCMonth(),
1151
+ monthStart.getUTCDate() + dayOffset
1152
+ ));
1153
+ const rawEndOffset = dayOffset + Math.round(width / dayWidth) - 1;
1154
+ const rawEndDate = new Date(Date.UTC(
1155
+ monthStart.getUTCFullYear(),
1156
+ monthStart.getUTCMonth(),
1157
+ monthStart.getUTCDate() + rawEndOffset
1158
+ ));
1159
+ if (!(businessDays && weekendPredicate)) {
1160
+ return { start: rawStartDate, end: rawEndDate };
1161
+ }
1162
+ if (mode === "move") {
1163
+ const originalStart2 = new Date(task.startDate);
1164
+ const snapDirection2 = rawStartDate.getTime() >= originalStart2.getTime() ? 1 : -1;
1165
+ return moveTaskRange(
1166
+ task.startDate,
1167
+ task.endDate,
1168
+ rawStartDate,
1169
+ true,
1170
+ weekendPredicate,
1171
+ snapDirection2
1172
+ );
1173
+ }
1174
+ if (mode === "resize-right") {
1175
+ const fixedStart = new Date(task.startDate);
1176
+ const originalEnd = new Date(task.endDate);
1177
+ const snapDirection2 = rawEndDate.getTime() >= originalEnd.getTime() ? 1 : -1;
1178
+ const alignedEnd = alignToWorkingDay(rawEndDate, snapDirection2, weekendPredicate);
1179
+ const duration2 = Math.max(1, getBusinessDaysCount(fixedStart, alignedEnd, weekendPredicate));
1180
+ return buildTaskRangeFromStart(fixedStart, duration2, true, weekendPredicate);
1181
+ }
1182
+ const fixedEnd = new Date(task.endDate);
1183
+ const originalStart = new Date(task.startDate);
1184
+ const snapDirection = rawStartDate.getTime() >= originalStart.getTime() ? 1 : -1;
1185
+ const alignedStart = alignToWorkingDay(rawStartDate, snapDirection, weekendPredicate);
1186
+ const duration = Math.max(1, getBusinessDaysCount(alignedStart, fixedEnd, weekendPredicate));
1187
+ return buildTaskRangeFromEnd(fixedEnd, duration, true, weekendPredicate);
1188
+ }
1189
+ function clampDateRangeForIncomingFS(task, range, allTasks, mode, businessDays, weekendPredicate) {
1190
+ if (mode === "resize-right") {
1191
+ return range;
1192
+ }
1193
+ return clampTaskRangeForIncomingFS(
1194
+ task,
1195
+ range.start,
1196
+ range.end,
1197
+ allTasks,
1198
+ businessDays,
1199
+ weekendPredicate
1200
+ );
1201
+ }
894
1202
  export {
895
1203
  DAY_MS,
896
1204
  addBusinessDays,
@@ -920,12 +1228,16 @@ export {
920
1228
  isAncestorTask,
921
1229
  isTaskParent,
922
1230
  moveTaskRange,
1231
+ moveTaskWithCascade,
923
1232
  normalizeDependencyLag,
924
1233
  normalizeUTCDate,
925
1234
  parseDateOnly,
926
1235
  recalculateIncomingLags,
1236
+ recalculateProjectSchedule,
1237
+ recalculateTaskFromDependencies,
927
1238
  reflowTasksOnModeSwitch,
928
1239
  removeDependenciesBetweenTasks,
1240
+ resizeTaskWithCascade,
929
1241
  resolveDateRangeFromPixels,
930
1242
  shiftBusinessDayOffset,
931
1243
  subtractBusinessDays,