gantt-lib 0.0.4 → 0.0.6

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.js CHANGED
@@ -37,27 +37,35 @@ __export(index_exports, {
37
37
  TaskRow: () => TaskRow_default,
38
38
  TimeScaleHeader: () => TimeScaleHeader_default,
39
39
  TodayIndicator: () => TodayIndicator_default,
40
+ buildAdjacencyList: () => buildAdjacencyList,
41
+ calculateBezierPath: () => calculateBezierPath,
40
42
  calculateGridLines: () => calculateGridLines,
41
43
  calculateGridWidth: () => calculateGridWidth,
44
+ calculateOrthogonalPath: () => calculateOrthogonalPath,
45
+ calculateSuccessorDate: () => calculateSuccessorDate,
42
46
  calculateTaskBar: () => calculateTaskBar,
43
47
  calculateWeekendBlocks: () => calculateWeekendBlocks,
48
+ detectCycles: () => detectCycles,
44
49
  detectEdgeZone: () => detectEdgeZone,
45
50
  formatDateLabel: () => formatDateLabel,
51
+ getAllDependencyEdges: () => getAllDependencyEdges,
46
52
  getCursorForPosition: () => getCursorForPosition,
47
53
  getDayOffset: () => getDayOffset,
48
54
  getMonthDays: () => getMonthDays,
49
55
  getMonthSpans: () => getMonthSpans,
50
56
  getMultiMonthDays: () => getMultiMonthDays,
57
+ getSuccessorChain: () => getSuccessorChain,
51
58
  isToday: () => isToday,
52
59
  isWeekend: () => isWeekend,
53
60
  parseUTCDate: () => parseUTCDate,
54
61
  pixelsToDate: () => pixelsToDate,
55
- useTaskDrag: () => useTaskDrag
62
+ useTaskDrag: () => useTaskDrag,
63
+ validateDependencies: () => validateDependencies
56
64
  });
57
65
  module.exports = __toCommonJS(index_exports);
58
66
 
59
67
  // src/components/GanttChart/GanttChart.tsx
60
- var import_react6 = require("react");
68
+ var import_react7 = require("react");
61
69
 
62
70
  // src/utils/dateUtils.ts
63
71
  var parseUTCDate = (date) => {
@@ -198,6 +206,148 @@ var formatDateLabel = (date) => {
198
206
  return `${day}.${month}`;
199
207
  };
200
208
 
209
+ // src/utils/dependencyUtils.ts
210
+ function buildAdjacencyList(tasks) {
211
+ const taskMap = new Map(tasks.map((t) => [t.id, t]));
212
+ const graph = /* @__PURE__ */ new Map();
213
+ for (const task of tasks) {
214
+ const successors = [];
215
+ for (const otherTask of tasks) {
216
+ if (otherTask.dependencies) {
217
+ for (const dep of otherTask.dependencies) {
218
+ if (dep.taskId === task.id) {
219
+ successors.push(otherTask.id);
220
+ break;
221
+ }
222
+ }
223
+ }
224
+ }
225
+ graph.set(task.id, successors);
226
+ }
227
+ return graph;
228
+ }
229
+ function detectCycles(tasks) {
230
+ const graph = buildAdjacencyList(tasks);
231
+ const visiting = /* @__PURE__ */ new Set();
232
+ const visited = /* @__PURE__ */ new Set();
233
+ const path = [];
234
+ function dfs(taskId) {
235
+ if (visiting.has(taskId)) {
236
+ return true;
237
+ }
238
+ if (visited.has(taskId)) {
239
+ return false;
240
+ }
241
+ visiting.add(taskId);
242
+ path.push(taskId);
243
+ const successors = graph.get(taskId) || [];
244
+ for (const successor of successors) {
245
+ if (dfs(successor)) {
246
+ return true;
247
+ }
248
+ }
249
+ visiting.delete(taskId);
250
+ path.pop();
251
+ visited.add(taskId);
252
+ return false;
253
+ }
254
+ for (const task of tasks) {
255
+ if (dfs(task.id)) {
256
+ return { hasCycle: true, cyclePath: [...path] };
257
+ }
258
+ }
259
+ return { hasCycle: false };
260
+ }
261
+ function calculateSuccessorDate(predecessorStart, predecessorEnd, linkType, lag = 0) {
262
+ const baseDate = linkType.startsWith("F") ? predecessorEnd : predecessorStart;
263
+ const lagMs = lag * 24 * 60 * 60 * 1e3;
264
+ const resultDate = new Date(baseDate.getTime() + lagMs);
265
+ return resultDate;
266
+ }
267
+ function validateDependencies(tasks) {
268
+ const errors = [];
269
+ const taskIds = new Set(tasks.map((t) => t.id));
270
+ for (const task of tasks) {
271
+ if (task.dependencies) {
272
+ for (const dep of task.dependencies) {
273
+ if (!taskIds.has(dep.taskId)) {
274
+ errors.push({
275
+ type: "missing-task",
276
+ taskId: task.id,
277
+ message: `Dependency references non-existent task: ${dep.taskId}`,
278
+ relatedTaskIds: [dep.taskId]
279
+ });
280
+ }
281
+ }
282
+ }
283
+ }
284
+ const cycleResult = detectCycles(tasks);
285
+ if (cycleResult.hasCycle && cycleResult.cyclePath) {
286
+ errors.push({
287
+ type: "cycle",
288
+ taskId: cycleResult.cyclePath[0],
289
+ message: "Circular dependency detected",
290
+ relatedTaskIds: cycleResult.cyclePath
291
+ });
292
+ }
293
+ return {
294
+ isValid: errors.length === 0,
295
+ errors
296
+ };
297
+ }
298
+ function getSuccessorChain(draggedTaskId, allTasks) {
299
+ const successorMap = /* @__PURE__ */ new Map();
300
+ for (const task of allTasks) {
301
+ successorMap.set(task.id, []);
302
+ }
303
+ for (const task of allTasks) {
304
+ if (!task.dependencies) continue;
305
+ for (const dep of task.dependencies) {
306
+ if (dep.type === "FS") {
307
+ const list = successorMap.get(dep.taskId) ?? [];
308
+ list.push(task.id);
309
+ successorMap.set(dep.taskId, list);
310
+ }
311
+ }
312
+ }
313
+ const taskById = new Map(allTasks.map((t) => [t.id, t]));
314
+ const visited = /* @__PURE__ */ new Set();
315
+ const queue = [draggedTaskId];
316
+ const chain = [];
317
+ visited.add(draggedTaskId);
318
+ while (queue.length > 0) {
319
+ const current = queue.shift();
320
+ const successors = successorMap.get(current) ?? [];
321
+ for (const sid of successors) {
322
+ if (!visited.has(sid)) {
323
+ visited.add(sid);
324
+ const t = taskById.get(sid);
325
+ if (t) {
326
+ chain.push(t);
327
+ queue.push(sid);
328
+ }
329
+ }
330
+ }
331
+ }
332
+ return chain;
333
+ }
334
+ function getAllDependencyEdges(tasks) {
335
+ const edges = [];
336
+ for (const task of tasks) {
337
+ if (task.dependencies) {
338
+ for (const dep of task.dependencies) {
339
+ edges.push({
340
+ predecessorId: dep.taskId,
341
+ successorId: task.id,
342
+ type: dep.type,
343
+ lag: dep.lag ?? 0
344
+ });
345
+ }
346
+ }
347
+ }
348
+ return edges;
349
+ }
350
+
201
351
  // src/components/TimeScaleHeader/TimeScaleHeader.tsx
202
352
  var import_react = require("react");
203
353
  var import_date_fns = require("date-fns");
@@ -361,6 +511,43 @@ var calculateWeekendBlocks = (dateRange, dayWidth) => {
361
511
  }
362
512
  return blocks;
363
513
  };
