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.
package/dist/index.mjs CHANGED
@@ -825,62 +825,6 @@ function recalculateIncomingLags(task, newStartDate, newEndDate, allTasks, busin
825
825
  return { ...dep, lag: nextLag };
826
826
  });
827
827
  }
828
- function resolveDateRangeFromPixels(mode, left, width, monthStart, dayWidth, task, businessDays, weekendPredicate) {
829
- const dayOffset = Math.round(left / dayWidth);
830
- const rawStartDate = new Date(Date.UTC(
831
- monthStart.getUTCFullYear(),
832
- monthStart.getUTCMonth(),
833
- monthStart.getUTCDate() + dayOffset
834
- ));
835
- const rawEndOffset = dayOffset + Math.round(width / dayWidth) - 1;
836
- const rawEndDate = new Date(Date.UTC(
837
- monthStart.getUTCFullYear(),
838
- monthStart.getUTCMonth(),
839
- monthStart.getUTCDate() + rawEndOffset
840
- ));
841
- if (!(businessDays && weekendPredicate)) {
842
- return { start: rawStartDate, end: rawEndDate };
843
- }
844
- if (mode === "move") {
845
- const originalStart2 = new Date(task.startDate);
846
- const snapDirection2 = rawStartDate.getTime() >= originalStart2.getTime() ? 1 : -1;
847
- return moveTaskRange(
848
- task.startDate,
849
- task.endDate,
850
- rawStartDate,
851
- true,
852
- weekendPredicate,
853
- snapDirection2
854
- );
855
- }
856
- if (mode === "resize-right") {
857
- const fixedStart = new Date(task.startDate);
858
- const originalEnd = new Date(task.endDate);
859
- const snapDirection2 = rawEndDate.getTime() >= originalEnd.getTime() ? 1 : -1;
860
- const alignedEnd = alignToWorkingDay(rawEndDate, snapDirection2, weekendPredicate);
861
- const duration2 = Math.max(1, getBusinessDaysCount(fixedStart, alignedEnd, weekendPredicate));
862
- return buildTaskRangeFromStart(fixedStart, duration2, true, weekendPredicate);
863
- }
864
- const fixedEnd = new Date(task.endDate);
865
- const originalStart = new Date(task.startDate);
866
- const snapDirection = rawStartDate.getTime() >= originalStart.getTime() ? 1 : -1;
867
- const alignedStart = alignToWorkingDay(rawStartDate, snapDirection, weekendPredicate);
868
- const duration = Math.max(1, getBusinessDaysCount(alignedStart, fixedEnd, weekendPredicate));
869
- return buildTaskRangeFromEnd(fixedEnd, duration, true, weekendPredicate);
870
- }
871
- function clampDateRangeForIncomingFS(task, range, allTasks, mode, businessDays, weekendPredicate) {
872
- if (mode === "resize-right") {
873
- return range;
874
- }
875
- return clampTaskRangeForIncomingFS(
876
- task,
877
- range.start,
878
- range.end,
879
- allTasks,
880
- businessDays,
881
- weekendPredicate
882
- );
883
- }
884
828
 
885
829
  // src/core/scheduling/cascade.ts
886
830
  function getSuccessorChain(draggedTaskId, allTasks, linkTypes = ["FS"]) {
@@ -1197,6 +1141,313 @@ function reflowTasksOnModeSwitch(sourceTasks, toBusinessDays, weekendPredicate)
1197
1141
  return tasks;
1198
1142
  }
1199
1143
 
