gantt-lib 0.60.2 → 0.62.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();
@@ -1777,6 +2028,67 @@ var isTaskExpired = (task, referenceDate = /* @__PURE__ */ new Date()) => {
1777
2028
 
1778
2029
  // src/hooks/useTaskDrag.ts
1779
2030
  import { useEffect, useRef, useState, useCallback } from "react";
2031
+
2032
+ // src/adapters/scheduling/drag.ts
2033
+ init_dateMath();
2034
+ function resolveDateRangeFromPixels(mode, left, width, monthStart, dayWidth, task, businessDays, weekendPredicate) {
2035
+ const dayOffset = Math.round(left / dayWidth);
2036
+ const rawStartDate = new Date(Date.UTC(
2037
+ monthStart.getUTCFullYear(),
2038
+ monthStart.getUTCMonth(),
2039
+ monthStart.getUTCDate() + dayOffset
2040
+ ));
2041
+ const rawEndOffset = dayOffset + Math.round(width / dayWidth) - 1;
2042
+ const rawEndDate = new Date(Date.UTC(
2043
+ monthStart.getUTCFullYear(),
2044
+ monthStart.getUTCMonth(),
2045
+ monthStart.getUTCDate() + rawEndOffset
2046
+ ));
2047
+ if (!(businessDays && weekendPredicate)) {
2048
+ return { start: rawStartDate, end: rawEndDate };
2049
+ }
2050
+ if (mode === "move") {
2051
+ const originalStart2 = new Date(task.startDate);
2052
+ const snapDirection2 = rawStartDate.getTime() >= originalStart2.getTime() ? 1 : -1;
2053
+ return moveTaskRange(
2054
+ task.startDate,
2055
+ task.endDate,
2056
+ rawStartDate,
2057
+ true,
2058
+ weekendPredicate,
2059
+ snapDirection2
2060
+ );
2061
+ }
2062
+ if (mode === "resize-right") {
2063
+ const fixedStart = new Date(task.startDate);
2064
+ const originalEnd = new Date(task.endDate);
2065
+ const snapDirection2 = rawEndDate.getTime() >= originalEnd.getTime() ? 1 : -1;
2066
+ const alignedEnd = alignToWorkingDay(rawEndDate, snapDirection2, weekendPredicate);
2067
+ const duration2 = Math.max(1, getBusinessDaysCount(fixedStart, alignedEnd, weekendPredicate));
2068
+ return buildTaskRangeFromStart(fixedStart, duration2, true, weekendPredicate);
2069
+ }
2070
+ const fixedEnd = new Date(task.endDate);
2071
+ const originalStart = new Date(task.startDate);
2072
+ const snapDirection = rawStartDate.getTime() >= originalStart.getTime() ? 1 : -1;
2073
+ const alignedStart = alignToWorkingDay(rawStartDate, snapDirection, weekendPredicate);
2074
+ const duration = Math.max(1, getBusinessDaysCount(alignedStart, fixedEnd, weekendPredicate));
2075
+ return buildTaskRangeFromEnd(fixedEnd, duration, true, weekendPredicate);
2076
+ }
2077
+ function clampDateRangeForIncomingFS(task, range, allTasks, mode, businessDays, weekendPredicate) {
2078
+ if (mode === "resize-right") {
2079
+ return range;
2080
+ }
2081
+ return clampTaskRangeForIncomingFS(
2082
+ task,
2083
+ range.start,
2084
+ range.end,
2085
+ allTasks,
2086
+ businessDays,
2087
+ weekendPredicate
2088
+ );
2089
+ }
2090
+
2091
+ // src/hooks/useTaskDrag.ts
1780
2092
  var globalActiveDrag = null;
1781
2093
  var globalRafId = null;
1782
2094
  function getDayOffsetFromMonthStart(date, monthStart) {
@@ -6718,8 +7030,11 @@ function GanttChartInner(props, ref) {
6718
7030
  });
6719
7031
  }
6720
7032
  const normalized = normalizeHierarchyTasks(updated);
7033
+ if (onReorder) {
7034
+ onReorder(normalized, movedTaskId, inferredParentId);
7035
+ return;
7036
+ }
6721
7037
  onTasksChange?.(normalized);
6722
- onReorder?.(normalized, movedTaskId, inferredParentId);
6723
7038
  }, [onTasksChange, onReorder]);
6724
7039
  const dependencyOverrides = useMemo9(() => {
6725
7040
  const map = new Map(cascadeOverrides);
@@ -7133,6 +7448,7 @@ export {
7133
7448
  calculateWeekGridLines,
7134
7449
  calculateWeekendBlocks,
7135
7450
  cascadeByLinks,
7451
+ clampDateRangeForIncomingFS,
7136
7452
  clampTaskRangeForIncomingFS,
7137
7453
  computeLagFromDates,
7138
7454
  computeParentDates,
@@ -7172,6 +7488,7 @@ export {
7172
7488
  isToday,
7173
7489
  isWeekend,
7174
7490
  moveTaskRange,
7491
+ moveTaskWithCascade,
7175
7492
  nameContains,
7176
7493
  normalizeDependencyLag,
7177
7494
  normalizeHierarchyTasks,
@@ -7184,8 +7501,12 @@ export {
7184
7501
  pixelsToDate,
7185
7502
  progressInRange,
7186
7503
  recalculateIncomingLags,
7504
+ recalculateProjectSchedule,
7505
+ recalculateTaskFromDependencies,
7187
7506
  reflowTasksOnModeSwitch,
7188
7507
  removeDependenciesBetweenTasks,
7508
+ resizeTaskWithCascade,
7509
+ resolveDateRangeFromPixels,
7189
7510
  shiftBusinessDayOffset,
7190
7511
  subtractBusinessDays2 as subtractBusinessDays,
7191
7512
  universalCascade,