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.
@@ -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
- * Convert pixel coordinates to a date range, applying business-day alignment
301
- * when businessDays mode is active. This is the pure scheduling core of
302
- * drag-to-date conversion.
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
- 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): {
307
- start: Date;
308
- end: Date;
309
- };
352
+
310
353
  /**
311
- * Clamp a proposed date range based on incoming FS dependencies.
312
- * For resize-right mode, returns range unchanged (only start is clamped).
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 clampDateRangeForIncomingFS(task: Task, range: {
317
- start: Date;
318
- end: Date;
319
- }, allTasks: Task[], mode: 'move' | 'resize-left' | 'resize-right', businessDays?: boolean, weekendPredicate?: (date: Date) => boolean): {
320
- start: Date;
321
- end: Date;
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
- export { isTaskParent as A, moveTaskRange as B, normalizeDependencyLag as C, DAY_MS as D, normalizeUTCDate as E, parseDateOnly as F, type GanttDateRange as G, recalculateIncomingLags as H, reflowTasksOnModeSwitch as I, removeDependenciesBetweenTasks as J, shiftBusinessDayOffset as K, type LinkType as L, type MonthSpan as M, universalCascade as N, validateDependencies as O, type TaskDependency as P, addBusinessDays as Q, clampDateRangeForIncomingFS as R, getBusinessDaysCount as S, type Task as T, resolveDateRangeFromPixels as U, type ValidationResult as V, type WeekendBlock as W, subtractBusinessDays as X, type DependencyError as a, 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, clampTaskRangeForIncomingFS as l, computeLagFromDates as m, computeParentDates as n, computeParentProgress as o, detectCycles as p, findParentId as q, getAllDependencyEdges as r, getAllDescendants as s, getBusinessDayOffset as t, getChildren as u, getDependencyLag as v, getSuccessorChain as w, getTaskDuration as x, getTransitiveCascadeChain as y, isAncestorTask as z };
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-CliEEiHA.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 clampTaskRangeForIncomingFS, m as computeLagFromDates, n as computeParentDates, o as computeParentProgress, p as detectCycles, q as findParentId, r as getAllDependencyEdges, s as getAllDescendants, t as getBusinessDayOffset, u as getChildren, v as getDependencyLag, w as getSuccessorChain, x as getTaskDuration, y as getTransitiveCascadeChain, z as isAncestorTask, A as isTaskParent, B as moveTaskRange, C as normalizeDependencyLag, E as normalizeUTCDate, F as parseDateOnly, H as recalculateIncomingLags, I as reflowTasksOnModeSwitch, J as removeDependenciesBetweenTasks, K as shiftBusinessDayOffset, N as universalCascade, O as validateDependencies } from './index-CliEEiHA.mjs';
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-CliEEiHA.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 clampTaskRangeForIncomingFS, m as computeLagFromDates, n as computeParentDates, o as computeParentProgress, p as detectCycles, q as findParentId, r as getAllDependencyEdges, s as getAllDescendants, t as getBusinessDayOffset, u as getChildren, v as getDependencyLag, w as getSuccessorChain, x as getTaskDuration, y as getTransitiveCascadeChain, z as isAncestorTask, A as isTaskParent, B as moveTaskRange, C as normalizeDependencyLag, E as normalizeUTCDate, F as parseDateOnly, H as recalculateIncomingLags, I as reflowTasksOnModeSwitch, J as removeDependenciesBetweenTasks, K as shiftBusinessDayOffset, N as universalCascade, O as validateDependencies } from './index-CliEEiHA.js';
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,