1144
+ // src/core/scheduling/execute.ts
1145
+ init_dateMath();
1146
+ function toIsoDate(date) {
1147
+ return date.toISOString().split("T")[0];
1148
+ }
1149
+ function createChangedResult(snapshot, nextTasks) {
1150
+ const originalById = new Map(snapshot.map((task) => [task.id, task]));
1151
+ const changedTasks = nextTasks.filter((task) => JSON.stringify(originalById.get(task.id)) !== JSON.stringify(task));
1152
+ return {
1153
+ changedTasks,
1154
+ changedIds: changedTasks.map((task) => task.id)
1155
+ };
1156
+ }
1157
+ function moveTaskWithCascade(taskId, newStart, snapshot, options) {
1158
+ const task = snapshot.find((t) => t.id === taskId);
1159
+ if (!task) {
1160
+ return { changedTasks: [], changedIds: [] };
1161
+ }
1162
+ const businessDays = options?.businessDays ?? false;
1163
+ const weekendPredicate = options?.weekendPredicate;
1164
+ const newRange = moveTaskRange(
1165
+ task.startDate,
1166
+ task.endDate,
1167
+ newStart,
1168
+ businessDays,
1169
+ weekendPredicate
1170
+ );
1171
+ const updatedDependencies = recalculateIncomingLags(
1172
+ task,
1173
+ newRange.start,
1174
+ newRange.end,
1175
+ snapshot,
1176
+ businessDays,
1177
+ weekendPredicate
1178
+ );
1179
+ const movedTask = {
1180
+ ...task,
1181
+ startDate: newRange.start.toISOString().split("T")[0],
1182
+ endDate: newRange.end.toISOString().split("T")[0],
1183
+ dependencies: updatedDependencies
1184
+ };
1185
+ if (options?.skipCascade) {
1186
+ return {
1187
+ changedTasks: [movedTask],
1188
+ changedIds: [movedTask.id]
1189
+ };
1190
+ }
1191
+ const cascadeResult = universalCascade(
1192
+ movedTask,
1193
+ newRange.start,
1194
+ newRange.end,
1195
+ snapshot,
1196
+ businessDays,
1197
+ weekendPredicate
1198
+ );
1199
+ const resultMap = /* @__PURE__ */ new Map();
1200
+ resultMap.set(movedTask.id, movedTask);
1201
+ for (const t of cascadeResult) {
1202
+ resultMap.set(t.id, t);
1203
+ }
1204
+ const changedTasks = Array.from(resultMap.values());
1205
+ return {
1206
+ changedTasks,
1207
+ changedIds: changedTasks.map((t) => t.id)
1208
+ };
1209
+ }
1210
+ function resizeTaskWithCascade(taskId, anchor, newDate, snapshot, options) {
1211
+ const task = snapshot.find((t) => t.id === taskId);
1212
+ if (!task) {
1213
+ return { changedTasks: [], changedIds: [] };
1214
+ }
1215
+ const businessDays = options?.businessDays ?? false;
1216
+ const weekendPredicate = options?.weekendPredicate;
1217
+ const originalStart = parseDateOnly(task.startDate);
1218
+ const originalEnd = parseDateOnly(task.endDate);
1219
+ let newRange;
1220
+ if (anchor === "end") {
1221
+ newRange = { start: originalStart, end: newDate };
1222
+ } else {
1223
+ newRange = { start: newDate, end: originalEnd };
1224
+ }
1225
+ const updatedDependencies = recalculateIncomingLags(
1226
+ task,
1227
+ newRange.start,
1228
+ newRange.end,
1229
+ snapshot,
1230
+ businessDays,
1231
+ weekendPredicate
1232
+ );
1233
+ const resizedTask = {
1234
+ ...task,
1235
+ startDate: newRange.start.toISOString().split("T")[0],
1236
+ endDate: newRange.end.toISOString().split("T")[0],
1237
+ dependencies: updatedDependencies
1238
+ };
1239
+ if (options?.skipCascade) {
1240
+ return {
1241
+ changedTasks: [resizedTask],
1242
+ changedIds: [resizedTask.id]
1243
+ };
1244
+ }
1245
+ const cascadeResult = universalCascade(
1246
+ resizedTask,
1247
+ newRange.start,
1248
+ newRange.end,
1249
+ snapshot,
1250
+ businessDays,
1251
+ weekendPredicate
1252
+ );
1253
+ const resultMap = /* @__PURE__ */ new Map();
1254
+ resultMap.set(resizedTask.id, resizedTask);
1255
+ for (const t of cascadeResult) {
1256
+ resultMap.set(t.id, t);
1257
+ }
1258
+ const changedTasks = Array.from(resultMap.values());
1259
+ return {
1260
+ changedTasks,
1261
+ changedIds: changedTasks.map((t) => t.id)
1262
+ };
1263
+ }
1264
+ function recalculateTaskFromDependencies(taskId, snapshot, options) {
1265
+ const task = snapshot.find((t) => t.id === taskId);
1266
+ if (!task) {
1267
+ return { changedTasks: [], changedIds: [] };
1268
+ }
1269
+ const businessDays = options?.businessDays ?? false;
1270
+ const weekendPredicate = options?.weekendPredicate;
1271
+ if (!task.dependencies || task.dependencies.length === 0) {
1272
+ return {
1273
+ changedTasks: [task],
1274
+ changedIds: [task.id]
1275
+ };
1276
+ }
1277
+ let constrainedStart = null;
1278
+ let constrainedEnd = null;
1279
+ for (const dep of task.dependencies) {
1280
+ const predecessor = snapshot.find((t) => t.id === dep.taskId);
1281
+ if (!predecessor) continue;
1282
+ const predStart = parseDateOnly(predecessor.startDate);
1283
+ const predEnd = parseDateOnly(predecessor.endDate);
1284
+ const constraintDate = calculateSuccessorDate(
1285
+ predStart,
1286
+ predEnd,
1287
+ dep.type,
1288
+ getDependencyLag(dep),
1289
+ businessDays,
1290
+ weekendPredicate
1291
+ );
1292
+ const duration = getTaskDuration(
1293
+ parseDateOnly(task.startDate),
1294
+ parseDateOnly(task.endDate),
1295
+ businessDays,
1296
+ weekendPredicate
1297
+ );
1298
+ let range;
1299
+ if (dep.type === "FS" || dep.type === "SS") {
1300
+ range = buildTaskRangeFromStart(constraintDate, duration, businessDays, weekendPredicate);
1301
+ } else {
1302
+ range = buildTaskRangeFromEnd(constraintDate, duration, businessDays, weekendPredicate);
1303
+ }
1304
+ if (!constrainedStart || range.start.getTime() > constrainedStart.getTime()) {
1305
+ constrainedStart = range.start;
1306
+ constrainedEnd = range.end;
1307
+ }
1308
+ }
1309
+ if (!constrainedStart || !constrainedEnd) {
1310
+ return {
1311
+ changedTasks: [task],
1312
+ changedIds: [task.id]
1313
+ };
1314
+ }
1315
+ const updatedDependencies = recalculateIncomingLags(
1316
+ task,
1317
+ constrainedStart,
1318
+ constrainedEnd,
1319
+ snapshot,
1320
+ businessDays,
1321
+ weekendPredicate
1322
+ );
1323
+ const recalculatedTask = {
1324
+ ...task,
1325
+ startDate: constrainedStart.toISOString().split("T")[0],
1326
+ endDate: constrainedEnd.toISOString().split("T")[0],
1327
+ dependencies: updatedDependencies
1328
+ };
1329
+ if (options?.skipCascade) {
1330
+ return {
1331
+ changedTasks: [recalculatedTask],
1332
+ changedIds: [recalculatedTask.id]
1333
+ };
1334
+ }
1335
+ const cascadeResult = universalCascade(
1336
+ recalculatedTask,
1337
+ constrainedStart,
1338
+ constrainedEnd,
1339
+ snapshot,
1340
+ businessDays,
1341
+ weekendPredicate
1342
+ );
1343
+ const resultMap = /* @__PURE__ */ new Map();
1344
+ resultMap.set(recalculatedTask.id, recalculatedTask);
1345
+ for (const t of cascadeResult) {
1346
+ resultMap.set(t.id, t);
1347
+ }
1348
+ const changedTasks = Array.from(resultMap.values());
1349
+ return {
1350
+ changedTasks,
1351
+ changedIds: changedTasks.map((t) => t.id)
1352
+ };
1353
+ }
1354
+ function recalculateProjectSchedule(snapshot, options) {
1355
+ const businessDays = options?.businessDays ?? false;
1356
+ const weekendPredicate = options?.weekendPredicate;
1357
+ const workingMap = new Map(snapshot.map((task) => [task.id, { ...task }]));
1358
+ const indegree = /* @__PURE__ */ new Map();
1359
+ const successorIdsByTask = /* @__PURE__ */ new Map();
1360
+ for (const task of snapshot) {
1361
+ indegree.set(task.id, 0);
1362
+ successorIdsByTask.set(task.id, []);
1363
+ }
1364
+ for (const task of snapshot) {
1365
+ for (const dep of task.dependencies ?? []) {
1366
+ if (!workingMap.has(dep.taskId)) {
1367
+ continue;
1368
+ }
1369
+ indegree.set(task.id, (indegree.get(task.id) ?? 0) + 1);
1370
+ successorIdsByTask.get(dep.taskId)?.push(task.id);
1371
+ }
1372
+ }
1373
+ const queue = snapshot.filter((task) => (indegree.get(task.id) ?? 0) === 0).map((task) => task.id);
1374
+ while (queue.length > 0) {
1375
+ const currentId = queue.shift();
1376
+ for (const successorId of successorIdsByTask.get(currentId) ?? []) {
1377
+ const nextIndegree = (indegree.get(successorId) ?? 0) - 1;
1378
+ indegree.set(successorId, nextIndegree);
1379
+ if (nextIndegree !== 0) {
1380
+ continue;
1381
+ }
1382
+ const currentTask = workingMap.get(successorId);
1383
+ if (!currentTask || currentTask.locked || !currentTask.dependencies?.length) {
1384
+ queue.push(successorId);
1385
+ continue;
1386
+ }
1387
+ const duration = getTaskDuration(
1388
+ parseDateOnly(currentTask.startDate),
1389
+ parseDateOnly(currentTask.endDate),
1390
+ businessDays,
1391
+ weekendPredicate
1392
+ );
1393
+ let constrainedRange = null;
1394
+ for (const dep of currentTask.dependencies) {
1395
+ const predecessor = workingMap.get(dep.taskId);
1396
+ if (!predecessor) {
1397
+ continue;
1398
+ }
1399
+ const predecessorStart = parseDateOnly(predecessor.startDate);
1400
+ const predecessorEnd = parseDateOnly(predecessor.endDate);
1401
+ const constraintDate = calculateSuccessorDate(
1402
+ predecessorStart,
1403
+ predecessorEnd,
1404
+ dep.type,
1405
+ getDependencyLag(dep),
1406
+ businessDays,
1407
+ weekendPredicate
1408
+ );
1409
+ const candidateRange = dep.type === "FS" || dep.type === "SS" ? buildTaskRangeFromStart(constraintDate, duration, businessDays, weekendPredicate) : buildTaskRangeFromEnd(constraintDate, duration, businessDays, weekendPredicate);
1410
+ if (!constrainedRange || candidateRange.start.getTime() > constrainedRange.start.getTime() || candidateRange.start.getTime() === constrainedRange.start.getTime() && candidateRange.end.getTime() > constrainedRange.end.getTime()) {
1411
+ constrainedRange = candidateRange;
1412
+ }
1413
+ }
1414
+ if (!constrainedRange) {
1415
+ queue.push(successorId);
1416
+ continue;
1417
+ }
1418
+ workingMap.set(successorId, {
1419
+ ...currentTask,
1420
+ startDate: toIsoDate(constrainedRange.start),
1421
+ endDate: toIsoDate(constrainedRange.end)
1422
+ });
1423
+ queue.push(successorId);
1424
+ }
1425
+ }
1426
+ const parentsByDepth = snapshot.filter((task) => isTaskParent(task.id, snapshot)).map((task) => {
1427
+ let depth = 0;
1428
+ let current = task.parentId ? workingMap.get(task.parentId) : void 0;
1429
+ while (current) {
1430
+ depth++;
1431
+ current = current.parentId ? workingMap.get(current.parentId) : void 0;
1432
+ }
1433
+ return { taskId: task.id, depth };
1434
+ }).sort((left, right) => right.depth - left.depth);
1435
+ const workingTasks = () => Array.from(workingMap.values());
1436
+ for (const { taskId } of parentsByDepth) {
1437
+ const parent = workingMap.get(taskId);
1438
+ if (!parent || parent.locked) {
1439
+ continue;
1440
+ }
1441
+ const { startDate, endDate } = computeParentDates(taskId, workingTasks());
1442
+ workingMap.set(taskId, {
1443
+ ...parent,
1444
+ startDate: toIsoDate(startDate),
1445
+ endDate: toIsoDate(endDate)
1446
+ });
1447
+ }
1448
+ return createChangedResult(snapshot, Array.from(workingMap.values()));
1449
+ }
1450
+
1200
1451
  // src/core/scheduling/validation.ts