514
+ var calculateBezierPath = (from, to) => {
515
+ const verticalDistance = Math.abs(to.y - from.y);
516
+ const cpOffset = Math.max(verticalDistance * 0.5, 20);
517
+ if (from.y === to.y) {
518
+ const arcHeight = 20;
519
+ const midX = (from.x + to.x) / 2;
520
+ return `M ${from.x} ${from.y} Q ${midX} ${from.y - arcHeight} ${to.x} ${to.y}`;
521
+ }
522
+ const cp1x = from.x;
523
+ const cp1y = from.y + (to.y > from.y ? cpOffset : -cpOffset);
524
+ const cp2x = to.x;
525
+ const cp2y = to.y - (to.y > from.y ? cpOffset : -cpOffset);
526
+ return `M ${Math.round(from.x)} ${Math.round(from.y)} C ${Math.round(cp1x)} ${Math.round(cp1y)}, ${Math.round(cp2x)} ${Math.round(cp2y)}, ${Math.round(to.x)} ${Math.round(to.y)}`;
527
+ };
528
+ var calculateOrthogonalPath = (from, to) => {
529
+ const fx = Math.round(from.x);
530
+ const fy = Math.round(from.y);
531
+ const tx = Math.round(to.x);
532
+ const ty = Math.round(to.y);
533
+ if (fy === ty) {
534
+ return `M ${fx} ${fy} H ${tx}`;
535
+ }
536
+ const C = 2;
537
+ const goingDown = ty > fy;
538
+ const goingRight = tx >= fx;
539
+ const dirY = goingDown ? 1 : -1;
540
+ const dirX = goingRight ? 1 : -1;
541
+ if (Math.abs(ty - fy) >= C && Math.abs(tx - fx) >= C) {
542
+ return [
543
+ `M ${fx} ${fy}`,
544
+ `H ${tx - dirX * C}`,
545
+ `L ${tx} ${fy + dirY * C}`,
546
+ `V ${ty}`
547
+ ].join(" ");
548
+ }
549
+ return `M ${fx} ${fy} H ${tx} V ${ty}`;
550
+ };
364
551
 
365
552
  // src/hooks/useTaskDrag.ts
366
553
  var import_react2 = require("react");
@@ -372,8 +559,8 @@ function completeDrag() {
372
559
  globalRafId = null;
373
560
  }
374
561
  if (globalActiveDrag) {
562
+ globalActiveDrag.onCascadeProgress?.(/* @__PURE__ */ new Map());
375
563
  const { onComplete, currentLeft, currentWidth } = globalActiveDrag;
376
- const drag = globalActiveDrag;
377
564
  globalActiveDrag = null;
378
565
  onComplete(currentLeft, currentWidth);
379
566
  }
