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/core/scheduling/index.d.mts +1 -1
- package/dist/core/scheduling/index.d.ts +1 -1
- package/dist/core/scheduling/index.js +372 -56
- package/dist/core/scheduling/index.js.map +1 -1
- package/dist/core/scheduling/index.mjs +368 -56
- package/dist/core/scheduling/index.mjs.map +1 -1
- package/dist/{index-CliEEiHA.d.mts → index-BlUshzVg.d.mts} +107 -21
- package/dist/{index-CliEEiHA.d.ts → index-BlUshzVg.d.ts} +107 -21
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +378 -56
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +372 -56
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -140,6 +140,53 @@ interface WeekendBlock {
|
|
|
140
140
|
width: number;
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Core scheduling type re-exports.
|
|
145
|
+
* This file is the type gateway into the core scheduling module.
|
|
146
|
+
* Zero React/DOM/date-fns imports.
|
|
147
|
+
*/
|
|
148
|
+
|
|
149
|
+
/** Minimal task shape for scheduling operations */
|
|
150
|
+
interface ScheduleTask {
|
|
151
|
+
id: string;
|
|
152
|
+
startDate: string | Date;
|
|
153
|
+
endDate: string | Date;
|
|
154
|
+
dependencies?: TaskDependency[];
|
|
155
|
+
parentId?: string;
|
|
156
|
+
locked?: boolean;
|
|
157
|
+
progress?: number;
|
|
158
|
+
}
|
|
159
|
+
/** Dependency for scheduling operations */
|
|
160
|
+
interface ScheduleDependency {
|
|
161
|
+
type: LinkType;
|
|
162
|
+
taskId: string;
|
|
163
|
+
lag?: number;
|
|
164
|
+
}
|
|
165
|
+
/** Update for a single task (id + changed fields) */
|
|
166
|
+
interface ScheduleTaskUpdate {
|
|
167
|
+
id: string;
|
|
168
|
+
startDate?: string;
|
|
169
|
+
endDate?: string;
|
|
170
|
+
dependencies?: TaskDependency[];
|
|
171
|
+
progress?: number;
|
|
172
|
+
}
|
|
173
|
+
/** Result of executing a scheduling command */
|
|
174
|
+
interface ScheduleCommandResult {
|
|
175
|
+
/** All tasks that changed as a result of the command (including the source task) */
|
|
176
|
+
changedTasks: Task[];
|
|
177
|
+
/** IDs of changed tasks */
|
|
178
|
+
changedIds: string[];
|
|
179
|
+
}
|
|
180
|
+
/** Options for scheduling commands */
|
|
181
|
+
interface ScheduleCommandOptions {
|
|
182
|
+
/** Account for business days during cascade */
|
|
183
|
+
businessDays?: boolean;
|
|
184
|
+
/** Weekend predicate function */
|
|
185
|
+
weekendPredicate?: (date: Date) => boolean;
|
|
186
|
+
/** Skip cascade, only recalculate the task itself */
|
|
187
|
+
skipCascade?: boolean;
|
|
188
|
+
}
|
|
189
|
+
|
|
143
190
|
/**
|
|
144
191
|
* Pure date math utilities for the core scheduling module.
|
|
145
192
|
* Zero React/DOM/date-fns dependencies.
|
|
@@ -296,30 +343,35 @@ declare function clampTaskRangeForIncomingFS(task: Pick<Task, 'dependencies'>, p
|
|
|
296
343
|
* Recalculate incoming dependency lags after a task's dates change.
|
|
297
344
|
*/
|
|
298
345
|
declare function recalculateIncomingLags(task: Task, newStartDate: Date, newEndDate: Date, allTasks: Task[], businessDays?: boolean, weekendPredicate?: (date: Date) => boolean): NonNullable<Task['dependencies']>;
|
|
346
|
+
|
|
299
347
|
/**
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
* Extracted from useTaskDrag.ts resolveDraggedRange.
|
|
348
|
+
* Command-level scheduling API.
|
|
349
|
+
* High-level functions that compose low-level scheduling primitives.
|
|
350
|
+
* Zero React/DOM/date-fns imports.
|
|
305
351
|
*/
|
|
306
|
-
|
|
307
|
-
start: Date;
|
|
308
|
-
end: Date;
|
|
309
|
-
};
|
|
352
|
+
|
|
310
353
|
/**
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
* Extracted from useTaskDrag.ts clampDraggedRangeForIncomingFS.
|
|
354
|
+
* Move a task to a new start date with cascade and lag recalculation.
|
|
355
|
+
* Identical to manual composition: moveTaskRange -> recalculateIncomingLags -> universalCascade.
|
|
315
356
|
*/
|
|
316
|
-
declare function
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
357
|
+
declare function moveTaskWithCascade(taskId: string, newStart: Date, snapshot: Task[], options?: ScheduleCommandOptions): ScheduleCommandResult;
|
|
358
|
+
/**
|
|
359
|
+
* Resize a task by changing its start or end date.
|
|
360
|
+
* anchor='end': new end date, start stays fixed.
|
|
361
|
+
* anchor='start': new start date, end stays fixed.
|
|
362
|
+
*/
|
|
363
|
+
declare function resizeTaskWithCascade(taskId: string, anchor: 'start' | 'end', newDate: Date, snapshot: Task[], options?: ScheduleCommandOptions): ScheduleCommandResult;
|
|
364
|
+
/**
|
|
365
|
+
* Recalculate a task's dates based on its dependency constraints.
|
|
366
|
+
* Finds all predecessors and computes the most constrained date.
|
|
367
|
+
*/
|
|
368
|
+
declare function recalculateTaskFromDependencies(taskId: string, snapshot: Task[], options?: ScheduleCommandOptions): ScheduleCommandResult;
|
|
369
|
+
/**
|
|
370
|
+
* Full project schedule recalculation.
|
|
371
|
+
* Recomputes the project against a continuously updated working snapshot.
|
|
372
|
+
* Returns only tasks whose normalized state changed.
|
|
373
|
+
*/
|
|
374
|
+
declare function recalculateProjectSchedule(snapshot: Task[], options?: ScheduleCommandOptions): ScheduleCommandResult;
|
|
323
375
|
|
|
324
376
|
/**
|
|
325
377
|
* Dependency validation and cycle detection.
|
|
@@ -407,4 +459,38 @@ declare function isAncestorTask(ancestorId: string, taskId: string, tasks: Task[
|
|
|
407
459
|
*/
|
|
408
460
|
declare function areTasksHierarchicallyRelated(taskId1: string, taskId2: string, tasks: Task[]): boolean;
|
|
409
461
|
|
|
410
|
-
|
|
462
|
+
/**
|
|
463
|
+
* UI adapter: converts pixel coordinates to date ranges for drag interactions.
|
|
464
|
+
* @module adapters/scheduling
|
|
465
|
+
*
|
|
466
|
+
* These functions bridge the chart's pixel-space (left, width, dayWidth)
|
|
467
|
+
* with the scheduling domain's date-space. They depend on core scheduling
|
|
468
|
+
* primitives but are NOT part of the domain core themselves.
|
|
469
|
+
*/
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Convert pixel coordinates to a date range, applying business-day alignment
|
|
473
|
+
* when businessDays mode is active. This is the pure scheduling core of
|
|
474
|
+
* drag-to-date conversion.
|
|
475
|
+
*
|
|
476
|
+
* Extracted from useTaskDrag.ts resolveDraggedRange.
|
|
477
|
+
*/
|
|
478
|
+
declare function resolveDateRangeFromPixels(mode: 'move' | 'resize-left' | 'resize-right', left: number, width: number, monthStart: Date, dayWidth: number, task: Task, businessDays?: boolean, weekendPredicate?: (date: Date) => boolean): {
|
|
479
|
+
start: Date;
|
|
480
|
+
end: Date;
|
|
481
|
+
};
|
|
482
|
+
/**
|
|
483
|
+
* Clamp a proposed date range based on incoming FS dependencies.
|
|
484
|
+
* For resize-right mode, returns range unchanged (only start is clamped).
|
|
485
|
+
*
|
|
486
|
+
* Extracted from useTaskDrag.ts clampDraggedRangeForIncomingFS.
|
|
487
|
+
*/
|
|
488
|
+
declare function clampDateRangeForIncomingFS(task: Task, range: {
|
|
489
|
+
start: Date;
|
|
490
|
+
end: Date;
|
|
491
|
+
}, allTasks: Task[], mode: 'move' | 'resize-left' | 'resize-right', businessDays?: boolean, weekendPredicate?: (date: Date) => boolean): {
|
|
492
|
+
start: Date;
|
|
493
|
+
end: Date;
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
export { type ScheduleTask as $, isAncestorTask as A, isTaskParent as B, moveTaskRange as C, DAY_MS as D, moveTaskWithCascade as E, normalizeDependencyLag as F, type GanttDateRange as G, normalizeUTCDate as H, parseDateOnly as I, recalculateIncomingLags as J, recalculateProjectSchedule as K, type LinkType as L, type MonthSpan as M, recalculateTaskFromDependencies as N, reflowTasksOnModeSwitch as O, removeDependenciesBetweenTasks as P, resizeTaskWithCascade as Q, resolveDateRangeFromPixels as R, shiftBusinessDayOffset as S, type Task as T, universalCascade as U, type ValidationResult as V, type WeekendBlock as W, validateDependencies as X, type ScheduleCommandOptions as Y, type ScheduleCommandResult as Z, type ScheduleDependency as _, type DependencyError as a, type ScheduleTaskUpdate as a0, type TaskDependency as a1, addBusinessDays as a2, getBusinessDaysCount as a3, subtractBusinessDays as a4, type GridConfig as b, type GridLine as c, type TaskBarGeometry as d, alignToWorkingDay as e, areTasksHierarchicallyRelated as f, buildAdjacencyList as g, buildTaskRangeFromEnd as h, buildTaskRangeFromStart as i, calculateSuccessorDate as j, cascadeByLinks as k, clampDateRangeForIncomingFS as l, clampTaskRangeForIncomingFS as m, computeLagFromDates as n, computeParentDates as o, computeParentProgress as p, detectCycles as q, findParentId as r, getAllDependencyEdges as s, getAllDescendants as t, getBusinessDayOffset as u, getChildren as v, getDependencyLag as w, getSuccessorChain as x, getTaskDuration as y, getTransitiveCascadeChain as z };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React$1, { ReactNode } from 'react';
|
|
2
|
-
import { T as Task$1, V as ValidationResult } from './index-
|
|
3
|
-
export { D as DAY_MS, a as DependencyError, G as GanttDateRange, b as GridConfig, c as GridLine, L as LinkType, M as MonthSpan, d as TaskBarGeometry, W as WeekendBlock, e as alignToWorkingDay, f as areTasksHierarchicallyRelated, g as buildAdjacencyList, h as buildTaskRangeFromEnd, i as buildTaskRangeFromStart, j as calculateSuccessorDate, k as cascadeByLinks, l as
|
|
2
|
+
import { T as Task$1, V as ValidationResult } from './index-BlUshzVg.mjs';
|
|
3
|
+
export { D as DAY_MS, a as DependencyError, G as GanttDateRange, b as GridConfig, c as GridLine, L as LinkType, M as MonthSpan, d as TaskBarGeometry, W as WeekendBlock, e as alignToWorkingDay, f as areTasksHierarchicallyRelated, g as buildAdjacencyList, h as buildTaskRangeFromEnd, i as buildTaskRangeFromStart, j as calculateSuccessorDate, k as cascadeByLinks, l as clampDateRangeForIncomingFS, m as clampTaskRangeForIncomingFS, n as computeLagFromDates, o as computeParentDates, p as computeParentProgress, q as detectCycles, r as findParentId, s as getAllDependencyEdges, t as getAllDescendants, u as getBusinessDayOffset, v as getChildren, w as getDependencyLag, x as getSuccessorChain, y as getTaskDuration, z as getTransitiveCascadeChain, A as isAncestorTask, B as isTaskParent, C as moveTaskRange, E as moveTaskWithCascade, F as normalizeDependencyLag, H as normalizeUTCDate, I as parseDateOnly, J as recalculateIncomingLags, K as recalculateProjectSchedule, N as recalculateTaskFromDependencies, O as reflowTasksOnModeSwitch, P as removeDependenciesBetweenTasks, Q as resizeTaskWithCascade, R as resolveDateRangeFromPixels, S as shiftBusinessDayOffset, U as universalCascade, X as validateDependencies } from './index-BlUshzVg.mjs';
|
|
4
4
|
import * as RadixPopover from '@radix-ui/react-popover';
|
|
5
5
|
|
|
6
6
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React$1, { ReactNode } from 'react';
|
|
2
|
-
import { T as Task$1, V as ValidationResult } from './index-
|
|
3
|
-
export { D as DAY_MS, a as DependencyError, G as GanttDateRange, b as GridConfig, c as GridLine, L as LinkType, M as MonthSpan, d as TaskBarGeometry, W as WeekendBlock, e as alignToWorkingDay, f as areTasksHierarchicallyRelated, g as buildAdjacencyList, h as buildTaskRangeFromEnd, i as buildTaskRangeFromStart, j as calculateSuccessorDate, k as cascadeByLinks, l as
|
|
2
|
+
import { T as Task$1, V as ValidationResult } from './index-BlUshzVg.js';
|
|
3
|
+
export { D as DAY_MS, a as DependencyError, G as GanttDateRange, b as GridConfig, c as GridLine, L as LinkType, M as MonthSpan, d as TaskBarGeometry, W as WeekendBlock, e as alignToWorkingDay, f as areTasksHierarchicallyRelated, g as buildAdjacencyList, h as buildTaskRangeFromEnd, i as buildTaskRangeFromStart, j as calculateSuccessorDate, k as cascadeByLinks, l as clampDateRangeForIncomingFS, m as clampTaskRangeForIncomingFS, n as computeLagFromDates, o as computeParentDates, p as computeParentProgress, q as detectCycles, r as findParentId, s as getAllDependencyEdges, t as getAllDescendants, u as getBusinessDayOffset, v as getChildren, w as getDependencyLag, x as getSuccessorChain, y as getTaskDuration, z as getTransitiveCascadeChain, A as isAncestorTask, B as isTaskParent, C as moveTaskRange, E as moveTaskWithCascade, F as normalizeDependencyLag, H as normalizeUTCDate, I as parseDateOnly, J as recalculateIncomingLags, K as recalculateProjectSchedule, N as recalculateTaskFromDependencies, O as reflowTasksOnModeSwitch, P as removeDependenciesBetweenTasks, Q as resizeTaskWithCascade, R as resolveDateRangeFromPixels, S as shiftBusinessDayOffset, U as universalCascade, X as validateDependencies } from './index-BlUshzVg.js';
|
|
4
4
|
import * as RadixPopover from '@radix-ui/react-popover';
|
|
5
5
|
|
|
6
6
|
/**
|
package/dist/index.js
CHANGED
|
@@ -537,6 +537,7 @@ __export(index_exports, {
|
|
|
537
537
|
calculateWeekGridLines: () => calculateWeekGridLines,
|
|
538
538
|
calculateWeekendBlocks: () => calculateWeekendBlocks,
|
|
539
539
|
cascadeByLinks: () => cascadeByLinks,
|
|
540
|
+
clampDateRangeForIncomingFS: () => clampDateRangeForIncomingFS,
|
|
540
541
|
clampTaskRangeForIncomingFS: () => clampTaskRangeForIncomingFS,
|
|
541
542
|
computeLagFromDates: () => computeLagFromDates,
|
|
542
543
|
computeParentDates: () => computeParentDates,
|
|
@@ -576,6 +577,7 @@ __export(index_exports, {
|
|
|
576
577
|
isToday: () => isToday,
|
|
577
578
|
isWeekend: () => isWeekend,
|
|
578
579
|
moveTaskRange: () => moveTaskRange,
|
|
580
|
+
moveTaskWithCascade: () => moveTaskWithCascade,
|
|
579
581
|
nameContains: () => nameContains,
|
|
580
582
|
normalizeDependencyLag: () => normalizeDependencyLag,
|
|
581
583
|
normalizeHierarchyTasks: () => normalizeHierarchyTasks,
|
|
@@ -588,8 +590,12 @@ __export(index_exports, {
|
|
|
588
590
|
pixelsToDate: () => pixelsToDate,
|
|
589
591
|
progressInRange: () => progressInRange,
|
|
590
592
|
recalculateIncomingLags: () => recalculateIncomingLags,
|
|
593
|
+
recalculateProjectSchedule: () => recalculateProjectSchedule,
|
|
594
|
+
recalculateTaskFromDependencies: () => recalculateTaskFromDependencies,
|
|
591
595
|
reflowTasksOnModeSwitch: () => reflowTasksOnModeSwitch,
|
|
592
596
|
removeDependenciesBetweenTasks: () => removeDependenciesBetweenTasks,
|
|
597
|
+
resizeTaskWithCascade: () => resizeTaskWithCascade,
|
|
598
|
+
resolveDateRangeFromPixels: () => resolveDateRangeFromPixels,
|
|
593
599
|
shiftBusinessDayOffset: () => shiftBusinessDayOffset,
|
|
594
600
|
subtractBusinessDays: () => subtractBusinessDays2,
|
|
595
601
|
universalCascade: () => universalCascade,
|
|
@@ -934,62 +940,6 @@ function recalculateIncomingLags(task, newStartDate, newEndDate, allTasks, busin
|
|
|
934
940
|
return { ...dep, lag: nextLag };
|
|
935
941
|
});
|
|
936
942
|
}
|
|
937
|
-
function resolveDateRangeFromPixels(mode, left, width, monthStart, dayWidth, task, businessDays, weekendPredicate) {
|
|
938
|
-
const dayOffset = Math.round(left / dayWidth);
|
|
939
|
-
const rawStartDate = new Date(Date.UTC(
|
|
940
|
-
monthStart.getUTCFullYear(),
|
|
941
|
-
monthStart.getUTCMonth(),
|
|
942
|
-
monthStart.getUTCDate() + dayOffset
|
|
943
|
-
));
|
|
944
|
-
const rawEndOffset = dayOffset + Math.round(width / dayWidth) - 1;
|
|
945
|
-
const rawEndDate = new Date(Date.UTC(
|
|
946
|
-
monthStart.getUTCFullYear(),
|
|
947
|
-
monthStart.getUTCMonth(),
|
|
948
|
-
monthStart.getUTCDate() + rawEndOffset
|
|
949
|
-
));
|
|
950
|
-
if (!(businessDays && weekendPredicate)) {
|
|
951
|
-
return { start: rawStartDate, end: rawEndDate };
|
|
952
|
-
}
|
|
953
|
-
if (mode === "move") {
|
|
954
|
-
const originalStart2 = new Date(task.startDate);
|
|
955
|
-
const snapDirection2 = rawStartDate.getTime() >= originalStart2.getTime() ? 1 : -1;
|
|
956
|
-
return moveTaskRange(
|
|
957
|
-
task.startDate,
|
|
958
|
-
task.endDate,
|
|
959
|
-
rawStartDate,
|
|
960
|
-
true,
|
|
961
|
-
weekendPredicate,
|
|
962
|
-
snapDirection2
|
|
963
|
-
);
|
|
964
|
-
}
|
|
965
|
-
if (mode === "resize-right") {
|
|
966
|
-
const fixedStart = new Date(task.startDate);
|
|
967
|
-
const originalEnd = new Date(task.endDate);
|
|
968
|
-
const snapDirection2 = rawEndDate.getTime() >= originalEnd.getTime() ? 1 : -1;
|
|
969
|
-
const alignedEnd = alignToWorkingDay(rawEndDate, snapDirection2, weekendPredicate);
|
|
970
|
-
const duration2 = Math.max(1, getBusinessDaysCount(fixedStart, alignedEnd, weekendPredicate));
|
|
971
|
-
return buildTaskRangeFromStart(fixedStart, duration2, true, weekendPredicate);
|
|
972
|
-
}
|
|
973
|
-
const fixedEnd = new Date(task.endDate);
|
|
974
|
-
const originalStart = new Date(task.startDate);
|
|
975
|
-
const snapDirection = rawStartDate.getTime() >= originalStart.getTime() ? 1 : -1;
|
|
976
|
-
const alignedStart = alignToWorkingDay(rawStartDate, snapDirection, weekendPredicate);
|
|
977
|
-
const duration = Math.max(1, getBusinessDaysCount(alignedStart, fixedEnd, weekendPredicate));
|
|
978
|
-
return buildTaskRangeFromEnd(fixedEnd, duration, true, weekendPredicate);
|
|
979
|
-
}
|
|
980
|
-
function clampDateRangeForIncomingFS(task, range, allTasks, mode, businessDays, weekendPredicate) {
|
|
981
|
-
if (mode === "resize-right") {
|
|
982
|
-
return range;
|
|
983
|
-
}
|
|
984
|
-
return clampTaskRangeForIncomingFS(
|
|
985
|
-
task,
|
|
986
|
-
range.start,
|
|
987
|
-
range.end,
|
|
988
|
-
allTasks,
|
|
989
|
-
businessDays,
|
|
990
|
-
weekendPredicate
|
|
991
|
-
);
|
|
992
|
-
}
|
|
993
943
|
|
|
994
944
|
// src/core/scheduling/cascade.ts
|
|
995
945
|
function getSuccessorChain(draggedTaskId, allTasks, linkTypes = ["FS"]) {
|
|
@@ -1306,6 +1256,313 @@ function reflowTasksOnModeSwitch(sourceTasks, toBusinessDays, weekendPredicate)
|
|
|
1306
1256
|
return tasks;
|
|
1307
1257
|
}
|
|
1308
1258
|
|
|
1259
|
+
// src/core/scheduling/execute.ts
|
|
1260
|
+
init_dateMath();
|
|
1261
|
+
function toIsoDate(date) {
|
|
1262
|
+
return date.toISOString().split("T")[0];
|
|
1263
|
+
}
|
|
1264
|
+
function createChangedResult(snapshot, nextTasks) {
|
|
1265
|
+
const originalById = new Map(snapshot.map((task) => [task.id, task]));
|
|
1266
|
+
const changedTasks = nextTasks.filter((task) => JSON.stringify(originalById.get(task.id)) !== JSON.stringify(task));
|
|
1267
|
+
return {
|
|
1268
|
+
changedTasks,
|
|
1269
|
+
changedIds: changedTasks.map((task) => task.id)
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
function moveTaskWithCascade(taskId, newStart, snapshot, options) {
|
|
1273
|
+
const task = snapshot.find((t) => t.id === taskId);
|
|
1274
|
+
if (!task) {
|
|
1275
|
+
return { changedTasks: [], changedIds: [] };
|
|
1276
|
+
}
|
|
1277
|
+
const businessDays = options?.businessDays ?? false;
|
|
1278
|
+
const weekendPredicate = options?.weekendPredicate;
|
|
1279
|
+
const newRange = moveTaskRange(
|
|
1280
|
+
task.startDate,
|
|
1281
|
+
task.endDate,
|
|
1282
|
+
newStart,
|
|
1283
|
+
businessDays,
|
|
1284
|
+
weekendPredicate
|
|
1285
|
+
);
|
|
1286
|
+
const updatedDependencies = recalculateIncomingLags(
|
|
1287
|
+
task,
|
|
1288
|
+
newRange.start,
|
|
1289
|
+
newRange.end,
|
|
1290
|
+
snapshot,
|
|
1291
|
+
businessDays,
|
|
1292
|
+
weekendPredicate
|
|
1293
|
+
);
|
|
1294
|
+
const movedTask = {
|
|
1295
|
+
...task,
|
|
1296
|
+
startDate: newRange.start.toISOString().split("T")[0],
|
|
1297
|
+
endDate: newRange.end.toISOString().split("T")[0],
|
|
1298
|
+
dependencies: updatedDependencies
|
|
1299
|
+
};
|
|
1300
|
+
if (options?.skipCascade) {
|
|
1301
|
+
return {
|
|
1302
|
+
changedTasks: [movedTask],
|
|
1303
|
+
changedIds: [movedTask.id]
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
const cascadeResult = universalCascade(
|
|
1307
|
+
movedTask,
|
|
1308
|
+
newRange.start,
|
|
1309
|
+
newRange.end,
|
|
1310
|
+
snapshot,
|
|
1311
|
+
businessDays,
|
|
1312
|
+
weekendPredicate
|
|
1313
|
+
);
|
|
1314
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
1315
|
+
resultMap.set(movedTask.id, movedTask);
|
|
1316
|
+
for (const t of cascadeResult) {
|
|
1317
|
+
resultMap.set(t.id, t);
|
|
1318
|
+
}
|
|
1319
|
+
const changedTasks = Array.from(resultMap.values());
|
|
1320
|
+
return {
|
|
1321
|
+
changedTasks,
|
|
1322
|
+
changedIds: changedTasks.map((t) => t.id)
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
function resizeTaskWithCascade(taskId, anchor, newDate, snapshot, options) {
|
|
1326
|
+
const task = snapshot.find((t) => t.id === taskId);
|
|
1327
|
+
if (!task) {
|
|
1328
|
+
return { changedTasks: [], changedIds: [] };
|
|
1329
|
+
}
|
|
1330
|
+
const businessDays = options?.businessDays ?? false;
|
|
1331
|
+
const weekendPredicate = options?.weekendPredicate;
|
|
1332
|
+
const originalStart = parseDateOnly(task.startDate);
|
|
1333
|
+
const originalEnd = parseDateOnly(task.endDate);
|
|
1334
|
+
let newRange;
|
|
1335
|
+
if (anchor === "end") {
|
|
1336
|
+
newRange = { start: originalStart, end: newDate };
|
|
1337
|
+
} else {
|
|
1338
|
+
newRange = { start: newDate, end: originalEnd };
|
|
1339
|
+
}
|
|
1340
|
+
const updatedDependencies = recalculateIncomingLags(
|
|
1341
|
+
task,
|
|
1342
|
+
newRange.start,
|
|
1343
|
+
newRange.end,
|
|
1344
|
+
snapshot,
|
|
1345
|
+
businessDays,
|
|
1346
|
+
weekendPredicate
|
|
1347
|
+
);
|
|
1348
|
+
const resizedTask = {
|
|
1349
|
+
...task,
|
|
1350
|
+
startDate: newRange.start.toISOString().split("T")[0],
|
|
1351
|
+
endDate: newRange.end.toISOString().split("T")[0],
|
|
1352
|
+
dependencies: updatedDependencies
|
|
1353
|
+
};
|
|
1354
|
+
if (options?.skipCascade) {
|
|
1355
|
+
return {
|
|
1356
|
+
changedTasks: [resizedTask],
|
|
1357
|
+
changedIds: [resizedTask.id]
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
const cascadeResult = universalCascade(
|
|
1361
|
+
resizedTask,
|
|
1362
|
+
newRange.start,
|
|
1363
|
+
newRange.end,
|
|
1364
|
+
snapshot,
|
|
1365
|
+
businessDays,
|
|
1366
|
+
weekendPredicate
|
|
1367
|
+
);
|
|
1368
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
1369
|
+
resultMap.set(resizedTask.id, resizedTask);
|
|
1370
|
+
for (const t of cascadeResult) {
|
|
1371
|
+
resultMap.set(t.id, t);
|
|
1372
|
+
}
|
|
1373
|
+
const changedTasks = Array.from(resultMap.values());
|
|
1374
|
+
return {
|
|
1375
|
+
changedTasks,
|
|
1376
|
+
changedIds: changedTasks.map((t) => t.id)
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
function recalculateTaskFromDependencies(taskId, snapshot, options) {
|
|
1380
|
+
const task = snapshot.find((t) => t.id === taskId);
|
|
1381
|
+
if (!task) {
|
|
1382
|
+
return { changedTasks: [], changedIds: [] };
|
|
1383
|
+
}
|
|
1384
|
+
const businessDays = options?.businessDays ?? false;
|
|
1385
|
+
const weekendPredicate = options?.weekendPredicate;
|
|
1386
|
+
if (!task.dependencies || task.dependencies.length === 0) {
|
|
1387
|
+
return {
|
|
1388
|
+
changedTasks: [task],
|
|
1389
|
+
changedIds: [task.id]
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
let constrainedStart = null;
|
|
1393
|
+
let constrainedEnd = null;
|
|
1394
|
+
for (const dep of task.dependencies) {
|
|
1395
|
+
const predecessor = snapshot.find((t) => t.id === dep.taskId);
|
|
1396
|
+
if (!predecessor) continue;
|
|
1397
|
+
const predStart = parseDateOnly(predecessor.startDate);
|
|
1398
|
+
const predEnd = parseDateOnly(predecessor.endDate);
|
|
1399
|
+
const constraintDate = calculateSuccessorDate(
|
|
1400
|
+
predStart,
|
|
1401
|
+
predEnd,
|
|
1402
|
+
dep.type,
|
|
1403
|
+
getDependencyLag(dep),
|
|
1404
|
+
businessDays,
|
|
1405
|
+
weekendPredicate
|
|
1406
|
+
);
|
|
1407
|
+
const duration = getTaskDuration(
|
|
1408
|
+
parseDateOnly(task.startDate),
|
|
1409
|
+
parseDateOnly(task.endDate),
|
|
1410
|
+
businessDays,
|
|
1411
|
+
weekendPredicate
|
|
1412
|
+
);
|
|
1413
|
+
let range;
|
|
1414
|
+
if (dep.type === "FS" || dep.type === "SS") {
|
|
1415
|
+
range = buildTaskRangeFromStart(constraintDate, duration, businessDays, weekendPredicate);
|
|
1416
|
+
} else {
|
|
1417
|
+
range = buildTaskRangeFromEnd(constraintDate, duration, businessDays, weekendPredicate);
|
|
1418
|
+
}
|
|
1419
|
+
if (!constrainedStart || range.start.getTime() > constrainedStart.getTime()) {
|
|
1420
|
+
constrainedStart = range.start;
|
|
1421
|
+
constrainedEnd = range.end;
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
if (!constrainedStart || !constrainedEnd) {
|
|
1425
|
+
return {
|
|
1426
|
+
changedTasks: [task],
|
|
1427
|
+
changedIds: [task.id]
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
const updatedDependencies = recalculateIncomingLags(
|
|
1431
|
+
task,
|
|
1432
|
+
constrainedStart,
|
|
1433
|
+
constrainedEnd,
|
|
1434
|
+
snapshot,
|
|
1435
|
+
businessDays,
|
|
1436
|
+
weekendPredicate
|
|
1437
|
+
);
|
|
1438
|
+
const recalculatedTask = {
|
|
1439
|
+
...task,
|
|
1440
|
+
startDate: constrainedStart.toISOString().split("T")[0],
|
|
1441
|
+
endDate: constrainedEnd.toISOString().split("T")[0],
|
|
1442
|
+
dependencies: updatedDependencies
|
|
1443
|
+
};
|
|
1444
|
+
if (options?.skipCascade) {
|
|
1445
|
+
return {
|
|
1446
|
+
changedTasks: [recalculatedTask],
|
|
1447
|
+
changedIds: [recalculatedTask.id]
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
const cascadeResult = universalCascade(
|
|
1451
|
+
recalculatedTask,
|
|
1452
|
+
constrainedStart,
|
|
1453
|
+
constrainedEnd,
|
|
1454
|
+
snapshot,
|
|
1455
|
+
businessDays,
|
|
1456
|
+
weekendPredicate
|
|
1457
|
+
);
|
|
1458
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
1459
|
+
resultMap.set(recalculatedTask.id, recalculatedTask);
|
|
1460
|
+
for (const t of cascadeResult) {
|
|
1461
|
+
resultMap.set(t.id, t);
|
|
1462
|
+
}
|
|
1463
|
+
const changedTasks = Array.from(resultMap.values());
|
|
1464
|
+
return {
|
|
1465
|
+
changedTasks,
|
|
1466
|
+
changedIds: changedTasks.map((t) => t.id)
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
function recalculateProjectSchedule(snapshot, options) {
|
|
1470
|
+
const businessDays = options?.businessDays ?? false;
|
|
1471
|
+
const weekendPredicate = options?.weekendPredicate;
|
|
1472
|
+
const workingMap = new Map(snapshot.map((task) => [task.id, { ...task }]));
|
|
1473
|
+
const indegree = /* @__PURE__ */ new Map();
|
|
1474
|
+
const successorIdsByTask = /* @__PURE__ */ new Map();
|
|
1475
|
+
for (const task of snapshot) {
|
|
1476
|
+
indegree.set(task.id, 0);
|
|
1477
|
+
successorIdsByTask.set(task.id, []);
|
|
1478
|
+
}
|
|
1479
|
+
for (const task of snapshot) {
|
|
1480
|
+
for (const dep of task.dependencies ?? []) {
|
|
1481
|
+
if (!workingMap.has(dep.taskId)) {
|
|
1482
|
+
continue;
|
|
1483
|
+
}
|
|
1484
|
+
indegree.set(task.id, (indegree.get(task.id) ?? 0) + 1);
|
|
1485
|
+
successorIdsByTask.get(dep.taskId)?.push(task.id);
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
const queue = snapshot.filter((task) => (indegree.get(task.id) ?? 0) === 0).map((task) => task.id);
|
|
1489
|
+
while (queue.length > 0) {
|
|
1490
|
+
const currentId = queue.shift();
|
|
1491
|
+
for (const successorId of successorIdsByTask.get(currentId) ?? []) {
|
|
1492
|
+
const nextIndegree = (indegree.get(successorId) ?? 0) - 1;
|
|
1493
|
+
indegree.set(successorId, nextIndegree);
|
|
1494
|
+
if (nextIndegree !== 0) {
|
|
1495
|
+
continue;
|
|
1496
|
+
}
|
|
1497
|
+
const currentTask = workingMap.get(successorId);
|
|
1498
|
+
if (!currentTask || currentTask.locked || !currentTask.dependencies?.length) {
|
|
1499
|
+
queue.push(successorId);
|
|
1500
|
+
continue;
|
|
1501
|
+
}
|
|
1502
|
+
const duration = getTaskDuration(
|
|
1503
|
+
parseDateOnly(currentTask.startDate),
|
|
1504
|
+
parseDateOnly(currentTask.endDate),
|
|
1505
|
+
businessDays,
|
|
1506
|
+
weekendPredicate
|
|
1507
|
+
);
|
|
1508
|
+
let constrainedRange = null;
|
|
1509
|
+
for (const dep of currentTask.dependencies) {
|
|
1510
|
+
const predecessor = workingMap.get(dep.taskId);
|
|
1511
|
+
if (!predecessor) {
|
|
1512
|
+
continue;
|
|
1513
|
+
}
|
|
1514
|
+
const predecessorStart = parseDateOnly(predecessor.startDate);
|
|
1515
|
+
const predecessorEnd = parseDateOnly(predecessor.endDate);
|
|
1516
|
+
const constraintDate = calculateSuccessorDate(
|
|
1517
|
+
predecessorStart,
|
|
1518
|
+
predecessorEnd,
|
|
1519
|
+
dep.type,
|
|
1520
|
+
getDependencyLag(dep),
|
|
1521
|
+
businessDays,
|
|
1522
|
+
weekendPredicate
|
|
1523
|
+
);
|
|
1524
|
+
const candidateRange = dep.type === "FS" || dep.type === "SS" ? buildTaskRangeFromStart(constraintDate, duration, businessDays, weekendPredicate) : buildTaskRangeFromEnd(constraintDate, duration, businessDays, weekendPredicate);
|
|
1525
|
+
if (!constrainedRange || candidateRange.start.getTime() > constrainedRange.start.getTime() || candidateRange.start.getTime() === constrainedRange.start.getTime() && candidateRange.end.getTime() > constrainedRange.end.getTime()) {
|
|
1526
|
+
constrainedRange = candidateRange;
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
if (!constrainedRange) {
|
|
1530
|
+
queue.push(successorId);
|
|
1531
|
+
continue;
|
|
1532
|
+
}
|
|
1533
|
+
workingMap.set(successorId, {
|
|
1534
|
+
...currentTask,
|
|
1535
|
+
startDate: toIsoDate(constrainedRange.start),
|
|
1536
|
+
endDate: toIsoDate(constrainedRange.end)
|
|
1537
|
+
});
|
|
1538
|
+
queue.push(successorId);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
const parentsByDepth = snapshot.filter((task) => isTaskParent(task.id, snapshot)).map((task) => {
|
|
1542
|
+
let depth = 0;
|
|
1543
|
+
let current = task.parentId ? workingMap.get(task.parentId) : void 0;
|
|
1544
|
+
while (current) {
|
|
1545
|
+
depth++;
|
|
1546
|
+
current = current.parentId ? workingMap.get(current.parentId) : void 0;
|
|
1547
|
+
}
|
|
1548
|
+
return { taskId: task.id, depth };
|
|
1549
|
+
}).sort((left, right) => right.depth - left.depth);
|
|
1550
|
+
const workingTasks = () => Array.from(workingMap.values());
|
|
1551
|
+
for (const { taskId } of parentsByDepth) {
|
|
1552
|
+
const parent = workingMap.get(taskId);
|
|
1553
|
+
if (!parent || parent.locked) {
|
|
1554
|
+
continue;
|
|
1555
|
+
}
|
|
1556
|
+
const { startDate, endDate } = computeParentDates(taskId, workingTasks());
|
|
1557
|
+
workingMap.set(taskId, {
|
|
1558
|
+
...parent,
|
|
1559
|
+
startDate: toIsoDate(startDate),
|
|
1560
|
+
endDate: toIsoDate(endDate)
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
return createChangedResult(snapshot, Array.from(workingMap.values()));
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1309
1566
|
// src/core/scheduling/validation.ts
|
|
1310
1567
|
function buildAdjacencyList(tasks) {
|
|
1311
1568
|
const graph = /* @__PURE__ */ new Map();
|
|
@@ -1405,6 +1662,65 @@ function validateDependencies(tasks) {
|
|
|
1405
1662
|
};
|
|
1406
1663
|
}
|
|
1407
1664
|
|
|
1665
|
+
// src/adapters/scheduling/drag.ts
|
|
1666
|
+
init_dateMath();
|
|
1667
|
+
function resolveDateRangeFromPixels(mode, left, width, monthStart, dayWidth, task, businessDays, weekendPredicate) {
|
|
1668
|
+
const dayOffset = Math.round(left / dayWidth);
|
|
1669
|
+
const rawStartDate = new Date(Date.UTC(
|
|
1670
|
+
monthStart.getUTCFullYear(),
|
|
1671
|
+
monthStart.getUTCMonth(),
|
|
1672
|
+
monthStart.getUTCDate() + dayOffset
|
|
1673
|
+
));
|
|
1674
|
+
const rawEndOffset = dayOffset + Math.round(width / dayWidth) - 1;
|
|
1675
|
+
const rawEndDate = new Date(Date.UTC(
|
|
1676
|
+
monthStart.getUTCFullYear(),
|
|
1677
|
+
monthStart.getUTCMonth(),
|
|
1678
|
+
monthStart.getUTCDate() + rawEndOffset
|
|
1679
|
+
));
|
|
1680
|
+
if (!(businessDays && weekendPredicate)) {
|
|
1681
|
+
return { start: rawStartDate, end: rawEndDate };
|
|
1682
|
+
}
|
|
1683
|
+
if (mode === "move") {
|
|
1684
|
+
const originalStart2 = new Date(task.startDate);
|
|
1685
|
+
const snapDirection2 = rawStartDate.getTime() >= originalStart2.getTime() ? 1 : -1;
|
|
1686
|
+
return moveTaskRange(
|
|
1687
|
+
task.startDate,
|
|
1688
|
+
task.endDate,
|
|
1689
|
+
rawStartDate,
|
|
1690
|
+
true,
|
|
1691
|
+
weekendPredicate,
|
|
1692
|
+
snapDirection2
|
|
1693
|
+
);
|
|
1694
|
+
}
|
|
1695
|
+
if (mode === "resize-right") {
|
|
1696
|
+
const fixedStart = new Date(task.startDate);
|
|
1697
|
+
const originalEnd = new Date(task.endDate);
|
|
1698
|
+
const snapDirection2 = rawEndDate.getTime() >= originalEnd.getTime() ? 1 : -1;
|
|
1699
|
+
const alignedEnd = alignToWorkingDay(rawEndDate, snapDirection2, weekendPredicate);
|
|
1700
|
+
const duration2 = Math.max(1, getBusinessDaysCount(fixedStart, alignedEnd, weekendPredicate));
|
|
1701
|
+
return buildTaskRangeFromStart(fixedStart, duration2, true, weekendPredicate);
|
|
1702
|
+
}
|
|
1703
|
+
const fixedEnd = new Date(task.endDate);
|
|
1704
|
+
const originalStart = new Date(task.startDate);
|
|
1705
|
+
const snapDirection = rawStartDate.getTime() >= originalStart.getTime() ? 1 : -1;
|
|
1706
|
+
const alignedStart = alignToWorkingDay(rawStartDate, snapDirection, weekendPredicate);
|
|
1707
|
+
const duration = Math.max(1, getBusinessDaysCount(alignedStart, fixedEnd, weekendPredicate));
|
|
1708
|
+
return buildTaskRangeFromEnd(fixedEnd, duration, true, weekendPredicate);
|
|
1709
|
+
}
|
|
1710
|
+
function clampDateRangeForIncomingFS(task, range, allTasks, mode, businessDays, weekendPredicate) {
|
|
1711
|
+
if (mode === "resize-right") {
|
|
1712
|
+
return range;
|
|
1713
|
+
}
|
|
1714
|
+
return clampTaskRangeForIncomingFS(
|
|
1715
|
+
task,
|
|
1716
|
+
range.start,
|
|
1717
|
+
range.end,
|
|
1718
|
+
allTasks,
|
|
1719
|
+
businessDays,
|
|
1720
|
+
weekendPredicate
|
|
1721
|
+
);
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1408
1724
|
// src/utils/hierarchyOrder.ts
|
|
1409
1725
|
init_dateUtils();
|
|
1410
1726
|
function flattenHierarchy(tasks) {
|
|
@@ -7219,6 +7535,7 @@ var nameContains = (substring, caseSensitive = false) => (task) => {
|
|
|
7219
7535
|
calculateWeekGridLines,
|
|
7220
7536
|
calculateWeekendBlocks,
|
|
7221
7537
|
cascadeByLinks,
|
|
7538
|
+
clampDateRangeForIncomingFS,
|
|
7222
7539
|
clampTaskRangeForIncomingFS,
|
|
7223
7540
|
computeLagFromDates,
|
|
7224
7541
|
computeParentDates,
|
|
@@ -7258,6 +7575,7 @@ var nameContains = (substring, caseSensitive = false) => (task) => {
|
|
|
7258
7575
|
isToday,
|
|
7259
7576
|
isWeekend,
|
|
7260
7577
|
moveTaskRange,
|
|
7578
|
+
moveTaskWithCascade,
|
|
7261
7579
|
nameContains,
|
|
7262
7580
|
normalizeDependencyLag,
|
|
7263
7581
|
normalizeHierarchyTasks,
|
|
@@ -7270,8 +7588,12 @@ var nameContains = (substring, caseSensitive = false) => (task) => {
|
|
|
7270
7588
|
pixelsToDate,
|
|
7271
7589
|
progressInRange,
|
|
7272
7590
|
recalculateIncomingLags,
|
|
7591
|
+
recalculateProjectSchedule,
|
|
7592
|
+
recalculateTaskFromDependencies,
|
|
7273
7593
|
reflowTasksOnModeSwitch,
|
|
7274
7594
|
removeDependenciesBetweenTasks,
|
|
7595
|
+
resizeTaskWithCascade,
|
|
7596
|
+
resolveDateRangeFromPixels,
|
|
7275
7597
|
shiftBusinessDayOffset,
|
|
7276
7598
|
subtractBusinessDays,
|
|
7277
7599
|
universalCascade,
|