1201
1452
  function buildAdjacencyList(tasks) {
1202
1453
  const graph = /* @__PURE__ */ new Map();
@@ -1296,6 +1547,65 @@ function validateDependencies(tasks) {
1296
1547
  };
1297
1548
  }
1298
1549
 
1550
+ // src/adapters/scheduling/drag.ts
1551
+ init_dateMath();
1552
+ function resolveDateRangeFromPixels(mode, left, width, monthStart, dayWidth, task, businessDays, weekendPredicate) {
1553
+ const dayOffset = Math.round(left / dayWidth);
1554
+ const rawStartDate = new Date(Date.UTC(
1555
+ monthStart.getUTCFullYear(),
1556
+ monthStart.getUTCMonth(),
1557
+ monthStart.getUTCDate() + dayOffset
1558
+ ));
1559
+ const rawEndOffset = dayOffset + Math.round(width / dayWidth) - 1;
1560
+ const rawEndDate = new Date(Date.UTC(
1561
+ monthStart.getUTCFullYear(),
1562
+ monthStart.getUTCMonth(),
1563
+ monthStart.getUTCDate() + rawEndOffset
1564
+ ));
1565
+ if (!(businessDays && weekendPredicate)) {
1566
+ return { start: rawStartDate, end: rawEndDate };
1567
+ }
1568
+ if (mode === "move") {
1569
+ const originalStart2 = new Date(task.startDate);
1570
+ const snapDirection2 = rawStartDate.getTime() >= originalStart2.getTime() ? 1 : -1;
1571
+ return moveTaskRange(
1572
+ task.startDate,
1573
+ task.endDate,
1574
+ rawStartDate,
1575
+ true,
1576
+ weekendPredicate,
1577
+ snapDirection2
1578
+ );
1579
+ }
1580
+ if (mode === "resize-right") {
1581
+ const fixedStart = new Date(task.startDate);
1582
+ const originalEnd = new Date(task.endDate);
1583
+ const snapDirection2 = rawEndDate.getTime() >= originalEnd.getTime() ? 1 : -1;
1584
+ const alignedEnd = alignToWorkingDay(rawEndDate, snapDirection2, weekendPredicate);
1585
+ const duration2 = Math.max(1, getBusinessDaysCount(fixedStart, alignedEnd, weekendPredicate));
1586
+ return buildTaskRangeFromStart(fixedStart, duration2, true, weekendPredicate);
1587
+ }
1588
+ const fixedEnd = new Date(task.endDate);
1589
+ const originalStart = new Date(task.startDate);
1590
+ const snapDirection = rawStartDate.getTime() >= originalStart.getTime() ? 1 : -1;
1591
+ const alignedStart = alignToWorkingDay(rawStartDate, snapDirection, weekendPredicate);
1592
+ const duration = Math.max(1, getBusinessDaysCount(alignedStart, fixedEnd, weekendPredicate));
1593
+ return buildTaskRangeFromEnd(fixedEnd, duration, true, weekendPredicate);
1594
+ }
1595
+ function clampDateRangeForIncomingFS(task, range, allTasks, mode, businessDays, weekendPredicate) {
1596
+ if (mode === "resize-right") {
1597
+ return range;
1598
+ }
1599
+ return clampTaskRangeForIncomingFS(
1600
+ task,
1601
+ range.start,
1602
+ range.end,
1603
+ allTasks,
1604
+ businessDays,
1605
+ weekendPredicate
1606
+ );
1607
+ }
1608
+
1299
1609
  // src/utils/hierarchyOrder.ts