@@ -392,6 +579,27 @@ function cancelDrag() {
392
579
  function snapToGrid(pixels, dayWidth) {
393
580
  return Math.round(pixels / dayWidth) * dayWidth;
394
581
  }
582
+ function recalculateIncomingLags(task, newStartDate, allTasks) {
583
+ if (!task.dependencies) return [];
584
+ const taskById = new Map(allTasks.map((t) => [t.id, t]));
585
+ return task.dependencies.map((dep) => {
586
+ if (dep.type !== "FS") return dep;
587
+ const predecessor = taskById.get(dep.taskId);
588
+ if (!predecessor) return dep;
589
+ const predEnd = new Date(predecessor.endDate);
590
+ const lagMs = Date.UTC(
591
+ newStartDate.getUTCFullYear(),
592
+ newStartDate.getUTCMonth(),
593
+ newStartDate.getUTCDate()
594
+ ) - Date.UTC(
595
+ predEnd.getUTCFullYear(),
596
+ predEnd.getUTCMonth(),
597
+ predEnd.getUTCDate()
598
+ );
599
+ const lagDays = Math.round(lagMs / (24 * 60 * 60 * 1e3));
600
+ return { ...dep, lag: lagDays };
601
+ });
602
+ }
395
603
  function handleGlobalMouseMove(e) {
396
604
  if (!globalActiveDrag || globalRafId !== null) {
397
605
  return;
@@ -401,7 +609,7 @@ function handleGlobalMouseMove(e) {
401
609
  globalRafId = null;
402
610
  return;
403
611
  }
404
- const { startX, initialLeft, initialWidth, mode, dayWidth, onProgress } = globalActiveDrag;
612
+ const { startX, initialLeft, initialWidth, mode, dayWidth, onProgress, allTasks } = globalActiveDrag;
405
613
  const deltaX = e.clientX - startX;
406
614
  let newLeft = initialLeft;
407
615
  let newWidth = initialWidth;
@@ -420,6 +628,50 @@ function handleGlobalMouseMove(e) {
420
628
  newWidth = Math.max(dayWidth, snappedWidth);
421
629
  break;
422
630
  }
631
+ if (mode === "move" && allTasks.length > 0 && !globalActiveDrag.disableConstraints) {
632
+ const currentTask = allTasks.find((t) => t.id === globalActiveDrag?.taskId);
633
+ if (currentTask && currentTask.dependencies && currentTask.dependencies.length > 0) {
634
+ let minAllowedLeft = 0;
635
+ for (const dep of currentTask.dependencies) {
636
+ if (dep.type !== "FS") continue;
637
+ const predecessor = globalActiveDrag.allTasks.find((t) => t.id === dep.taskId);
638
+ if (!predecessor) continue;
639
+ const predStart = new Date(predecessor.startDate);
640
+ const predStartOffset = Math.round(
641
+ (Date.UTC(predStart.getUTCFullYear(), predStart.getUTCMonth(), predStart.getUTCDate()) - Date.UTC(
642
+ globalActiveDrag.monthStart.getUTCFullYear(),
643
+ globalActiveDrag.monthStart.getUTCMonth(),
644
+ globalActiveDrag.monthStart.getUTCDate()
645
+ )) / (24 * 60 * 60 * 1e3)
646
+ );
647
+ const predStartLeft = Math.round(predStartOffset * globalActiveDrag.dayWidth);
648
+ minAllowedLeft = Math.max(minAllowedLeft, predStartLeft);
649
+ }
650
+ newLeft = Math.max(minAllowedLeft, newLeft);
651
+ }
652
+ }
653
+ if ((mode === "move" || mode === "resize-right") && !globalActiveDrag.disableConstraints && globalActiveDrag.cascadeChain.length > 0 && globalActiveDrag.onCascadeProgress) {
654
+ const deltaDays = mode === "resize-right" ? Math.round((newWidth - globalActiveDrag.initialWidth) / globalActiveDrag.dayWidth) : Math.round((newLeft - globalActiveDrag.initialLeft) / globalActiveDrag.dayWidth);
655
+ const overrides = /* @__PURE__ */ new Map();
656
+ for (const chainTask of globalActiveDrag.cascadeChain) {
657
+ const chainStart = new Date(chainTask.startDate);
658
+ const chainEnd = new Date(chainTask.endDate);
659
+ const chainStartOffset = Math.round(
660
+ (Date.UTC(chainStart.getUTCFullYear(), chainStart.getUTCMonth(), chainStart.getUTCDate()) - Date.UTC(
661
+ globalActiveDrag.monthStart.getUTCFullYear(),
662
+ globalActiveDrag.monthStart.getUTCMonth(),
663
+ globalActiveDrag.monthStart.getUTCDate()
664
+ )) / (24 * 60 * 60 * 1e3)
665
+ );
666
+ const chainDuration = Math.round(
667
+ (Date.UTC(chainEnd.getUTCFullYear(), chainEnd.getUTCMonth(), chainEnd.getUTCDate()) - Date.UTC(chainStart.getUTCFullYear(), chainStart.getUTCMonth(), chainStart.getUTCDate())) / (24 * 60 * 60 * 1e3)
668
+ );
669
+ const chainLeft = Math.round((chainStartOffset + deltaDays) * globalActiveDrag.dayWidth);
670
+ const chainWidth = Math.round((chainDuration + 1) * globalActiveDrag.dayWidth);
671
+ overrides.set(chainTask.id, { left: chainLeft, width: chainWidth });
672
+ }
673
+ globalActiveDrag.onCascadeProgress(overrides);
674
+ }
423
675
  globalActiveDrag.currentLeft = newLeft;
424
676
  globalActiveDrag.currentWidth = newWidth;
425
677
  onProgress(newLeft, newWidth);
@@ -448,7 +700,13 @@ var useTaskDrag = (options) => {
448
700
  dayWidth,
449
701
  onDragEnd,
450
702
  onDragStateChange,
451
- edgeZoneWidth = 12
703
+ edgeZoneWidth = 12,
704
+ allTasks = [],
705
+ rowIndex,
706
+ enableAutoSchedule = false,
707
+ disableConstraints = false,
708
+ onCascadeProgress,
709
+ onCascade
452
710
  } = options;
453
711
  const isOwnerRef = (0, import_react2.useRef)(false);
454
712
  const [isDragging, setIsDragging] = (0, import_react2.useState)(false);
@@ -518,14 +776,60 @@ var useTaskDrag = (options) => {
518
776
  width: finalWidth
519
777
  });
520
778
  }
521
- if (onDragEnd && wasOwner) {
522
- onDragEnd({
523
- id: taskId,
524
- startDate: newStartDate,
525
- endDate: newEndDate
526
- });
779
+ if (wasOwner) {
780
+ if (!disableConstraints && onCascade && allTasks.length > 0) {
781
+ const chain = getSuccessorChain(taskId, allTasks);
782
+ if (chain.length > 0) {
783
+ const origEndMs = Date.UTC(
784
+ initialEndDate.getUTCFullYear(),
785
+ initialEndDate.getUTCMonth(),
786
+ initialEndDate.getUTCDate()
787
+ );
788
+ const newEndMs = Date.UTC(
789
+ newEndDate.getUTCFullYear(),
790
+ newEndDate.getUTCMonth(),
791
+ newEndDate.getUTCDate()
792
+ );
793
+ const deltaDays = Math.round((newEndMs - origEndMs) / (24 * 60 * 60 * 1e3));
794
+ const draggedTaskData = allTasks.find((t) => t.id === taskId);
795
+ const cascadedTasks = [
796
+ {
797
+ ...draggedTaskData ?? { id: taskId, name: "", startDate: "", endDate: "" },
798
+ startDate: newStartDate.toISOString(),
799
+ endDate: newEndDate.toISOString(),
800
+ ...draggedTaskData?.dependencies && {
801
+ dependencies: recalculateIncomingLags(draggedTaskData, newStartDate, allTasks)
802
+ }
803
+ },
804
+ ...chain.map((chainTask) => {
805
+ const origStart = new Date(chainTask.startDate);
806
+ const origEnd = new Date(chainTask.endDate);
807
+ const newStart = new Date(Date.UTC(
808
+ origStart.getUTCFullYear(),
809
+ origStart.getUTCMonth(),
810
+ origStart.getUTCDate() + deltaDays
811
+ ));
812
+ const newEnd = new Date(Date.UTC(
813
+ origEnd.getUTCFullYear(),
814
+ origEnd.getUTCMonth(),
815
+ origEnd.getUTCDate() + deltaDays
816
+ ));
817
+ return { ...chainTask, startDate: newStart.toISOString(), endDate: newEnd.toISOString() };
818
+ })
819
+ ];
820
+ onCascade(cascadedTasks);
821
+ return;
822
+ }
823
+ }
824
+ if (allTasks.length > 0 && onDragEnd) {
825
+ const currentTaskData = allTasks.find((t) => t.id === taskId);
826
+ const updatedDependencies = currentTaskData?.dependencies ? recalculateIncomingLags(currentTaskData, newStartDate, allTasks) : void 0;
827
+ onDragEnd({ id: taskId, startDate: newStartDate, endDate: newEndDate, updatedDependencies });
828
+ } else if (onDragEnd) {
829
+ onDragEnd({ id: taskId, startDate: newStartDate, endDate: newEndDate });
830
+ }
527
831
  }
528
- }, [dayWidth, monthStart, onDragEnd, onDragStateChange, taskId]);
832
+ }, [dayWidth, monthStart, onDragEnd, onDragStateChange, taskId, disableConstraints, onCascade, allTasks, initialStartDate, initialEndDate]);
529
833
  const handleCancel = (0, import_react2.useCallback)(() => {
530
834
  isOwnerRef.current = false;
531
835
  setIsDragging(false);
@@ -592,9 +896,13 @@ var useTaskDrag = (options) => {
592
896
  monthStart,
593
897
  onProgress: handleProgress,
594
898
  onComplete: handleComplete,
595
- onCancel: handleCancel
899
+ onCancel: handleCancel,
900
+ allTasks,
901
+ disableConstraints,
902
+ cascadeChain: (mode === "move" || mode === "resize-right") && !disableConstraints ? getSuccessorChain(taskId, allTasks) : [],
903
+ onCascadeProgress
596
904
  };
597
- }, [edgeZoneWidth, currentLeft, currentWidth, dayWidth, monthStart, taskId, onDragStateChange, handleProgress, handleComplete, handleCancel]);
905
+ }, [edgeZoneWidth, currentLeft, currentWidth, dayWidth, monthStart, taskId, onDragStateChange, handleProgress, handleComplete, handleCancel, allTasks, disableConstraints, onCascadeProgress, onCascade]);
598
906
  const getCursorStyle = (0, import_react2.useCallback)(() => {
599
907
  if (isDragging) {
600
908
  return "grabbing";
@@ -619,10 +927,10 @@ var useTaskDrag = (options) => {
619
927
  // src/components/TaskRow/TaskRow.tsx
620
928
  var import_jsx_runtime2 = require("react/jsx-runtime");
621
929
  var arePropsEqual = (prevProps, nextProps) => {
622
- return prevProps.task.id === nextProps.task.id && prevProps.task.name === nextProps.task.name && prevProps.task.startDate === nextProps.task.startDate && prevProps.task.endDate === nextProps.task.endDate && prevProps.task.color === nextProps.task.color && prevProps.monthStart.getTime() === nextProps.monthStart.getTime() && prevProps.dayWidth === nextProps.dayWidth && prevProps.rowHeight === nextProps.rowHeight;
930
+ return prevProps.task.id === nextProps.task.id && prevProps.task.name === nextProps.task.name && prevProps.task.startDate === nextProps.task.startDate && prevProps.task.endDate === nextProps.task.endDate && prevProps.task.color === nextProps.task.color && prevProps.task.progress === nextProps.task.progress && prevProps.task.accepted === nextProps.task.accepted && prevProps.monthStart.getTime() === nextProps.monthStart.getTime() && prevProps.dayWidth === nextProps.dayWidth && prevProps.rowHeight === nextProps.rowHeight && prevProps.overridePosition?.left === nextProps.overridePosition?.left && prevProps.overridePosition?.width === nextProps.overridePosition?.width && prevProps.allTasks === nextProps.allTasks && prevProps.disableConstraints === nextProps.disableConstraints;
623
931
  };
624
932
  var TaskRow = import_react3.default.memo(
625
- ({ task, monthStart, dayWidth, rowHeight, onChange, onDragStateChange }) => {
933
+ ({ task, monthStart, dayWidth, rowHeight, onChange, onDragStateChange, rowIndex, allTasks, enableAutoSchedule, disableConstraints, overridePosition, onCascadeProgress, onCascade }) => {
626
934
  const taskStartDate = (0, import_react3.useMemo)(() => parseUTCDate(task.startDate), [task.startDate]);
627
935
  const taskEndDate = (0, import_react3.useMemo)(() => parseUTCDate(task.endDate), [task.endDate]);
628
936
  const { left, width } = (0, import_react3.useMemo)(
@@ -630,11 +938,22 @@ var TaskRow = import_react3.default.memo(
630
938
  [taskStartDate, taskEndDate, monthStart, dayWidth]
631
939
  );
632
940
  const barColor = task.color || "var(--gantt-task-bar-default-color)";
941
+ const progressWidth = (0, import_react3.useMemo)(() => {
942
+ if (task.progress === void 0 || task.progress <= 0) return 0;
943
+ return Math.min(100, Math.max(0, Math.round(task.progress)));
944
+ }, [task.progress]);
945
+ const progressColor = (0, import_react3.useMemo)(() => {
946
+ if (progressWidth === 100) {
947
+ return task.accepted ? "var(--gantt-progress-accepted, #22c55e)" : "var(--gantt-progress-completed, #fbbf24)";
948
+ }
949
+ return task.color ? `color-mix(in srgb, ${task.color} 40%, black)` : "var(--gantt-progress-color, rgba(0, 0, 0, 0.2))";
950
+ }, [progressWidth, task.accepted, task.color]);
633
951
  const handleDragEnd = (result) => {
634
952
  const updatedTask = {
635
953
  ...task,
636
954
  startDate: result.startDate.toISOString(),
637
- endDate: result.endDate.toISOString()
955
+ endDate: result.endDate.toISOString(),
956
+ ...result.updatedDependencies !== void 0 && { dependencies: result.updatedDependencies }
638
957
  };
639
958
  onChange?.(updatedTask);
640
959
  };
@@ -652,10 +971,16 @@ var TaskRow = import_react3.default.memo(
652
971
  dayWidth,
653
972
  onDragEnd: handleDragEnd,
654
973
  onDragStateChange,
655
- edgeZoneWidth: 20
974
+ edgeZoneWidth: 20,
975
+ allTasks,
976
+ rowIndex,
977
+ enableAutoSchedule,
978
+ disableConstraints,
979
+ onCascadeProgress,
980
+ onCascade
656
981
  });
657
- const displayLeft = isDragging ? currentLeft : left;
658
- const displayWidth = isDragging ? currentWidth : width;
982
+ const displayLeft = overridePosition?.left ?? (isDragging ? currentLeft : left);
983
+ const displayWidth = overridePosition?.width ?? (isDragging ? currentWidth : width);
659
984
  const currentStartDate = isDragging ? pixelsToDate(displayLeft, monthStart, dayWidth) : taskStartDate;
660
985
  const currentEndDate = isDragging ? pixelsToDate(displayLeft + displayWidth - dayWidth, monthStart, dayWidth) : taskEndDate;
661
986
  const startDateLabel = formatDateLabel(currentStartDate);
@@ -663,16 +988,6 @@ var TaskRow = import_react3.default.memo(
663
988
  const durationDays = Math.round(
664
989
  (currentEndDate.getTime() - currentStartDate.getTime()) / (1e3 * 60 * 60 * 24)
665
990
  ) + 1;
666
- const [isNameOverflow, setIsNameOverflow] = (0, import_react3.useState)(false);
667
- const taskNameRef = (0, import_react3.useRef)(null);
668
- (0, import_react3.useEffect)(() => {
669
- const nameEl = taskNameRef.current;
670
- if (nameEl) {
671
- const reservedWidth = 120;
672
- const availableWidth = displayWidth - reservedWidth;
673
- setIsNameOverflow(nameEl.scrollWidth > availableWidth);
674
- }
675
- }, [displayWidth, task.name]);
676
991
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
677
992
  "div",
678
993
  {
@@ -694,22 +1009,21 @@ var TaskRow = import_react3.default.memo(
694
1009
  },
695
1010
  onMouseDown: dragHandleProps.onMouseDown,
696
1011
  children: [
1012
+ progressWidth > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1013
+ "div",
1014
+ {
1015
+ className: "gantt-tr-progressBar",
1016
+ style: {
1017
+ width: `${progressWidth}%`,
1018
+ backgroundColor: progressColor
1019
+ }
1020
+ }
1021
+ ),
697
1022
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "gantt-tr-resizeHandle gantt-tr-resizeHandleLeft" }),
698
1023
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "gantt-tr-taskDuration", children: [
699
1024
  durationDays,
700
1025
  " \u0434"
701
1026
  ] }),
702
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
703
- "span",
704
- {
705
- ref: taskNameRef,
706
- className: `gantt-tr-taskName ${isNameOverflow ? "gantt-tr-taskNameHidden" : ""}`,
707
- children: [
708
- "\u2014 ",
709
- task.name
710
- ]
711
- }
712
- ),
713
1027
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "gantt-tr-resizeHandle gantt-tr-resizeHandleRight" })
714
1028
  ]
715
1029
  }
