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.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/components/GanttChart/GanttChart.tsx
4
- import { useMemo as useMemo5, useCallback as useCallback2, useRef as useRef3, useState as useState3, useEffect as useEffect3 } from "react";
4
+ import { useMemo as useMemo6, useCallback as useCallback2, useRef as useRef2, useState as useState2, useEffect as useEffect2 } from "react";
5
5
 
6
6
  // src/utils/dateUtils.ts
7
7
  var parseUTCDate = (date) => {
@@ -142,6 +142,148 @@ var formatDateLabel = (date) => {
142
142
  return `${day}.${month}`;
143
143
  };
144
144
 
145
+ // src/utils/dependencyUtils.ts
146
+ function buildAdjacencyList(tasks) {
147
+ const taskMap = new Map(tasks.map((t) => [t.id, t]));
148
+ const graph = /* @__PURE__ */ new Map();
149
+ for (const task of tasks) {
150
+ const successors = [];
151
+ for (const otherTask of tasks) {
152
+ if (otherTask.dependencies) {
153
+ for (const dep of otherTask.dependencies) {
154
+ if (dep.taskId === task.id) {
155
+ successors.push(otherTask.id);
156
+ break;
157
+ }
158
+ }
159
+ }
160
+ }
161
+ graph.set(task.id, successors);
162
+ }
163
+ return graph;
164
+ }
165
+ function detectCycles(tasks) {
166
+ const graph = buildAdjacencyList(tasks);
167
+ const visiting = /* @__PURE__ */ new Set();
168
+ const visited = /* @__PURE__ */ new Set();
169
+ const path = [];
170
+ function dfs(taskId) {
171
+ if (visiting.has(taskId)) {
172
+ return true;
173
+ }
174
+ if (visited.has(taskId)) {
175
+ return false;
176
+ }
177
+ visiting.add(taskId);
178
+ path.push(taskId);
179
+ const successors = graph.get(taskId) || [];
180
+ for (const successor of successors) {
181
+ if (dfs(successor)) {
182
+ return true;
183
+ }
184
+ }
185
+ visiting.delete(taskId);
186
+ path.pop();
187
+ visited.add(taskId);
188
+ return false;
189
+ }
190
+ for (const task of tasks) {
191
+ if (dfs(task.id)) {
192
+ return { hasCycle: true, cyclePath: [...path] };
193
+ }
194
+ }
195
+ return { hasCycle: false };
196
+ }
197
+ function calculateSuccessorDate(predecessorStart, predecessorEnd, linkType, lag = 0) {
198
+ const baseDate = linkType.startsWith("F") ? predecessorEnd : predecessorStart;
199
+ const lagMs = lag * 24 * 60 * 60 * 1e3;
200
+ const resultDate = new Date(baseDate.getTime() + lagMs);
201
+ return resultDate;
202
+ }
203
+ function validateDependencies(tasks) {
204
+ const errors = [];
205
+ const taskIds = new Set(tasks.map((t) => t.id));
206
+ for (const task of tasks) {
207
+ if (task.dependencies) {
208
+ for (const dep of task.dependencies) {
209
+ if (!taskIds.has(dep.taskId)) {
210
+ errors.push({
211
+ type: "missing-task",
212
+ taskId: task.id,
213
+ message: `Dependency references non-existent task: ${dep.taskId}`,
214
+ relatedTaskIds: [dep.taskId]
215
+ });
216
+ }
217
+ }
218
+ }
219
+ }
220
+ const cycleResult = detectCycles(tasks);
221
+ if (cycleResult.hasCycle && cycleResult.cyclePath) {
222
+ errors.push({
223
+ type: "cycle",
224
+ taskId: cycleResult.cyclePath[0],
225
+ message: "Circular dependency detected",
226
+ relatedTaskIds: cycleResult.cyclePath
227
+ });
228
+ }
229
+ return {
230
+ isValid: errors.length === 0,
231
+ errors
232
+ };
233
+ }
234
+ function getSuccessorChain(draggedTaskId, allTasks) {
235
+ const successorMap = /* @__PURE__ */ new Map();
236
+ for (const task of allTasks) {
237
+ successorMap.set(task.id, []);
238
+ }
239
+ for (const task of allTasks) {
240
+ if (!task.dependencies) continue;
241
+ for (const dep of task.dependencies) {
242
+ if (dep.type === "FS") {
243
+ const list = successorMap.get(dep.taskId) ?? [];
244
+ list.push(task.id);
245
+ successorMap.set(dep.taskId, list);
246
+ }
247
+ }
248
+ }
249
+ const taskById = new Map(allTasks.map((t) => [t.id, t]));
250
+ const visited = /* @__PURE__ */ new Set();
251
+ const queue = [draggedTaskId];
252
+ const chain = [];
253
+ visited.add(draggedTaskId);
254
+ while (queue.length > 0) {
255
+ const current = queue.shift();
256
+ const successors = successorMap.get(current) ?? [];
257
+ for (const sid of successors) {
258
+ if (!visited.has(sid)) {
259
+ visited.add(sid);
260
+ const t = taskById.get(sid);
261
+ if (t) {
262
+ chain.push(t);
263
+ queue.push(sid);
264
+ }
265
+ }
266
+ }
267
+ }
268
+ return chain;
269
+ }
270
+ function getAllDependencyEdges(tasks) {
271
+ const edges = [];
272
+ for (const task of tasks) {
273
+ if (task.dependencies) {
274
+ for (const dep of task.dependencies) {
275
+ edges.push({
276
+ predecessorId: dep.taskId,
277
+ successorId: task.id,
278
+ type: dep.type,
279
+ lag: dep.lag ?? 0
280
+ });
281
+ }
282
+ }
283
+ }
284
+ return edges;
285
+ }
286
+
145
287
  // src/components/TimeScaleHeader/TimeScaleHeader.tsx
146
288
  import { useMemo } from "react";
147
289
  import { format } from "date-fns";
@@ -205,7 +347,7 @@ var TimeScaleHeader = ({
205
347
  var TimeScaleHeader_default = TimeScaleHeader;
206
348
 
207
349
  // src/components/TaskRow/TaskRow.tsx
208
- import React2, { useMemo as useMemo2, useState as useState2, useEffect as useEffect2, useRef as useRef2 } from "react";
350
+ import React2, { useMemo as useMemo2 } from "react";
209
351
 
210
352
  // src/utils/geometry.ts
211
353
  var getUTCDayDifference = (date1, date2) => {
@@ -305,6 +447,43 @@ var calculateWeekendBlocks = (dateRange, dayWidth) => {
305
447
  }
306
448
  return blocks;
307
449
  };
450
+ var calculateBezierPath = (from, to) => {
451
+ const verticalDistance = Math.abs(to.y - from.y);
452
+ const cpOffset = Math.max(verticalDistance * 0.5, 20);
453
+ if (from.y === to.y) {
454
+ const arcHeight = 20;
455
+ const midX = (from.x + to.x) / 2;
456
+ return `M ${from.x} ${from.y} Q ${midX} ${from.y - arcHeight} ${to.x} ${to.y}`;
457
+ }
458
+ const cp1x = from.x;
459
+ const cp1y = from.y + (to.y > from.y ? cpOffset : -cpOffset);
460
+ const cp2x = to.x;
461
+ const cp2y = to.y - (to.y > from.y ? cpOffset : -cpOffset);
462
+ 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)}`;
463
+ };
464
+ var calculateOrthogonalPath = (from, to) => {
465
+ const fx = Math.round(from.x);
466
+ const fy = Math.round(from.y);
467
+ const tx = Math.round(to.x);
468
+ const ty = Math.round(to.y);
469
+ if (fy === ty) {
470
+ return `M ${fx} ${fy} H ${tx}`;
471
+ }
472
+ const C = 2;
473
+ const goingDown = ty > fy;
474
+ const goingRight = tx >= fx;
475
+ const dirY = goingDown ? 1 : -1;
476
+ const dirX = goingRight ? 1 : -1;
477
+ if (Math.abs(ty - fy) >= C && Math.abs(tx - fx) >= C) {
478
+ return [
479
+ `M ${fx} ${fy}`,
480
+ `H ${tx - dirX * C}`,
481
+ `L ${tx} ${fy + dirY * C}`,
482
+ `V ${ty}`
483
+ ].join(" ");
484
+ }
485
+ return `M ${fx} ${fy} H ${tx} V ${ty}`;
486
+ };
308
487
 
309
488
  // src/hooks/useTaskDrag.ts
310
489
  import { useEffect, useRef, useState, useCallback } from "react";
@@ -316,8 +495,8 @@ function completeDrag() {
316
495
  globalRafId = null;
317
496
  }
318
497
  if (globalActiveDrag) {
498
+ globalActiveDrag.onCascadeProgress?.(/* @__PURE__ */ new Map());
319
499
  const { onComplete, currentLeft, currentWidth } = globalActiveDrag;
320
- const drag = globalActiveDrag;
321
500
  globalActiveDrag = null;
322
501
  onComplete(currentLeft, currentWidth);
323
502
  }
@@ -336,6 +515,27 @@ function cancelDrag() {
336
515
  function snapToGrid(pixels, dayWidth) {
337
516
  return Math.round(pixels / dayWidth) * dayWidth;
338
517
  }
518
+ function recalculateIncomingLags(task, newStartDate, allTasks) {
519
+ if (!task.dependencies) return [];
520
+ const taskById = new Map(allTasks.map((t) => [t.id, t]));
521
+ return task.dependencies.map((dep) => {
522
+ if (dep.type !== "FS") return dep;
523
+ const predecessor = taskById.get(dep.taskId);
524
+ if (!predecessor) return dep;
525
+ const predEnd = new Date(predecessor.endDate);
526
+ const lagMs = Date.UTC(
527
+ newStartDate.getUTCFullYear(),
528
+ newStartDate.getUTCMonth(),
529
+ newStartDate.getUTCDate()
530
+ ) - Date.UTC(
531
+ predEnd.getUTCFullYear(),
532
+ predEnd.getUTCMonth(),
533
+ predEnd.getUTCDate()
534
+ );
535
+ const lagDays = Math.round(lagMs / (24 * 60 * 60 * 1e3));
536
+ return { ...dep, lag: lagDays };
537
+ });
538
+ }
339
539
  function handleGlobalMouseMove(e) {
340
540
  if (!globalActiveDrag || globalRafId !== null) {
341
541
  return;
@@ -345,7 +545,7 @@ function handleGlobalMouseMove(e) {
345
545
  globalRafId = null;
346
546
  return;
347
547
  }
348
- const { startX, initialLeft, initialWidth, mode, dayWidth, onProgress } = globalActiveDrag;
548
+ const { startX, initialLeft, initialWidth, mode, dayWidth, onProgress, allTasks } = globalActiveDrag;
349
549
  const deltaX = e.clientX - startX;
350
550
  let newLeft = initialLeft;
351
551
  let newWidth = initialWidth;
@@ -364,6 +564,50 @@ function handleGlobalMouseMove(e) {
364
564
  newWidth = Math.max(dayWidth, snappedWidth);
365
565
  break;
366
566
  }
567
+ if (mode === "move" && allTasks.length > 0 && !globalActiveDrag.disableConstraints) {
568
+ const currentTask = allTasks.find((t) => t.id === globalActiveDrag?.taskId);
569
+ if (currentTask && currentTask.dependencies && currentTask.dependencies.length > 0) {
570
+ let minAllowedLeft = 0;
571
+ for (const dep of currentTask.dependencies) {
572
+ if (dep.type !== "FS") continue;
573
+ const predecessor = globalActiveDrag.allTasks.find((t) => t.id === dep.taskId);
574
+ if (!predecessor) continue;
575
+ const predStart = new Date(predecessor.startDate);
576
+ const predStartOffset = Math.round(
577
+ (Date.UTC(predStart.getUTCFullYear(), predStart.getUTCMonth(), predStart.getUTCDate()) - Date.UTC(
578
+ globalActiveDrag.monthStart.getUTCFullYear(),
579
+ globalActiveDrag.monthStart.getUTCMonth(),
580
+ globalActiveDrag.monthStart.getUTCDate()
581
+ )) / (24 * 60 * 60 * 1e3)
582
+ );
583
+ const predStartLeft = Math.round(predStartOffset * globalActiveDrag.dayWidth);
584
+ minAllowedLeft = Math.max(minAllowedLeft, predStartLeft);
585
+ }
586
+ newLeft = Math.max(minAllowedLeft, newLeft);
587
+ }
588
+ }
589
+ if ((mode === "move" || mode === "resize-right") && !globalActiveDrag.disableConstraints && globalActiveDrag.cascadeChain.length > 0 && globalActiveDrag.onCascadeProgress) {
590
+ const deltaDays = mode === "resize-right" ? Math.round((newWidth - globalActiveDrag.initialWidth) / globalActiveDrag.dayWidth) : Math.round((newLeft - globalActiveDrag.initialLeft) / globalActiveDrag.dayWidth);
591
+ const overrides = /* @__PURE__ */ new Map();
592
+ for (const chainTask of globalActiveDrag.cascadeChain) {
593
+ const chainStart = new Date(chainTask.startDate);
594
+ const chainEnd = new Date(chainTask.endDate);
595
+ const chainStartOffset = Math.round(
596
+ (Date.UTC(chainStart.getUTCFullYear(), chainStart.getUTCMonth(), chainStart.getUTCDate()) - Date.UTC(
597
+ globalActiveDrag.monthStart.getUTCFullYear(),
598
+ globalActiveDrag.monthStart.getUTCMonth(),
599
+ globalActiveDrag.monthStart.getUTCDate()
600
+ )) / (24 * 60 * 60 * 1e3)
601
+ );
602
+ const chainDuration = Math.round(
603
+ (Date.UTC(chainEnd.getUTCFullYear(), chainEnd.getUTCMonth(), chainEnd.getUTCDate()) - Date.UTC(chainStart.getUTCFullYear(), chainStart.getUTCMonth(), chainStart.getUTCDate())) / (24 * 60 * 60 * 1e3)
604
+ );
605
+ const chainLeft = Math.round((chainStartOffset + deltaDays) * globalActiveDrag.dayWidth);
606
+ const chainWidth = Math.round((chainDuration + 1) * globalActiveDrag.dayWidth);
607
+ overrides.set(chainTask.id, { left: chainLeft, width: chainWidth });
608
+ }
609
+ globalActiveDrag.onCascadeProgress(overrides);
610
+ }
367
611
  globalActiveDrag.currentLeft = newLeft;
368
612
  globalActiveDrag.currentWidth = newWidth;
369
613
  onProgress(newLeft, newWidth);
@@ -392,7 +636,13 @@ var useTaskDrag = (options) => {
392
636
  dayWidth,
393
637
  onDragEnd,
394
638
  onDragStateChange,
395
- edgeZoneWidth = 12
639
+ edgeZoneWidth = 12,
640
+ allTasks = [],
641
+ rowIndex,
642
+ enableAutoSchedule = false,
643
+ disableConstraints = false,
644
+ onCascadeProgress,
645
+ onCascade
396
646
  } = options;
397
647
  const isOwnerRef = useRef(false);
398
648
  const [isDragging, setIsDragging] = useState(false);
@@ -462,14 +712,60 @@ var useTaskDrag = (options) => {
462
712
  width: finalWidth
463
713
  });
464
714
  }
465
- if (onDragEnd && wasOwner) {
466
- onDragEnd({
467
- id: taskId,
468
- startDate: newStartDate,
469
- endDate: newEndDate
470
- });
715
+ if (wasOwner) {
716
+ if (!disableConstraints && onCascade && allTasks.length > 0) {
717
+ const chain = getSuccessorChain(taskId, allTasks);
718
+ if (chain.length > 0) {
719
+ const origEndMs = Date.UTC(
720
+ initialEndDate.getUTCFullYear(),
721
+ initialEndDate.getUTCMonth(),
722
+ initialEndDate.getUTCDate()
723
+ );
724
+ const newEndMs = Date.UTC(
725
+ newEndDate.getUTCFullYear(),
726
+ newEndDate.getUTCMonth(),
727
+ newEndDate.getUTCDate()
728
+ );
729
+ const deltaDays = Math.round((newEndMs - origEndMs) / (24 * 60 * 60 * 1e3));
730
+ const draggedTaskData = allTasks.find((t) => t.id === taskId);
731
+ const cascadedTasks = [
732
+ {
733
+ ...draggedTaskData ?? { id: taskId, name: "", startDate: "", endDate: "" },
734
+ startDate: newStartDate.toISOString(),
735
+ endDate: newEndDate.toISOString(),
736
+ ...draggedTaskData?.dependencies && {
737
+ dependencies: recalculateIncomingLags(draggedTaskData, newStartDate, allTasks)
738
+ }
739
+ },
740
+ ...chain.map((chainTask) => {
741
+ const origStart = new Date(chainTask.startDate);
742
+ const origEnd = new Date(chainTask.endDate);
743
+ const newStart = new Date(Date.UTC(
744
+ origStart.getUTCFullYear(),
745
+ origStart.getUTCMonth(),
746
+ origStart.getUTCDate() + deltaDays
747
+ ));
748
+ const newEnd = new Date(Date.UTC(
749
+ origEnd.getUTCFullYear(),
750
+ origEnd.getUTCMonth(),
751
+ origEnd.getUTCDate() + deltaDays
752
+ ));
753
+ return { ...chainTask, startDate: newStart.toISOString(), endDate: newEnd.toISOString() };
754
+ })
755
+ ];
756
+ onCascade(cascadedTasks);
757
+ return;
758
+ }
759
+ }
760
+ if (allTasks.length > 0 && onDragEnd) {
761
+ const currentTaskData = allTasks.find((t) => t.id === taskId);
762
+ const updatedDependencies = currentTaskData?.dependencies ? recalculateIncomingLags(currentTaskData, newStartDate, allTasks) : void 0;
763
+ onDragEnd({ id: taskId, startDate: newStartDate, endDate: newEndDate, updatedDependencies });
764
+ } else if (onDragEnd) {
765
+ onDragEnd({ id: taskId, startDate: newStartDate, endDate: newEndDate });
766
+ }
471
767
  }
472
- }, [dayWidth, monthStart, onDragEnd, onDragStateChange, taskId]);
768
+ }, [dayWidth, monthStart, onDragEnd, onDragStateChange, taskId, disableConstraints, onCascade, allTasks, initialStartDate, initialEndDate]);
473
769
  const handleCancel = useCallback(() => {
474
770
  isOwnerRef.current = false;
475
771
  setIsDragging(false);
@@ -536,9 +832,13 @@ var useTaskDrag = (options) => {
536
832
  monthStart,
537
833
  onProgress: handleProgress,
538
834
  onComplete: handleComplete,
539
- onCancel: handleCancel
835
+ onCancel: handleCancel,
836
+ allTasks,
837
+ disableConstraints,
838
+ cascadeChain: (mode === "move" || mode === "resize-right") && !disableConstraints ? getSuccessorChain(taskId, allTasks) : [],
839
+ onCascadeProgress
540
840
  };
541
- }, [edgeZoneWidth, currentLeft, currentWidth, dayWidth, monthStart, taskId, onDragStateChange, handleProgress, handleComplete, handleCancel]);
841
+ }, [edgeZoneWidth, currentLeft, currentWidth, dayWidth, monthStart, taskId, onDragStateChange, handleProgress, handleComplete, handleCancel, allTasks, disableConstraints, onCascadeProgress, onCascade]);
542
842
  const getCursorStyle = useCallback(() => {
543
843
  if (isDragging) {
544
844
  return "grabbing";
@@ -563,10 +863,10 @@ var useTaskDrag = (options) => {
563
863
  // src/components/TaskRow/TaskRow.tsx
564
864
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
565
865
  var arePropsEqual = (prevProps, nextProps) => {
566
- 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;
866
+ 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;
567
867
  };
568
868
  var TaskRow = React2.memo(
569
- ({ task, monthStart, dayWidth, rowHeight, onChange, onDragStateChange }) => {
869
+ ({ task, monthStart, dayWidth, rowHeight, onChange, onDragStateChange, rowIndex, allTasks, enableAutoSchedule, disableConstraints, overridePosition, onCascadeProgress, onCascade }) => {
570
870
  const taskStartDate = useMemo2(() => parseUTCDate(task.startDate), [task.startDate]);
571
871
  const taskEndDate = useMemo2(() => parseUTCDate(task.endDate), [task.endDate]);
572
872
  const { left, width } = useMemo2(
@@ -574,11 +874,22 @@ var TaskRow = React2.memo(
574
874
  [taskStartDate, taskEndDate, monthStart, dayWidth]
575
875
  );
576
876
  const barColor = task.color || "var(--gantt-task-bar-default-color)";
877
+ const progressWidth = useMemo2(() => {
878
+ if (task.progress === void 0 || task.progress <= 0) return 0;
879
+ return Math.min(100, Math.max(0, Math.round(task.progress)));
880
+ }, [task.progress]);
881
+ const progressColor = useMemo2(() => {
882
+ if (progressWidth === 100) {
883
+ return task.accepted ? "var(--gantt-progress-accepted, #22c55e)" : "var(--gantt-progress-completed, #fbbf24)";
884
+ }
885
+ return task.color ? `color-mix(in srgb, ${task.color} 40%, black)` : "var(--gantt-progress-color, rgba(0, 0, 0, 0.2))";
886
+ }, [progressWidth, task.accepted, task.color]);
577
887
  const handleDragEnd = (result) => {
578
888
  const updatedTask = {
579
889
  ...task,
580
890
  startDate: result.startDate.toISOString(),
581
- endDate: result.endDate.toISOString()
891
+ endDate: result.endDate.toISOString(),
892
+ ...result.updatedDependencies !== void 0 && { dependencies: result.updatedDependencies }
582
893
  };
583
894
  onChange?.(updatedTask);
584
895
  };
@@ -596,10 +907,16 @@ var TaskRow = React2.memo(
596
907
  dayWidth,
597
908
  onDragEnd: handleDragEnd,
598
909
  onDragStateChange,
599
- edgeZoneWidth: 20
910
+ edgeZoneWidth: 20,
911
+ allTasks,
912
+ rowIndex,
913
+ enableAutoSchedule,
914
+ disableConstraints,
915
+ onCascadeProgress,
916
+ onCascade
600
917
  });
601
- const displayLeft = isDragging ? currentLeft : left;
602
- const displayWidth = isDragging ? currentWidth : width;
918
+ const displayLeft = overridePosition?.left ?? (isDragging ? currentLeft : left);
919
+ const displayWidth = overridePosition?.width ?? (isDragging ? currentWidth : width);
603
920
  const currentStartDate = isDragging ? pixelsToDate(displayLeft, monthStart, dayWidth) : taskStartDate;
604
921
  const currentEndDate = isDragging ? pixelsToDate(displayLeft + displayWidth - dayWidth, monthStart, dayWidth) : taskEndDate;
605
922
  const startDateLabel = formatDateLabel(currentStartDate);
@@ -607,16 +924,6 @@ var TaskRow = React2.memo(
607
924
  const durationDays = Math.round(
608
925
  (currentEndDate.getTime() - currentStartDate.getTime()) / (1e3 * 60 * 60 * 24)
609
926
  ) + 1;
610
- const [isNameOverflow, setIsNameOverflow] = useState2(false);
611
- const taskNameRef = useRef2(null);
612
- useEffect2(() => {
613
- const nameEl = taskNameRef.current;
614
- if (nameEl) {
615
- const reservedWidth = 120;
616
- const availableWidth = displayWidth - reservedWidth;
617
- setIsNameOverflow(nameEl.scrollWidth > availableWidth);
618
- }
619
- }, [displayWidth, task.name]);
620
927
  return /* @__PURE__ */ jsx2(
621
928
  "div",
622
929
  {
@@ -638,22 +945,21 @@ var TaskRow = React2.memo(
638
945
  },
639
946
  onMouseDown: dragHandleProps.onMouseDown,
640
947
  children: [
948
+ progressWidth > 0 && /* @__PURE__ */ jsx2(
949
+ "div",
950
+ {
951
+ className: "gantt-tr-progressBar",
952
+ style: {
953
+ width: `${progressWidth}%`,
954
+ backgroundColor: progressColor
955
+ }
956
+ }
957
+ ),
641
958
  /* @__PURE__ */ jsx2("div", { className: "gantt-tr-resizeHandle gantt-tr-resizeHandleLeft" }),
642
959
  /* @__PURE__ */ jsxs2("span", { className: "gantt-tr-taskDuration", children: [
643
960
  durationDays,
644
961
  " \u0434"
645
962
  ] }),
646
- /* @__PURE__ */ jsxs2(
647
- "span",
648
- {
649
- ref: taskNameRef,
650
- className: `gantt-tr-taskName ${isNameOverflow ? "gantt-tr-taskNameHidden" : ""}`,
651
- children: [
652
- "\u2014 ",
653
- task.name
654
- ]
655
- }
656
- ),
657
963
  /* @__PURE__ */ jsx2("div", { className: "gantt-tr-resizeHandle gantt-tr-resizeHandleRight" })
658
964
  ]
659
965
  }
@@ -679,7 +985,7 @@ var TaskRow = React2.memo(
679
985
  style: {
680
986
  left: `${displayLeft + displayWidth}px`
681
987
  },
682
- children: isNameOverflow && /* @__PURE__ */ jsx2("span", { className: "gantt-tr-externalTaskName", children: task.name })
988
+ children: /* @__PURE__ */ jsx2("span", { className: "gantt-tr-externalTaskName", children: task.name })
683
989
  }
684
990
  )
685
991
  ] })
@@ -825,39 +1131,183 @@ var DragGuideLines = ({
825
1131
  };
826
1132
  var DragGuideLines_default = DragGuideLines;
827
1133
 
828
- // src/components/GanttChart/GanttChart.tsx
1134
+ // src/components/DependencyLines/DependencyLines.tsx
1135
+ import React5, { useMemo as useMemo5 } from "react";
829
1136
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1137
+ var DependencyLines = React5.memo(({
1138
+ tasks,
1139
+ monthStart,
1140
+ dayWidth,
1141
+ rowHeight,
1142
+ gridWidth,
1143
+ dragOverrides
1144
+ }) => {
1145
+ const { taskPositions, taskIndices } = useMemo5(() => {
1146
+ const positions = /* @__PURE__ */ new Map();
1147
+ const indices = /* @__PURE__ */ new Map();
1148
+ tasks.forEach((task, index) => {
1149
+ const startDate = new Date(task.startDate);
1150
+ const endDate = new Date(task.endDate);
1151
+ const computed = calculateTaskBar(startDate, endDate, monthStart, dayWidth);
1152
+ const override = dragOverrides?.get(task.id);
1153
+ const resolvedLeft = override?.left ?? computed.left;
1154
+ const resolvedWidth = override?.width ?? computed.width;
1155
+ indices.set(task.id, index);
1156
+ positions.set(task.id, {
1157
+ left: resolvedLeft + 10,
1158
+ right: resolvedLeft + resolvedWidth,
1159
+ rowTop: index * rowHeight
1160
+ });
1161
+ });
1162
+ return { taskPositions: positions, taskIndices: indices };
1163
+ }, [tasks, monthStart, dayWidth, rowHeight, dragOverrides]);
1164
+ const cycleInfo = useMemo5(() => {
1165
+ const result = detectCycles(tasks);
1166
+ const cycleTaskIds = new Set(result.cyclePath || []);
1167
+ return cycleTaskIds;
1168
+ }, [tasks]);
1169
+ const lines = useMemo5(() => {
1170
+ const edges = getAllDependencyEdges(tasks);
1171
+ const lines2 = [];
1172
+ for (const edge of edges) {
1173
+ const predecessor = taskPositions.get(edge.predecessorId);
1174
+ const successor = taskPositions.get(edge.successorId);
1175
+ const predecessorIndex = taskIndices.get(edge.predecessorId);
1176
+ const successorIndex = taskIndices.get(edge.successorId);
1177
+ if (!predecessor || !successor || predecessorIndex === void 0 || successorIndex === void 0) {
1178
+ continue;
1179
+ }
1180
+ const reverseOrder = predecessorIndex > successorIndex;
1181
+ let fromY;
1182
+ let toY;
1183
+ if (reverseOrder) {
1184
+ fromY = predecessor.rowTop + 10;
1185
+ toY = successor.rowTop + rowHeight - 6;
1186
+ } else {
1187
+ fromY = predecessor.rowTop + rowHeight - 10;
1188
+ toY = successor.rowTop + 6;
1189
+ }
1190
+ const from = { x: predecessor.right, y: fromY };
1191
+ const to = { x: successor.left, y: toY };
1192
+ const path = calculateOrthogonalPath(from, to);
1193
+ const hasCycle = cycleInfo.has(edge.predecessorId) || cycleInfo.has(edge.successorId);
1194
+ lines2.push({
1195
+ id: `${edge.predecessorId}-${edge.successorId}`,
1196
+ path,
1197
+ hasCycle
1198
+ });
1199
+ }
1200
+ return lines2;
1201
+ }, [tasks, taskPositions, taskIndices, cycleInfo]);
1202
+ return /* @__PURE__ */ jsxs5(
1203
+ "svg",
1204
+ {
1205
+ className: "gantt-dependencies-svg",
1206
+ width: gridWidth,
1207
+ height: tasks.length * rowHeight,
1208
+ xmlns: "http://www.w3.org/2000/svg",
1209
+ children: [
1210
+ /* @__PURE__ */ jsxs5("defs", { children: [
1211
+ /* @__PURE__ */ jsx6(
1212
+ "marker",
1213
+ {
1214
+ id: "arrowhead",
1215
+ markerWidth: "8",
1216
+ markerHeight: "6",
1217
+ markerUnits: "userSpaceOnUse",
1218
+ refX: "7",
1219
+ refY: "3",
1220
+ orient: "auto",
1221
+ children: /* @__PURE__ */ jsx6(
1222
+ "polygon",
1223
+ {
1224
+ points: "0 0, 8 3, 0 6",
1225
+ fill: "var(--gantt-dependency-line-color, #666666)"
1226
+ }
1227
+ )
1228
+ }
1229
+ ),
1230
+ /* @__PURE__ */ jsx6(
1231
+ "marker",
1232
+ {
1233
+ id: "arrowhead-cycle",
1234
+ markerWidth: "8",
1235
+ markerHeight: "6",
1236
+ markerUnits: "userSpaceOnUse",
1237
+ refX: "7",
1238
+ refY: "3",
1239
+ orient: "auto",
1240
+ children: /* @__PURE__ */ jsx6(
1241
+ "polygon",
1242
+ {
1243
+ points: "0 0, 8 3, 0 6",
1244
+ fill: "var(--gantt-dependency-cycle-color, #ef4444)"
1245
+ }
1246
+ )
1247
+ }
1248
+ )
1249
+ ] }),
1250
+ lines.map(({ id, path, hasCycle }) => /* @__PURE__ */ jsx6(
1251
+ "path",
1252
+ {
1253
+ d: path,
1254
+ className: hasCycle ? "gantt-dependency-path gantt-dependency-cycle" : "gantt-dependency-path",
1255
+ markerEnd: hasCycle ? "url(#arrowhead-cycle)" : "url(#arrowhead)"
1256
+ },
1257
+ id
1258
+ ))
1259
+ ]
1260
+ }
1261
+ );
1262
+ });
1263
+ DependencyLines.displayName = "DependencyLines";
1264
+ var DependencyLines_default = DependencyLines;
1265
+
1266
+ // src/components/GanttChart/GanttChart.tsx
1267
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
830
1268
  var GanttChart = ({
831
1269
  tasks,
832
1270
  dayWidth = 40,
833
1271
  rowHeight = 40,
834
1272
  headerHeight = 40,
835
1273
  containerHeight = 600,
836
- onChange
1274
+ onChange,
1275
+ onValidateDependencies,
1276
+ enableAutoSchedule,
1277
+ disableConstraints,
1278
+ onCascade
837
1279
  }) => {
838
- const scrollContainerRef = useRef3(null);
839
- const dateRange = useMemo5(() => getMultiMonthDays(tasks), [tasks]);
840
- const gridWidth = useMemo5(
1280
+ const scrollContainerRef = useRef2(null);
1281
+ const dateRange = useMemo6(() => getMultiMonthDays(tasks), [tasks]);
1282
+ const [validationResult, setValidationResult] = useState2(null);
1283
+ const [cascadeOverrides, setCascadeOverrides] = useState2(/* @__PURE__ */ new Map());
1284
+ const gridWidth = useMemo6(
841
1285
  () => Math.round(dateRange.length * dayWidth),
842
1286
  [dateRange.length, dayWidth]
843
1287
  );
844
- const totalGridHeight = useMemo5(
1288
+ const totalGridHeight = useMemo6(
845
1289
  () => tasks.length * rowHeight,
846
1290
  [tasks.length, rowHeight]
847
1291
  );
848
- const monthStart = useMemo5(() => {
1292
+ const monthStart = useMemo6(() => {
849
1293
  if (dateRange.length === 0) {
850
1294
  return new Date(Date.UTC((/* @__PURE__ */ new Date()).getUTCFullYear(), (/* @__PURE__ */ new Date()).getUTCMonth(), 1));
851
1295
  }
852
1296
  const firstDay = dateRange[0];
853
1297
  return new Date(Date.UTC(firstDay.getUTCFullYear(), firstDay.getUTCMonth(), 1));
854
1298
  }, [dateRange]);
855
- const todayInRange = useMemo5(() => {
1299
+ const todayInRange = useMemo6(() => {
856
1300
  const now = /* @__PURE__ */ new Date();
857
1301
  const today = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
858
1302
  return dateRange.some((day) => day.getTime() === today.getTime());
859
1303
  }, [dateRange]);
860
- const [dragGuideLines, setDragGuideLines] = useState3(null);
1304
+ const [dragGuideLines, setDragGuideLines] = useState2(null);
1305
+ const [draggedTaskOverride, setDraggedTaskOverride] = useState2(null);
1306
+ useEffect2(() => {
1307
+ const result = validateDependencies(tasks);
1308
+ setValidationResult(result);
1309
+ onValidateDependencies?.(result);
1310
+ }, [tasks, onValidateDependencies]);
861
1311
  const handleTaskChange = useCallback2((updatedTask) => {
862
1312
  onChange?.(
863
1313
  (currentTasks) => currentTasks.map(
@@ -865,14 +1315,27 @@ var GanttChart = ({
865
1315
  )
866
1316
  );
867
1317
  }, [onChange]);
868
- const handleDragStateChange = useCallback2((state) => {
869
- if (state.isDragging) {
870
- setDragGuideLines(state);
871
- } else {
872
- setDragGuideLines(null);
1318
+ const dependencyOverrides = useMemo6(() => {
1319
+ const map = new Map(cascadeOverrides);
1320
+ if (draggedTaskOverride) {
1321
+ map.set(draggedTaskOverride.taskId, {
1322
+ left: draggedTaskOverride.left,
1323
+ width: draggedTaskOverride.width
1324
+ });
873
1325
  }
1326
+ return map;
1327
+ }, [cascadeOverrides, draggedTaskOverride]);
1328
+ const handleCascadeProgress = useCallback2((overrides) => {
1329
+ setCascadeOverrides(new Map(overrides));
874
1330
  }, []);
875
- const panStateRef = useRef3(null);
1331
+ const handleCascade = useCallback2((cascadedTasks) => {
1332
+ onChange?.((currentTasks) => {
1333
+ const cascadeMap = new Map(cascadedTasks.map((t) => [t.id, t]));
1334
+ return currentTasks.map((t) => cascadeMap.get(t.id) ?? t);
1335
+ });
1336
+ onCascade?.(cascadedTasks);
1337
+ }, [onChange, onCascade]);
1338
+ const panStateRef = useRef2(null);
876
1339
  const handlePanStart = useCallback2((e) => {
877
1340
  if (e.button !== 0) return;
878
1341
  const target = e.target;
@@ -889,7 +1352,7 @@ var GanttChart = ({
889
1352
  container.style.cursor = "grabbing";
890
1353
  e.preventDefault();
891
1354
  }, []);
892
- useEffect3(() => {
1355
+ useEffect2(() => {
893
1356
  const handlePanMove = (e) => {
894
1357
  const pan = panStateRef.current;
895
1358
  if (!pan?.active) return;
@@ -911,7 +1374,7 @@ var GanttChart = ({
911
1374
  window.removeEventListener("mouseup", handlePanEnd);
912
1375
  };
913
1376
  }, []);
914
- return /* @__PURE__ */ jsx6("div", { className: "gantt-container", children: /* @__PURE__ */ jsxs5(
1377
+ return /* @__PURE__ */ jsx7("div", { className: "gantt-container", children: /* @__PURE__ */ jsxs6(
915
1378
  "div",
916
1379
  {
917
1380
  ref: scrollContainerRef,
@@ -919,7 +1382,7 @@ var GanttChart = ({
919
1382
  style: { height: `${containerHeight}px`, cursor: "grab" },
920
1383
  onMouseDown: handlePanStart,
921
1384
  children: [
922
- /* @__PURE__ */ jsx6("div", { className: "gantt-stickyHeader", style: { width: `${gridWidth}px` }, children: /* @__PURE__ */ jsx6(
1385
+ /* @__PURE__ */ jsx7("div", { className: "gantt-stickyHeader", style: { width: `${gridWidth}px` }, children: /* @__PURE__ */ jsx7(
923
1386
  TimeScaleHeader_default,
924
1387
  {
925
1388
  days: dateRange,
@@ -927,7 +1390,7 @@ var GanttChart = ({
927
1390
  headerHeight
928
1391
  }
929
1392
  ) }),
930
- /* @__PURE__ */ jsxs5(
1393
+ /* @__PURE__ */ jsxs6(
931
1394
  "div",
932
1395
  {
933
1396
  className: "gantt-taskArea",
@@ -936,7 +1399,7 @@ var GanttChart = ({
936
1399
  width: `${gridWidth}px`
937
1400
  },
938
1401
  children: [
939
- /* @__PURE__ */ jsx6(
1402
+ /* @__PURE__ */ jsx7(
940
1403
  GridBackground_default,
941
1404
  {
942
1405
  dateRange,
@@ -944,8 +1407,19 @@ var GanttChart = ({
944
1407
  totalHeight: totalGridHeight
945
1408
  }
946
1409
  ),
947
- todayInRange && /* @__PURE__ */ jsx6(TodayIndicator_default, { monthStart, dayWidth }),
948
- dragGuideLines && /* @__PURE__ */ jsx6(
1410
+ todayInRange && /* @__PURE__ */ jsx7(TodayIndicator_default, { monthStart, dayWidth }),
1411
+ /* @__PURE__ */ jsx7(
1412
+ DependencyLines_default,
1413
+ {
1414
+ tasks,
1415
+ monthStart,
1416
+ dayWidth,
1417
+ rowHeight,
1418
+ gridWidth,
1419
+ dragOverrides: dependencyOverrides
1420
+ }
1421
+ ),
1422
+ dragGuideLines && /* @__PURE__ */ jsx7(
949
1423
  DragGuideLines_default,
950
1424
  {
951
1425
  isDragging: dragGuideLines.isDragging,
@@ -955,7 +1429,7 @@ var GanttChart = ({
955
1429
  totalHeight: totalGridHeight
956
1430
  }
957
1431
  ),
958
- tasks.map((task) => /* @__PURE__ */ jsx6(
1432
+ tasks.map((task, index) => /* @__PURE__ */ jsx7(
959
1433
  TaskRow_default,
960
1434
  {
961
1435
  task,
@@ -963,7 +1437,22 @@ var GanttChart = ({
963
1437
  dayWidth,
964
1438
  rowHeight,
965
1439
  onChange: handleTaskChange,
966
- onDragStateChange: handleDragStateChange
1440
+ onDragStateChange: (state) => {
1441
+ if (state.isDragging) {
1442
+ setDragGuideLines(state);
1443
+ setDraggedTaskOverride({ taskId: task.id, left: state.left, width: state.width });
1444
+ } else {
1445
+ setDragGuideLines(null);
1446
+ setDraggedTaskOverride(null);
1447
+ }
1448
+ },
1449
+ rowIndex: index,
1450
+ allTasks: tasks,
1451
+ enableAutoSchedule: enableAutoSchedule ?? false,
1452
+ disableConstraints: disableConstraints ?? false,
1453
+ overridePosition: cascadeOverrides.get(task.id),
1454
+ onCascadeProgress: handleCascadeProgress,
1455
+ onCascade: handleCascade
967
1456
  },
968
1457
  task.id
969
1458
  ))
@@ -981,21 +1470,29 @@ export {
981
1470
  TaskRow_default as TaskRow,
982
1471
  TimeScaleHeader_default as TimeScaleHeader,
983
1472
  TodayIndicator_default as TodayIndicator,
1473
+ buildAdjacencyList,
1474
+ calculateBezierPath,
984
1475
  calculateGridLines,
985
1476
  calculateGridWidth,
1477
+ calculateOrthogonalPath,
1478
+ calculateSuccessorDate,
986
1479
  calculateTaskBar,
987
1480
  calculateWeekendBlocks,
1481
+ detectCycles,
988
1482
  detectEdgeZone,
989
1483
  formatDateLabel,
1484
+ getAllDependencyEdges,
990
1485
  getCursorForPosition,
991
1486
  getDayOffset,
992
1487
  getMonthDays,
993
1488
  getMonthSpans,
994
1489
  getMultiMonthDays,
1490
+ getSuccessorChain,
995
1491
  isToday,
996
1492
  isWeekend,
997
1493
  parseUTCDate,
998
1494
  pixelsToDate,
999
- useTaskDrag
1495
+ useTaskDrag,
1496
+ validateDependencies
1000
1497
  };
1001
1498
  //# sourceMappingURL=index.mjs.map