1300
1610
  init_dateUtils();
1301
1611
  function flattenHierarchy(tasks) {
@@ -7133,6 +7443,7 @@ export {
7133
7443
  calculateWeekGridLines,
7134
7444
  calculateWeekendBlocks,
7135
7445
  cascadeByLinks,
7446
+ clampDateRangeForIncomingFS,
7136
7447
  clampTaskRangeForIncomingFS,
7137
7448
  computeLagFromDates,
7138
7449
  computeParentDates,
@@ -7172,6 +7483,7 @@ export {
7172
7483
  isToday,
7173
7484
  isWeekend,
7174
7485
  moveTaskRange,
7486
+ moveTaskWithCascade,
7175
7487
  nameContains,
7176
7488
  normalizeDependencyLag,
7177
7489
  normalizeHierarchyTasks,
@@ -7184,8 +7496,12 @@ export {
7184
7496
  pixelsToDate,
7185
7497
  progressInRange,
7186
7498
  recalculateIncomingLags,
7499
+ recalculateProjectSchedule,
7500
+ recalculateTaskFromDependencies,
7187
7501
  reflowTasksOnModeSwitch,
7188
7502
  removeDependenciesBetweenTasks,
7503
+ resizeTaskWithCascade,
7504
+ resolveDateRangeFromPixels,
7189
7505
  shiftBusinessDayOffset,
7190
7506
  subtractBusinessDays2 as subtractBusinessDays,
7191
7507
  universalCascade,