@@ -735,7 +1049,7 @@ var TaskRow = import_react3.default.memo(
735
1049
  style: {
736
1050
  left: `${displayLeft + displayWidth}px`
737
1051
  },
738
- children: isNameOverflow && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-externalTaskName", children: task.name })
1052
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-externalTaskName", children: task.name })
739
1053
  }
740
1054
  )
741
1055
  ] })
@@ -881,55 +1195,212 @@ var DragGuideLines = ({
881
1195
  };
882
1196
  var DragGuideLines_default = DragGuideLines;
883
1197
 
884
- // src/components/GanttChart/GanttChart.tsx
1198
+ // src/components/DependencyLines/DependencyLines.tsx
1199
+ var import_react6 = __toESM(require("react"));
885
1200
  var import_jsx_runtime6 = require("react/jsx-runtime");
1201
+ var DependencyLines = import_react6.default.memo(({
1202
+ tasks,
1203
+ monthStart,
1204
+ dayWidth,
1205
+ rowHeight,
1206
+ gridWidth,
1207
+ dragOverrides
1208
+ }) => {
1209
+ const { taskPositions, taskIndices } = (0, import_react6.useMemo)(() => {
1210
+ const positions = /* @__PURE__ */ new Map();
1211
+ const indices = /* @__PURE__ */ new Map();
1212
+ tasks.forEach((task, index) => {
1213
+ const startDate = new Date(task.startDate);
1214
+ const endDate = new Date(task.endDate);
1215
+ const computed = calculateTaskBar(startDate, endDate, monthStart, dayWidth);
1216
+ const override = dragOverrides?.get(task.id);
1217
+ const resolvedLeft = override?.left ?? computed.left;
1218
+ const resolvedWidth = override?.width ?? computed.width;
1219
+ indices.set(task.id, index);
1220
+ positions.set(task.id, {
1221
+ left: resolvedLeft + 10,
1222
+ right: resolvedLeft + resolvedWidth,
1223
+ rowTop: index * rowHeight
1224
+ });
1225
+ });
1226
+ return { taskPositions: positions, taskIndices: indices };
1227
+ }, [tasks, monthStart, dayWidth, rowHeight, dragOverrides]);
1228
+ const cycleInfo = (0, import_react6.useMemo)(() => {
1229
+ const result = detectCycles(tasks);
1230
+ const cycleTaskIds = new Set(result.cyclePath || []);
1231
+ return cycleTaskIds;
1232
+ }, [tasks]);
1233
+ const lines = (0, import_react6.useMemo)(() => {
1234
+ const edges = getAllDependencyEdges(tasks);
1235
+ const lines2 = [];
1236
+ for (const edge of edges) {
1237
+ const predecessor = taskPositions.get(edge.predecessorId);
1238
+ const successor = taskPositions.get(edge.successorId);
1239
+ const predecessorIndex = taskIndices.get(edge.predecessorId);
1240
+ const successorIndex = taskIndices.get(edge.successorId);
1241
+ if (!predecessor || !successor || predecessorIndex === void 0 || successorIndex === void 0) {
1242
+ continue;
1243
+ }
1244
+ const reverseOrder = predecessorIndex > successorIndex;
1245
+ let fromY;
1246
+ let toY;
1247
+ if (reverseOrder) {
1248
+ fromY = predecessor.rowTop + 10;
1249
+ toY = successor.rowTop + rowHeight - 6;
1250
+ } else {
1251
+ fromY = predecessor.rowTop + rowHeight - 10;
1252
+ toY = successor.rowTop + 6;
1253
+ }
1254
+ const from = { x: predecessor.right, y: fromY };
1255
+ const to = { x: successor.left, y: toY };
1256
+ const path = calculateOrthogonalPath(from, to);
1257
+ const hasCycle = cycleInfo.has(edge.predecessorId) || cycleInfo.has(edge.successorId);
1258
+ lines2.push({
1259
+ id: `${edge.predecessorId}-${edge.successorId}`,
1260
+ path,
1261
+ hasCycle
1262
+ });
1263
+ }
1264
+ return lines2;
1265
+ }, [tasks, taskPositions, taskIndices, cycleInfo]);
1266
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1267
+ "svg",
1268
+ {
1269
+ className: "gantt-dependencies-svg",
1270
+ width: gridWidth,
1271
+ height: tasks.length * rowHeight,
1272
+ xmlns: "http://www.w3.org/2000/svg",
1273
+ children: [
1274
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("defs", { children: [
1275
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1276
+ "marker",
1277
+ {
1278
+ id: "arrowhead",
1279
+ markerWidth: "8",
1280
+ markerHeight: "6",
1281
+ markerUnits: "userSpaceOnUse",
1282
+ refX: "7",
1283
+ refY: "3",
1284
+ orient: "auto",
1285
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1286
+ "polygon",
1287
+ {
1288
+ points: "0 0, 8 3, 0 6",
1289
+ fill: "var(--gantt-dependency-line-color, #666666)"
1290
+ }
1291
+ )
1292
+ }
1293
+ ),
1294
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1295
+ "marker",
1296
+ {
1297
+ id: "arrowhead-cycle",
1298
+ markerWidth: "8",
1299
+ markerHeight: "6",
1300
+ markerUnits: "userSpaceOnUse",
1301
+ refX: "7",
1302
+ refY: "3",
1303
+ orient: "auto",
1304
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1305
+ "polygon",
1306
+ {
1307
+ points: "0 0, 8 3, 0 6",
1308
+ fill: "var(--gantt-dependency-cycle-color, #ef4444)"
1309
+ }
1310
+ )
1311
+ }
1312
+ )
1313
+ ] }),
1314
+ lines.map(({ id, path, hasCycle }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1315
+ "path",
1316
+ {
1317
+ d: path,
1318
+ className: hasCycle ? "gantt-dependency-path gantt-dependency-cycle" : "gantt-dependency-path",
1319
+ markerEnd: hasCycle ? "url(#arrowhead-cycle)" : "url(#arrowhead)"
1320
+ },
1321
+ id
1322
+ ))
1323
+ ]
1324
+ }
1325
+ );
1326
+ });
1327
+ DependencyLines.displayName = "DependencyLines";
1328
+ var DependencyLines_default = DependencyLines;
1329
+
1330
+ // src/components/GanttChart/GanttChart.tsx
1331
+ var import_jsx_runtime7 = require("react/jsx-runtime");
886
1332
  var GanttChart = ({
887
1333
  tasks,
888
1334
  dayWidth = 40,
889
1335
  rowHeight = 40,
890
1336
  headerHeight = 40,
891
1337
  containerHeight = 600,
892
- onChange
1338
+ onChange,
1339
+ onValidateDependencies,
1340
+ enableAutoSchedule,
1341
+ disableConstraints,
1342
+ onCascade
893
1343
  }) => {
894
- const scrollContainerRef = (0, import_react6.useRef)(null);
895
- const dateRange = (0, import_react6.useMemo)(() => getMultiMonthDays(tasks), [tasks]);
896
- const gridWidth = (0, import_react6.useMemo)(
1344
+ const scrollContainerRef = (0, import_react7.useRef)(null);
1345
+ const dateRange = (0, import_react7.useMemo)(() => getMultiMonthDays(tasks), [tasks]);
1346
+ const [validationResult, setValidationResult] = (0, import_react7.useState)(null);
1347
+ const [cascadeOverrides, setCascadeOverrides] = (0, import_react7.useState)(/* @__PURE__ */ new Map());
1348
+ const gridWidth = (0, import_react7.useMemo)(
897
1349
  () => Math.round(dateRange.length * dayWidth),
898
1350
  [dateRange.length, dayWidth]
899
1351
  );
900
- const totalGridHeight = (0, import_react6.useMemo)(
1352
+ const totalGridHeight = (0, import_react7.useMemo)(
901
1353
  () => tasks.length * rowHeight,
902
1354
  [tasks.length, rowHeight]
903
1355
  );
904
- const monthStart = (0, import_react6.useMemo)(() => {
1356
+ const monthStart = (0, import_react7.useMemo)(() => {
905
1357
  if (dateRange.length === 0) {
906
1358
  return new Date(Date.UTC((/* @__PURE__ */ new Date()).getUTCFullYear(), (/* @__PURE__ */ new Date()).getUTCMonth(), 1));
907
1359
  }
908
1360
  const firstDay = dateRange[0];
909
1361
  return new Date(Date.UTC(firstDay.getUTCFullYear(), firstDay.getUTCMonth(), 1));
910
1362
  }, [dateRange]);
911
- const todayInRange = (0, import_react6.useMemo)(() => {
1363
+ const todayInRange = (0, import_react7.useMemo)(() => {
912
1364
  const now = /* @__PURE__ */ new Date();
913
1365
  const today = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
914
1366
  return dateRange.some((day) => day.getTime() === today.getTime());
915
1367
  }, [dateRange]);
916
- const [dragGuideLines, setDragGuideLines] = (0, import_react6.useState)(null);
917
- const handleTaskChange = (0, import_react6.useCallback)((updatedTask) => {
1368
+ const [dragGuideLines, setDragGuideLines] = (0, import_react7.useState)(null);
1369
+ const [draggedTaskOverride, setDraggedTaskOverride] = (0, import_react7.useState)(null);
1370
+ (0, import_react7.useEffect)(() => {
1371
+ const result = validateDependencies(tasks);
1372
+ setValidationResult(result);
1373
+ onValidateDependencies?.(result);
1374
+ }, [tasks, onValidateDependencies]);
1375
+ const handleTaskChange = (0, import_react7.useCallback)((updatedTask) => {
918
1376
  onChange?.(
919
1377
  (currentTasks) => currentTasks.map(
920
1378
  (t) => t.id === updatedTask.id ? updatedTask : t
921
1379
  )
922
1380
  );
923
1381
  }, [onChange]);
924
- const handleDragStateChange = (0, import_react6.useCallback)((state) => {
925
- if (state.isDragging) {
926
- setDragGuideLines(state);
927
- } else {
928
- setDragGuideLines(null);
1382
+ const dependencyOverrides = (0, import_react7.useMemo)(() => {
1383
+ const map = new Map(cascadeOverrides);
1384
+ if (draggedTaskOverride) {
1385
+ map.set(draggedTaskOverride.taskId, {
1386
+ left: draggedTaskOverride.left,
1387
+ width: draggedTaskOverride.width
1388
+ });
929
1389
  }
1390
+ return map;
1391
+ }, [cascadeOverrides, draggedTaskOverride]);
1392
+ const handleCascadeProgress = (0, import_react7.useCallback)((overrides) => {
1393
+ setCascadeOverrides(new Map(overrides));
930
1394
  }, []);
931
- const panStateRef = (0, import_react6.useRef)(null);
932
- const handlePanStart = (0, import_react6.useCallback)((e) => {
1395
+ const handleCascade = (0, import_react7.useCallback)((cascadedTasks) => {
1396
+ onChange?.((currentTasks) => {
1397
+ const cascadeMap = new Map(cascadedTasks.map((t) => [t.id, t]));
1398
+ return currentTasks.map((t) => cascadeMap.get(t.id) ?? t);
1399
+ });
1400
+ onCascade?.(cascadedTasks);
1401
+ }, [onChange, onCascade]);
1402
+ const panStateRef = (0, import_react7.useRef)(null);
1403
+ const handlePanStart = (0, import_react7.useCallback)((e) => {
933
1404
  if (e.button !== 0) return;
934
1405
  const target = e.target;
935
1406
  if (target.closest("[data-taskbar]")) return;
@@ -945,7 +1416,7 @@ var GanttChart = ({
945
1416
  container.style.cursor = "grabbing";
946
1417
  e.preventDefault();
947
1418
  }, []);
948
- (0, import_react6.useEffect)(() => {
1419
+ (0, import_react7.useEffect)(() => {
949
1420
  const handlePanMove = (e) => {
950
1421
  const pan = panStateRef.current;
951
1422
  if (!pan?.active) return;
@@ -967,7 +1438,7 @@ var GanttChart = ({
967
1438
  window.removeEventListener("mouseup", handlePanEnd);
968
1439
  };
969
1440
  }, []);
970
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "gantt-container", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1441
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "gantt-container", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
971
1442
  "div",
972
1443
  {
973
1444
  ref: scrollContainerRef,
@@ -975,7 +1446,7 @@ var GanttChart = ({
975
1446
  style: { height: `${containerHeight}px`, cursor: "grab" },
976
1447
  onMouseDown: handlePanStart,
977
1448
  children: [
978
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "gantt-stickyHeader", style: { width: `${gridWidth}px` }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1449
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "gantt-stickyHeader", style: { width: `${gridWidth}px` }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
979
1450
  TimeScaleHeader_default,
980
1451
  {
981
1452
  days: dateRange,
@@ -983,7 +1454,7 @@ var GanttChart = ({
983
1454
  headerHeight
984
1455
  }
985
1456
  ) }),
986
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1457
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
987
1458
  "div",
988
1459
  {
989
1460
  className: "gantt-taskArea",
@@ -992,7 +1463,7 @@ var GanttChart = ({
992
1463
  width: `${gridWidth}px`
993
1464
  },
994
1465
  children: [
995
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1466
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
996
1467
  GridBackground_default,
997
1468
  {
998
1469
  dateRange,
@@ -1000,8 +1471,19 @@ var GanttChart = ({
1000
1471
  totalHeight: totalGridHeight
1001
1472
  }
1002
1473
  ),
1003
- todayInRange && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TodayIndicator_default, { monthStart, dayWidth }),
1004
- dragGuideLines && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1474
+ todayInRange && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TodayIndicator_default, { monthStart, dayWidth }),
1475
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1476
+ DependencyLines_default,
1477
+ {
1478
+ tasks,
1479
+ monthStart,
1480
+ dayWidth,
1481
+ rowHeight,
1482
+ gridWidth,
1483
+ dragOverrides: dependencyOverrides
1484
+ }
1485
+ ),
1486
+ dragGuideLines && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1005
1487
  DragGuideLines_default,
1006
1488
  {
1007
1489
  isDragging: dragGuideLines.isDragging,
@@ -1011,7 +1493,7 @@ var GanttChart = ({
1011
1493
  totalHeight: totalGridHeight
1012
1494
  }
1013
1495
  ),
1014
- tasks.map((task) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1496
+ tasks.map((task, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1015
1497
  TaskRow_default,
1016
1498
  {
1017
1499
  task,
@@ -1019,7 +1501,22 @@ var GanttChart = ({
1019
1501
  dayWidth,
1020
1502
  rowHeight,
1021
1503
  onChange: handleTaskChange,
1022
- onDragStateChange: handleDragStateChange
1504
+ onDragStateChange: (state) => {
1505
+ if (state.isDragging) {
1506
+ setDragGuideLines(state);
1507
+ setDraggedTaskOverride({ taskId: task.id, left: state.left, width: state.width });
1508
+ } else {
1509
+ setDragGuideLines(null);
1510
+ setDraggedTaskOverride(null);
1511
+ }
1512
+ },
1513
+ rowIndex: index,
1514
+ allTasks: tasks,
1515
+ enableAutoSchedule: enableAutoSchedule ?? false,
1516
+ disableConstraints: disableConstraints ?? false,
1517
+ overridePosition: cascadeOverrides.get(task.id),
1518
+ onCascadeProgress: handleCascadeProgress,
1519
+ onCascade: handleCascade
1023
1520
  },
1024
1521
  task.id
1025
1522
  ))
@@ -1038,21 +1535,29 @@ var GanttChart = ({
1038
1535
  TaskRow,
1039
1536
  TimeScaleHeader,
1040
1537
  TodayIndicator,
1538
+ buildAdjacencyList,
1539
+ calculateBezierPath,
1041
1540
  calculateGridLines,
1042
1541
  calculateGridWidth,
1542
+ calculateOrthogonalPath,
1543
+ calculateSuccessorDate,
1043
1544
  calculateTaskBar,
1044
1545
  calculateWeekendBlocks,
1546
+ detectCycles,
1045
1547
  detectEdgeZone,
1046
1548
  formatDateLabel,
1549
+ getAllDependencyEdges,
1047
1550
  getCursorForPosition,
1048
1551
  getDayOffset,
1049
1552
  getMonthDays,
1050
1553
  getMonthSpans,
1051
1554
  getMultiMonthDays,
1555
+ getSuccessorChain,
1052
1556
  isToday,
1053
1557
  isWeekend,
1054
1558
  parseUTCDate,
1055
1559
  pixelsToDate,
1056
- useTaskDrag
1560
+ useTaskDrag,
1561
+ validateDependencies
1057
1562
  });
1058
1563
  //# sourceMappingURL=index.js.map