gantt-lib 0.122.0 → 0.124.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/core/scheduling/index.d.mts +1 -1
- package/dist/core/scheduling/index.d.ts +1 -1
- package/dist/core/scheduling/index.js +157 -0
- package/dist/core/scheduling/index.js.map +1 -1
- package/dist/core/scheduling/index.mjs +155 -0
- package/dist/core/scheduling/index.mjs.map +1 -1
- package/dist/{index-OZVb2o_D.d.mts → index-DD3YvhvA.d.mts} +38 -1
- package/dist/{index-OZVb2o_D.d.ts → index-DD3YvhvA.d.ts} +38 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +10 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +225 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +223 -20
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +33 -0
- package/package.json +1 -1
|
@@ -208,6 +208,159 @@ function calculateSuccessorDate(predecessorStart, predecessorEnd, linkType, lag
|
|
|
208
208
|
return shiftBusinessDayOffset(anchorDate, offset, weekendPredicate);
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
// src/core/scheduling/criticalPath.ts
|
|
212
|
+
function computeCriticalPath(tasks, options = {}) {
|
|
213
|
+
const { businessDays = false, weekendPredicate } = options;
|
|
214
|
+
const taskById = new Map(tasks.map((task) => [task.id, task]));
|
|
215
|
+
const childIds = /* @__PURE__ */ new Set();
|
|
216
|
+
for (const task of tasks) {
|
|
217
|
+
if (task.parentId) childIds.add(task.parentId);
|
|
218
|
+
}
|
|
219
|
+
const leafTasks = tasks.filter((task) => !childIds.has(task.id));
|
|
220
|
+
const leafIds = new Set(leafTasks.map((task) => task.id));
|
|
221
|
+
const leafIdsByAncestor = /* @__PURE__ */ new Map();
|
|
222
|
+
for (const leaf of leafTasks) {
|
|
223
|
+
let ancestor = leaf.parentId;
|
|
224
|
+
while (ancestor) {
|
|
225
|
+
const set = leafIdsByAncestor.get(ancestor) ?? /* @__PURE__ */ new Set();
|
|
226
|
+
set.add(leaf.id);
|
|
227
|
+
leafIdsByAncestor.set(ancestor, set);
|
|
228
|
+
ancestor = taskById.get(ancestor)?.parentId;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const durationOf = (task) => getTaskDuration(task.startDate, task.endDate, businessDays, weekendPredicate);
|
|
232
|
+
const projectStartDate = leafTasks.reduce((earliest, task) => {
|
|
233
|
+
const taskStart = parseDateOnly(task.startDate);
|
|
234
|
+
return taskStart.getTime() < earliest.getTime() ? taskStart : earliest;
|
|
235
|
+
}, parseDateOnly(leafTasks[0]?.startDate ?? /* @__PURE__ */ new Date()));
|
|
236
|
+
const offsetFromProjectStart = (date) => {
|
|
237
|
+
const targetDate = parseDateOnly(date);
|
|
238
|
+
if (businessDays && weekendPredicate) {
|
|
239
|
+
return getBusinessDayOffset(projectStartDate, targetDate, weekendPredicate);
|
|
240
|
+
}
|
|
241
|
+
return Math.round((targetDate.getTime() - projectStartDate.getTime()) / DAY_MS);
|
|
242
|
+
};
|
|
243
|
+
const scheduledStart = /* @__PURE__ */ new Map();
|
|
244
|
+
for (const leaf of leafTasks) {
|
|
245
|
+
scheduledStart.set(leaf.id, offsetFromProjectStart(leaf.startDate));
|
|
246
|
+
}
|
|
247
|
+
const succByPred = /* @__PURE__ */ new Map();
|
|
248
|
+
const predBySucc = /* @__PURE__ */ new Map();
|
|
249
|
+
const seenEdges = /* @__PURE__ */ new Set();
|
|
250
|
+
const addEdge = (predecessorId, succId, lag, type) => {
|
|
251
|
+
const edgeKey = `${predecessorId}|${succId}|${type}`;
|
|
252
|
+
if (seenEdges.has(edgeKey)) return;
|
|
253
|
+
seenEdges.add(edgeKey);
|
|
254
|
+
const outEdges = succByPred.get(predecessorId) ?? [];
|
|
255
|
+
outEdges.push({ succId, lag, type });
|
|
256
|
+
succByPred.set(predecessorId, outEdges);
|
|
257
|
+
const inEdges = predBySucc.get(succId) ?? [];
|
|
258
|
+
inEdges.push({ predId: predecessorId, lag, type });
|
|
259
|
+
predBySucc.set(succId, inEdges);
|
|
260
|
+
};
|
|
261
|
+
for (const succ of leafTasks) {
|
|
262
|
+
for (const dep of succ.dependencies ?? []) {
|
|
263
|
+
const lag = getDependencyLag(dep);
|
|
264
|
+
if (leafIds.has(dep.taskId)) {
|
|
265
|
+
addEdge(dep.taskId, succ.id, lag, dep.type);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
const descendants = leafIdsByAncestor.get(dep.taskId);
|
|
269
|
+
if (!descendants || descendants.size === 0) continue;
|
|
270
|
+
for (const leafId of descendants) {
|
|
271
|
+
if (leafId === succ.id) continue;
|
|
272
|
+
addEdge(leafId, succ.id, lag, dep.type);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const indegree = /* @__PURE__ */ new Map();
|
|
277
|
+
for (const leaf of leafTasks) indegree.set(leaf.id, 0);
|
|
278
|
+
for (const edges of succByPred.values()) {
|
|
279
|
+
for (const { succId } of edges) {
|
|
280
|
+
indegree.set(succId, (indegree.get(succId) ?? 0) + 1);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const order = [];
|
|
284
|
+
const enqueued = /* @__PURE__ */ new Set();
|
|
285
|
+
const queue = leafTasks.filter((leaf) => (indegree.get(leaf.id) ?? 0) === 0).map((leaf) => leaf.id);
|
|
286
|
+
for (const id of queue) enqueued.add(id);
|
|
287
|
+
while (queue.length > 0) {
|
|
288
|
+
const current = queue.shift();
|
|
289
|
+
order.push(current);
|
|
290
|
+
for (const { succId } of succByPred.get(current) ?? []) {
|
|
291
|
+
const next = (indegree.get(succId) ?? 0) - 1;
|
|
292
|
+
indegree.set(succId, next);
|
|
293
|
+
if (next === 0 && !enqueued.has(succId)) {
|
|
294
|
+
enqueued.add(succId);
|
|
295
|
+
queue.push(succId);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const projectFinish = Math.max(
|
|
300
|
+
0,
|
|
301
|
+
...leafTasks.map((leaf) => (scheduledStart.get(leaf.id) ?? 0) + durationOf(leaf))
|
|
302
|
+
);
|
|
303
|
+
const LF = /* @__PURE__ */ new Map();
|
|
304
|
+
const LS = /* @__PURE__ */ new Map();
|
|
305
|
+
for (const leaf of leafTasks) LF.set(leaf.id, projectFinish);
|
|
306
|
+
for (let i = order.length - 1; i >= 0; i--) {
|
|
307
|
+
const id = order[i];
|
|
308
|
+
const task = taskById.get(id);
|
|
309
|
+
const durPred = durationOf(task);
|
|
310
|
+
const outEdges = succByPred.get(id) ?? [];
|
|
311
|
+
let lf = projectFinish;
|
|
312
|
+
if (outEdges.length > 0) {
|
|
313
|
+
const candidates = outEdges.map(({ succId, lag, type }) => {
|
|
314
|
+
const succLs = LS.get(succId) ?? projectFinish;
|
|
315
|
+
const durSucc = durationOf(taskById.get(succId));
|
|
316
|
+
switch (type) {
|
|
317
|
+
case "FS":
|
|
318
|
+
return succLs - lag;
|
|
319
|
+
case "SS":
|
|
320
|
+
return succLs - lag + durPred;
|
|
321
|
+
case "FF":
|
|
322
|
+
return succLs + durSucc - lag;
|
|
323
|
+
case "SF":
|
|
324
|
+
return succLs + durSucc - lag + durPred;
|
|
325
|
+
default:
|
|
326
|
+
return projectFinish;
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
lf = Math.min(projectFinish, ...candidates);
|
|
330
|
+
}
|
|
331
|
+
LF.set(id, lf);
|
|
332
|
+
LS.set(id, lf - durPred);
|
|
333
|
+
}
|
|
334
|
+
const critical = /* @__PURE__ */ new Set();
|
|
335
|
+
const ordered = new Set(order);
|
|
336
|
+
for (const leaf of leafTasks) {
|
|
337
|
+
if (!ordered.has(leaf.id)) continue;
|
|
338
|
+
const hasGraphEdge = (succByPred.get(leaf.id)?.length ?? 0) > 0 || (predBySucc.get(leaf.id)?.length ?? 0) > 0;
|
|
339
|
+
if (!hasGraphEdge && (leaf.dependencies?.length ?? 0) > 0) continue;
|
|
340
|
+
const es = scheduledStart.get(leaf.id) ?? 0;
|
|
341
|
+
const ls = LS.get(leaf.id) ?? 0;
|
|
342
|
+
if (ls - es <= 0) critical.add(leaf.id);
|
|
343
|
+
}
|
|
344
|
+
return critical;
|
|
345
|
+
}
|
|
346
|
+
function extendCriticalIdsToParents(criticalLeafIds, tasks) {
|
|
347
|
+
const result = new Set(criticalLeafIds);
|
|
348
|
+
const parentByChild = /* @__PURE__ */ new Map();
|
|
349
|
+
for (const task of tasks) {
|
|
350
|
+
if (task.parentId) parentByChild.set(task.id, task.parentId);
|
|
351
|
+
}
|
|
352
|
+
const stack = Array.from(criticalLeafIds);
|
|
353
|
+
while (stack.length > 0) {
|
|
354
|
+
const id = stack.pop();
|
|
355
|
+
const parentId = parentByChild.get(id);
|
|
356
|
+
if (parentId && !result.has(parentId)) {
|
|
357
|
+
result.add(parentId);
|
|
358
|
+
stack.push(parentId);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return result;
|
|
362
|
+
}
|
|
363
|
+
|
|
211
364
|
// src/core/scheduling/commands.ts
|
|
212
365
|
function buildTaskRangeFromStart(startDate, duration, businessDays = false, weekendPredicate, snapDirection = 1) {
|
|
213
366
|
const normalizedStart = businessDays && weekendPredicate ? alignToWorkingDay(startDate, snapDirection, weekendPredicate) : normalizeUTCDate(startDate);
|
|
@@ -1190,11 +1343,13 @@ export {
|
|
|
1190
1343
|
calculateSuccessorDate,
|
|
1191
1344
|
cascadeByLinks,
|
|
1192
1345
|
clampTaskRangeForIncomingFS,
|
|
1346
|
+
computeCriticalPath,
|
|
1193
1347
|
computeLagFromDates,
|
|
1194
1348
|
computeParentDates,
|
|
1195
1349
|
computeParentProgress,
|
|
1196
1350
|
createCascadeContext,
|
|
1197
1351
|
detectCycles,
|
|
1352
|
+
extendCriticalIdsToParents,
|
|
1198
1353
|
findParentId,
|
|
1199
1354
|
getAllDependencyEdges,
|
|
1200
1355
|
getAllDescendants,
|