gantt-lib 0.6.2 → 0.7.1

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
@@ -72,10 +72,12 @@ __export(index_exports, {
72
72
  getMultiMonthDays: () => getMultiMonthDays,
73
73
  getSuccessorChain: () => getSuccessorChain,
74
74
  getTransitiveCascadeChain: () => getTransitiveCascadeChain,
75
+ getVisibleReorderPosition: () => getVisibleReorderPosition,
75
76
  isTaskParent: () => isTaskParent,
76
77
  isToday: () => isToday,
77
78
  isWeekend: () => isWeekend,
78
79
  normalizeHierarchyTasks: () => normalizeHierarchyTasks,
80
+ normalizeTaskDates: () => normalizeTaskDates,
79
81
  parseUTCDate: () => parseUTCDate,
80
82
  pixelsToDate: () => pixelsToDate,
81
83
  recalculateIncomingLags: () => recalculateIncomingLags,
@@ -227,6 +229,20 @@ var formatDateLabel = (date) => {
227
229
  const month = String(parsed.getUTCMonth() + 1).padStart(2, "0");
228
230
  return `${day}.${month}`;
229
231
  };
232
+ var normalizeTaskDates = (startDate, endDate) => {
233
+ const start = parseUTCDate(startDate);
234
+ const end = parseUTCDate(endDate);
235
+ if (end.getTime() < start.getTime()) {
236
+ return {
237
+ startDate: end.toISOString().split("T")[0],
238
+ endDate: start.toISOString().split("T")[0]
239
+ };
240
+ }
241
+ return {
242
+ startDate: start.toISOString().split("T")[0],
243
+ endDate: end.toISOString().split("T")[0]
244
+ };
245
+ };
230
246
 
231
247
  // src/utils/dependencyUtils.ts
232
248
  function buildAdjacencyList(tasks) {
@@ -281,33 +297,33 @@ function detectCycles(tasks) {
281
297
  return { hasCycle: false };
282
298
  }
283
299
  function computeLagFromDates(linkType, predStart, predEnd, succStart, succEnd) {
284
- const DAY_MS = 24 * 60 * 60 * 1e3;
300
+ const DAY_MS2 = 24 * 60 * 60 * 1e3;
285
301
  const pS = Date.UTC(predStart.getUTCFullYear(), predStart.getUTCMonth(), predStart.getUTCDate());
286
302
  const pE = Date.UTC(predEnd.getUTCFullYear(), predEnd.getUTCMonth(), predEnd.getUTCDate());
287
303
  const sS = Date.UTC(succStart.getUTCFullYear(), succStart.getUTCMonth(), succStart.getUTCDate());
288
304
  const sE = Date.UTC(succEnd.getUTCFullYear(), succEnd.getUTCMonth(), succEnd.getUTCDate());
289
305
  switch (linkType) {
290
306
  case "FS":
291
- return Math.round((sS - pE) / DAY_MS) - 1;
307
+ return Math.round((sS - pE) / DAY_MS2) - 1;
292
308
  case "SS":
293
- return Math.round((sS - pS) / DAY_MS);
309
+ return Math.round((sS - pS) / DAY_MS2);
294
310
  case "FF":
295
- return Math.round((sE - pE) / DAY_MS);
311
+ return Math.round((sE - pE) / DAY_MS2);
296
312
  case "SF":
297
- return Math.round((sE - pS) / DAY_MS) + 1;
313
+ return Math.round((sE - pS) / DAY_MS2) + 1;
298
314
  }
299
315
  }
300
316
  function calculateSuccessorDate(predecessorStart, predecessorEnd, linkType, lag = 0) {
301
- const DAY_MS = 24 * 60 * 60 * 1e3;
317
+ const DAY_MS2 = 24 * 60 * 60 * 1e3;
302
318
  switch (linkType) {
303
319
  case "FS":
304
- return new Date(predecessorEnd.getTime() + (lag + 1) * DAY_MS);
320
+ return new Date(predecessorEnd.getTime() + (lag + 1) * DAY_MS2);
305
321
  case "SS":
306
- return new Date(predecessorStart.getTime() + lag * DAY_MS);
322
+ return new Date(predecessorStart.getTime() + lag * DAY_MS2);
307
323
  case "FF":
308
- return new Date(predecessorEnd.getTime() + lag * DAY_MS);
324
+ return new Date(predecessorEnd.getTime() + lag * DAY_MS2);
309
325
  case "SF":
310
- return new Date(predecessorStart.getTime() + (lag - 1) * DAY_MS);
326
+ return new Date(predecessorStart.getTime() + (lag - 1) * DAY_MS2);
311
327
  }
312
328
  }
313
329
  function validateDependencies(tasks) {
@@ -539,13 +555,13 @@ function computeParentProgress(parentId, tasks) {
539
555
  if (children.length === 0) {
540
556
  return 0;
541
557
  }
542
- const DAY_MS = 24 * 60 * 60 * 1e3;
558
+ const DAY_MS2 = 24 * 60 * 60 * 1e3;
543
559
  let totalWeight = 0;
544
560
  let weightedSum = 0;
545
561
  for (const child of children) {
546
562
  const start = new Date(child.startDate).getTime();
547
563
  const end = new Date(child.endDate).getTime();
548
- const duration = (end - start + DAY_MS) / DAY_MS;
564
+ const duration = (end - start + DAY_MS2) / DAY_MS2;
549
565
  const progress = child.progress ?? 0;
550
566
  totalWeight += duration;
551
567
  weightedSum += duration * progress;
@@ -607,7 +623,10 @@ function flattenHierarchy(tasks) {
607
623
  return result;
608
624
  }
609
625
  function normalizeHierarchyTasks(tasks) {
610
- const orderedTasks = flattenHierarchy(tasks).map((task) => ({ ...task }));
626
+ const orderedTasks = flattenHierarchy(tasks).map((task) => {
627
+ const { startDate, endDate } = normalizeTaskDates(task.startDate, task.endDate);
628
+ return { ...task, startDate, endDate };
629
+ });
611
630
  for (const task of [...orderedTasks].reverse()) {
612
631
  if (!isTaskParent(task.id, orderedTasks)) continue;
613
632
  const { startDate, endDate } = computeParentDates(task.id, orderedTasks);
@@ -2082,6 +2101,36 @@ var DependencyLines_default = DependencyLines;
2082
2101
  // src/components/TaskList/TaskList.tsx
2083
2102
  var import_react12 = __toESM(require("react"));
2084
2103
 
2104
+ // src/utils/taskListReorder.ts
2105
+ function getVisibleReorderPosition(orderedTasks, visibleTasks, movedTaskId, originVisibleIndex, dropVisibleIndex) {
2106
+ const originOrderedIndex = orderedTasks.findIndex((task) => task.id === movedTaskId);
2107
+ if (originOrderedIndex === -1) {
2108
+ return null;
2109
+ }
2110
+ const reorderedWithoutMoved = orderedTasks.filter((task) => task.id !== movedTaskId);
2111
+ const visibleWithoutMoved = visibleTasks.filter((task) => task.id !== movedTaskId);
2112
+ const visibleInsertIndex = originVisibleIndex < dropVisibleIndex ? dropVisibleIndex - 1 : dropVisibleIndex;
2113
+ if (visibleWithoutMoved.length === 0) {
2114
+ return { originOrderedIndex, insertIndex: 0 };
2115
+ }
2116
+ if (visibleInsertIndex <= 0) {
2117
+ return {
2118
+ originOrderedIndex,
2119
+ insertIndex: reorderedWithoutMoved.findIndex((task) => task.id === visibleWithoutMoved[0].id)
2120
+ };
2121
+ }
2122
+ if (visibleInsertIndex >= visibleWithoutMoved.length) {
2123
+ return {
2124
+ originOrderedIndex,
2125
+ insertIndex: reorderedWithoutMoved.length
2126
+ };
2127
+ }
2128
+ return {
2129
+ originOrderedIndex,
2130
+ insertIndex: reorderedWithoutMoved.findIndex((task) => task.id === visibleWithoutMoved[visibleInsertIndex].id)
2131
+ };
2132
+ }
2133
+
2085
2134
  // src/components/ui/Popover.tsx
2086
2135
  var RadixPopover = __toESM(require("@radix-ui/react-popover"));
2087
2136
  var import_jsx_runtime7 = require("react/jsx-runtime");
@@ -2418,6 +2467,16 @@ var LINK_TYPE_LABELS = {
2418
2467
 
2419
2468
  // src/components/TaskList/TaskListRow.tsx
2420
2469
  var import_jsx_runtime12 = require("react/jsx-runtime");
2470
+ var DAY_MS = 24 * 60 * 60 * 1e3;
2471
+ var getInclusiveDurationDays = (startDate, endDate) => {
2472
+ const start = parseUTCDate(startDate);
2473
+ const end = parseUTCDate(endDate);
2474
+ return Math.max(1, Math.round((end.getTime() - start.getTime()) / DAY_MS) + 1);
2475
+ };
2476
+ var getEndDateFromDuration = (startDate, durationDays) => {
2477
+ const start = parseUTCDate(startDate);
2478
+ return new Date(start.getTime() + (durationDays - 1) * DAY_MS).toISOString().split("T")[0];
2479
+ };
2421
2480
  var TrashIcon = () => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2422
2481
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" }),
2423
2482
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M3 6h18" }),
@@ -2432,6 +2491,7 @@ var DragHandleIcon = () => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("svg",
2432
2491
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("circle", { cx: "2", cy: "12", r: "1.5" }),
2433
2492
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("circle", { cx: "8", cy: "12", r: "1.5" })
2434
2493
  ] });
2494
+ var ChevronRightIcon = () => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "m9 18 6-6-6-6" }) });
2435
2495
  var HierarchyButton = ({
2436
2496
  isChild,
2437
2497
  isParent,
@@ -2600,11 +2660,16 @@ var TaskListRow = import_react10.default.memo(
2600
2660
  const [editingName, setEditingName] = (0, import_react10.useState)(false);
2601
2661
  const [nameValue, setNameValue] = (0, import_react10.useState)("");
2602
2662
  const nameInputRef = (0, import_react10.useRef)(null);
2663
+ const [editingDuration, setEditingDuration] = (0, import_react10.useState)(false);
2664
+ const [durationValue, setDurationValue] = (0, import_react10.useState)(getInclusiveDurationDays(task.startDate, task.endDate));
2665
+ const durationInputRef = (0, import_react10.useRef)(null);
2603
2666
  const [editingProgress, setEditingProgress] = (0, import_react10.useState)(false);
2604
2667
  const [progressValue, setProgressValue] = (0, import_react10.useState)(0);
2605
2668
  const progressInputRef = (0, import_react10.useRef)(null);
2606
2669
  const [overflowOpen, setOverflowOpen] = (0, import_react10.useState)(false);
2607
- const confirmedRef = (0, import_react10.useRef)(false);
2670
+ const nameConfirmedRef = (0, import_react10.useRef)(false);
2671
+ const durationConfirmedRef = (0, import_react10.useRef)(false);
2672
+ const progressConfirmedRef = (0, import_react10.useRef)(false);
2608
2673
  const autoEditedForRef = (0, import_react10.useRef)(null);
2609
2674
  const editTriggerRef = (0, import_react10.useRef)("doubleclick");
2610
2675
  const [deletePending, setDeletePending] = (0, import_react10.useState)(false);
@@ -2659,7 +2724,7 @@ var TaskListRow = import_react10.default.memo(
2659
2724
  (0, import_react10.useEffect)(() => {
2660
2725
  if (editingTaskId === task.id && !disableTaskNameEditing && autoEditedForRef.current !== editingTaskId) {
2661
2726
  autoEditedForRef.current = editingTaskId;
2662
- confirmedRef.current = false;
2727
+ nameConfirmedRef.current = false;
2663
2728
  editTriggerRef.current = "autoedit";
2664
2729
  setNameValue(task.name);
2665
2730
  setEditingName(true);
@@ -2674,7 +2739,7 @@ var TaskListRow = import_react10.default.memo(
2674
2739
  const handleNameDoubleClick = (0, import_react10.useCallback)((e) => {
2675
2740
  if (disableTaskNameEditing) return;
2676
2741
  e.stopPropagation();
2677
- confirmedRef.current = false;
2742
+ nameConfirmedRef.current = false;
2678
2743
  editTriggerRef.current = "doubleclick";
2679
2744
  setNameValue(task.name);
2680
2745
  setEditingName(true);
@@ -2683,7 +2748,7 @@ var TaskListRow = import_react10.default.memo(
2683
2748
  if (editingProgress) return;
2684
2749
  if (!editingName && !disableTaskNameEditing && e.key === "F2") {
2685
2750
  e.preventDefault();
2686
- confirmedRef.current = false;
2751
+ nameConfirmedRef.current = false;
2687
2752
  editTriggerRef.current = "keypress";
2688
2753
  setNameValue(task.name);
2689
2754
  setEditingName(true);
@@ -2691,15 +2756,15 @@ var TaskListRow = import_react10.default.memo(
2691
2756
  }
2692
2757
  if (!editingName && !disableTaskNameEditing && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
2693
2758
  e.preventDefault();
2694
- confirmedRef.current = false;
2759
+ nameConfirmedRef.current = false;
2695
2760
  editTriggerRef.current = "keypress";
2696
2761
  setNameValue(e.key);
2697
2762
  setEditingName(true);
2698
2763
  }
2699
2764
  }, [editingName, disableTaskNameEditing, task.name]);
2700
2765
  const handleNameSave = (0, import_react10.useCallback)(() => {
2701
- if (confirmedRef.current) {
2702
- confirmedRef.current = false;
2766
+ if (nameConfirmedRef.current) {
2767
+ nameConfirmedRef.current = false;
2703
2768
  return;
2704
2769
  }
2705
2770
  if (nameValue.trim()) {
@@ -2712,7 +2777,7 @@ var TaskListRow = import_react10.default.memo(
2712
2777
  }, []);
2713
2778
  const handleNameKeyDown = (0, import_react10.useCallback)((e) => {
2714
2779
  if (e.key === "Enter") {
2715
- confirmedRef.current = true;
2780
+ nameConfirmedRef.current = true;
2716
2781
  if (nameValue.trim()) {
2717
2782
  onTaskChange?.({ ...task, name: nameValue.trim() });
2718
2783
  }
@@ -2721,15 +2786,54 @@ var TaskListRow = import_react10.default.memo(
2721
2786
  handleNameCancel();
2722
2787
  }
2723
2788
  }, [nameValue, task, onTaskChange, handleNameCancel]);
2789
+ const handleDurationClick = (0, import_react10.useCallback)((e) => {
2790
+ if (task.locked) return;
2791
+ e.stopPropagation();
2792
+ durationConfirmedRef.current = false;
2793
+ setDurationValue(getInclusiveDurationDays(task.startDate, task.endDate));
2794
+ setEditingDuration(true);
2795
+ }, [task.locked, task.startDate, task.endDate]);
2796
+ const applyDurationChange = (0, import_react10.useCallback)((nextDuration) => {
2797
+ const normalizedDuration = Math.max(1, Math.round(nextDuration) || 1);
2798
+ setDurationValue(normalizedDuration);
2799
+ }, []);
2800
+ const handleDurationSave = (0, import_react10.useCallback)(() => {
2801
+ if (durationConfirmedRef.current) {
2802
+ durationConfirmedRef.current = false;
2803
+ return;
2804
+ }
2805
+ const normalizedDuration = Math.max(1, Math.round(durationValue) || 1);
2806
+ onTaskChange?.({ ...task, endDate: getEndDateFromDuration(task.startDate, normalizedDuration) });
2807
+ setEditingDuration(false);
2808
+ }, [durationValue, task, onTaskChange]);
2809
+ const handleDurationCancel = (0, import_react10.useCallback)(() => {
2810
+ setDurationValue(getInclusiveDurationDays(task.startDate, task.endDate));
2811
+ setEditingDuration(false);
2812
+ }, [task.startDate, task.endDate]);
2813
+ const handleDurationAdjust = (0, import_react10.useCallback)((delta) => {
2814
+ applyDurationChange(durationValue + delta);
2815
+ }, [applyDurationChange, durationValue]);
2816
+ const handleDurationKeyDown = (0, import_react10.useCallback)((e) => {
2817
+ e.stopPropagation();
2818
+ if (e.key === "Enter") {
2819
+ durationConfirmedRef.current = true;
2820
+ const normalizedDuration = Math.max(1, Math.round(durationValue) || 1);
2821
+ onTaskChange?.({ ...task, endDate: getEndDateFromDuration(task.startDate, normalizedDuration) });
2822
+ setEditingDuration(false);
2823
+ } else if (e.key === "Escape") {
2824
+ handleDurationCancel();
2825
+ }
2826
+ }, [durationValue, task, onTaskChange, handleDurationCancel]);
2724
2827
  const handleProgressClick = (0, import_react10.useCallback)((e) => {
2828
+ if (task.locked) return;
2725
2829
  e.stopPropagation();
2726
- confirmedRef.current = false;
2830
+ progressConfirmedRef.current = false;
2727
2831
  setProgressValue(task.progress ?? 0);
2728
2832
  setEditingProgress(true);
2729
- }, [task.progress]);
2833
+ }, [task.progress, task.locked]);
2730
2834
  const handleProgressSave = (0, import_react10.useCallback)(() => {
2731
- if (confirmedRef.current) {
2732
- confirmedRef.current = false;
2835
+ if (progressConfirmedRef.current) {
2836
+ progressConfirmedRef.current = false;
2733
2837
  return;
2734
2838
  }
2735
2839
  const clampedValue = Math.max(0, Math.min(100, progressValue));
@@ -2739,10 +2843,13 @@ var TaskListRow = import_react10.default.memo(
2739
2843
  const handleProgressCancel = (0, import_react10.useCallback)(() => {
2740
2844
  setEditingProgress(false);
2741
2845
  }, []);
2846
+ const handleProgressAdjust = (0, import_react10.useCallback)((delta) => {
2847
+ setProgressValue((current) => Math.max(0, Math.min(100, current + delta)));
2848
+ }, []);
2742
2849
  const handleProgressKeyDown = (0, import_react10.useCallback)((e) => {
2743
2850
  e.stopPropagation();
2744
2851
  if (e.key === "Enter") {
2745
- confirmedRef.current = true;
2852
+ progressConfirmedRef.current = true;
2746
2853
  const clampedValue = Math.max(0, Math.min(100, progressValue));
2747
2854
  onTaskChange?.({ ...task, progress: clampedValue });
2748
2855
  setEditingProgress(false);
@@ -2756,6 +2863,15 @@ var TaskListRow = import_react10.default.memo(
2756
2863
  progressInputRef.current.select();
2757
2864
  }
2758
2865
  }, [editingProgress]);
2866
+ (0, import_react10.useEffect)(() => {
2867
+ setDurationValue(getInclusiveDurationDays(task.startDate, task.endDate));
2868
+ }, [task.startDate, task.endDate]);
2869
+ (0, import_react10.useEffect)(() => {
2870
+ if (editingDuration && durationInputRef.current) {
2871
+ durationInputRef.current.focus();
2872
+ durationInputRef.current.select();
2873
+ }
2874
+ }, [editingDuration]);
2759
2875
  const handleStartDateChange = (0, import_react10.useCallback)((newDateISO) => {
2760
2876
  if (!newDateISO) return;
2761
2877
  const origStart = parseUTCDate(task.startDate);
@@ -2763,7 +2879,11 @@ var TaskListRow = import_react10.default.memo(
2763
2879
  const durationMs = origEnd.getTime() - origStart.getTime();
2764
2880
  const newStart = /* @__PURE__ */ new Date(newDateISO + "T00:00:00Z");
2765
2881
  const newEnd = new Date(newStart.getTime() + durationMs);
2766
- onTaskChange?.({ ...task, startDate: newDateISO, endDate: newEnd.toISOString().split("T")[0] });
2882
+ const { startDate: normalizedStart, endDate: normalizedEnd } = normalizeTaskDates(
2883
+ newDateISO,
2884
+ newEnd.toISOString().split("T")[0]
2885
+ );
2886
+ onTaskChange?.({ ...task, startDate: normalizedStart, endDate: normalizedEnd });
2767
2887
  }, [task, onTaskChange]);
2768
2888
  const handleEndDateChange = (0, import_react10.useCallback)((newDateISO) => {
2769
2889
  if (!newDateISO) return;
@@ -2772,7 +2892,11 @@ var TaskListRow = import_react10.default.memo(
2772
2892
  const durationMs = origEnd.getTime() - origStart.getTime();
2773
2893
  const newEnd = /* @__PURE__ */ new Date(newDateISO + "T00:00:00Z");
2774
2894
  const newStart = new Date(newEnd.getTime() - durationMs);
2775
- onTaskChange?.({ ...task, startDate: newStart.toISOString().split("T")[0], endDate: newDateISO });
2895
+ const { startDate: normalizedStart, endDate: normalizedEnd } = normalizeTaskDates(
2896
+ newStart.toISOString().split("T")[0],
2897
+ newDateISO
2898
+ );
2899
+ onTaskChange?.({ ...task, startDate: normalizedStart, endDate: normalizedEnd });
2776
2900
  }, [task, onTaskChange]);
2777
2901
  const handleRowClickInternal = (0, import_react10.useCallback)(() => {
2778
2902
  onRowClick?.(task.id);
@@ -2820,7 +2944,7 @@ var TaskListRow = import_react10.default.memo(
2820
2944
  onChipSelect?.(null);
2821
2945
  }, [selectedChip, onRemoveDependency, onChipSelect]);
2822
2946
  const startDateISO = toISODate(task.startDate);
2823
- const endDateISO = toISODate(task.endDate);
2947
+ const endDateISO = editingDuration ? getEndDateFromDuration(task.startDate, durationValue) : toISODate(task.endDate);
2824
2948
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2825
2949
  "div",
2826
2950
  {
@@ -2846,21 +2970,12 @@ var TaskListRow = import_react10.default.memo(
2846
2970
  },
2847
2971
  tabIndex: isSelected ? 0 : -1,
2848
2972
  children: [
2849
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2973
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2850
2974
  "div",
2851
2975
  {
2852
2976
  className: "gantt-tl-cell gantt-tl-cell-number",
2853
2977
  onClick: handleNumberClick,
2854
- children: isParent ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2855
- "button",
2856
- {
2857
- type: "button",
2858
- className: "gantt-tl-collapse-btn",
2859
- onClick: handleToggleCollapse,
2860
- "aria-label": isCollapsed ? "\u0420\u0430\u0437\u0432\u0435\u0440\u043D\u0443\u0442\u044C" : "\u0421\u0432\u0435\u0440\u043D\u0443\u0442\u044C",
2861
- children: isCollapsed ? "+" : "-"
2862
- }
2863
- ) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
2978
+ children: [
2864
2979
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2865
2980
  "span",
2866
2981
  {
@@ -2876,10 +2991,20 @@ var TaskListRow = import_react10.default.memo(
2876
2991
  }
2877
2992
  ),
2878
2993
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "gantt-tl-num-label", children: rowIndex + 1 })
2879
- ] })
2994
+ ]
2880
2995
  }
2881
2996
  ),
2882
2997
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "gantt-tl-cell gantt-tl-cell-name", children: [
2998
+ isParent && !editingName && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2999
+ "button",
3000
+ {
3001
+ type: "button",
3002
+ className: `gantt-tl-collapse-btn ${isCollapsed ? "gantt-tl-collapse-btn-collapsed" : ""}`,
3003
+ onClick: handleToggleCollapse,
3004
+ "aria-label": isCollapsed ? "Expand children" : "Collapse children",
3005
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ChevronRightIcon, {})
3006
+ }
3007
+ ),
2883
3008
  editingName && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2884
3009
  Input,
2885
3010
  {
@@ -2889,7 +3014,7 @@ var TaskListRow = import_react10.default.memo(
2889
3014
  onChange: (e) => setNameValue(e.target.value),
2890
3015
  onBlur: handleNameSave,
2891
3016
  onKeyDown: handleNameKeyDown,
2892
- className: "gantt-tl-name-input",
3017
+ className: ["gantt-tl-name-input", isChild ? "gantt-tl-name-input-child" : ""].filter(Boolean).join(" "),
2893
3018
  onClick: (e) => e.stopPropagation()
2894
3019
  }
2895
3020
  ),
@@ -2897,7 +3022,13 @@ var TaskListRow = import_react10.default.memo(
2897
3022
  "button",
2898
3023
  {
2899
3024
  type: "button",
2900
- className: `gantt-tl-name-trigger ${disableTaskNameEditing ? "gantt-tl-name-locked" : ""}`,
3025
+ className: [
3026
+ "gantt-tl-name-trigger",
3027
+ disableTaskNameEditing ? "gantt-tl-name-locked" : "",
3028
+ isParent ? "gantt-tl-name-trigger-parent" : "",
3029
+ isChild ? "gantt-tl-name-trigger-child" : ""
3030
+ ].filter(Boolean).join(" "),
3031
+ title: task.name,
2901
3032
  onClick: handleNameClick,
2902
3033
  onDoubleClick: handleNameDoubleClick,
2903
3034
  style: editingName ? { visibility: "hidden", pointerEvents: "none" } : void 0,
@@ -2986,22 +3117,91 @@ var TaskListRow = import_react10.default.memo(
2986
3117
  disabled: task.locked
2987
3118
  }
2988
3119
  ) }),
3120
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "gantt-tl-cell gantt-tl-cell-duration", onClick: handleDurationClick, children: [
3121
+ editingDuration && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "gantt-tl-number-editor", onClick: (e) => e.stopPropagation(), children: [
3122
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3123
+ Input,
3124
+ {
3125
+ ref: durationInputRef,
3126
+ type: "number",
3127
+ min: 1,
3128
+ step: 1,
3129
+ value: durationValue,
3130
+ onChange: (e) => applyDurationChange(parseInt(e.target.value, 10) || 1),
3131
+ onBlur: handleDurationSave,
3132
+ onKeyDown: handleDurationKeyDown,
3133
+ className: "gantt-tl-number-input"
3134
+ }
3135
+ ),
3136
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "gantt-tl-number-steppers", "aria-hidden": "true", children: [
3137
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3138
+ "button",
3139
+ {
3140
+ type: "button",
3141
+ className: "gantt-tl-number-stepper",
3142
+ tabIndex: -1,
3143
+ onMouseDown: (e) => e.preventDefault(),
3144
+ onClick: () => handleDurationAdjust(1),
3145
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "10", height: "10", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "m18 15-6-6-6 6" }) })
3146
+ }
3147
+ ),
3148
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3149
+ "button",
3150
+ {
3151
+ type: "button",
3152
+ className: "gantt-tl-number-stepper",
3153
+ tabIndex: -1,
3154
+ onMouseDown: (e) => e.preventDefault(),
3155
+ onClick: () => handleDurationAdjust(-1),
3156
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "10", height: "10", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "m6 9 6 6 6-6" }) })
3157
+ }
3158
+ )
3159
+ ] })
3160
+ ] }),
3161
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: editingDuration ? { visibility: "hidden", pointerEvents: "none" } : void 0, children: getInclusiveDurationDays(task.startDate, task.endDate) })
3162
+ ] }),
2989
3163
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "gantt-tl-cell gantt-tl-cell-progress", onClick: handleProgressClick, children: [
2990
- editingProgress && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2991
- Input,
2992
- {
2993
- ref: progressInputRef,
2994
- type: "number",
2995
- min: 0,
2996
- max: 100,
2997
- value: progressValue,
2998
- onChange: (e) => setProgressValue(parseInt(e.target.value) || 0),
2999
- onBlur: handleProgressSave,
3000
- onKeyDown: handleProgressKeyDown,
3001
- className: "gantt-tl-progress-input",
3002
- onClick: (e) => e.stopPropagation()
3003
- }
3004
- ),
3164
+ editingProgress && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "gantt-tl-number-editor", onClick: (e) => e.stopPropagation(), children: [
3165
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3166
+ Input,
3167
+ {
3168
+ ref: progressInputRef,
3169
+ type: "number",
3170
+ min: 0,
3171
+ max: 100,
3172
+ step: 1,
3173
+ value: progressValue,
3174
+ onChange: (e) => setProgressValue(parseInt(e.target.value, 10) || 0),
3175
+ onBlur: handleProgressSave,
3176
+ onKeyDown: handleProgressKeyDown,
3177
+ className: "gantt-tl-number-input"
3178
+ }
3179
+ ),
3180
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "gantt-tl-number-steppers", "aria-hidden": "true", children: [
3181
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3182
+ "button",
3183
+ {
3184
+ type: "button",
3185
+ className: "gantt-tl-number-stepper",
3186
+ tabIndex: -1,
3187
+ onMouseDown: (e) => e.preventDefault(),
3188
+ onClick: () => handleProgressAdjust(1),
3189
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "10", height: "10", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "m18 15-6-6-6 6" }) })
3190
+ }
3191
+ ),
3192
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3193
+ "button",
3194
+ {
3195
+ type: "button",
3196
+ className: "gantt-tl-number-stepper",
3197
+ tabIndex: -1,
3198
+ onMouseDown: (e) => e.preventDefault(),
3199
+ onClick: () => handleProgressAdjust(-1),
3200
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "10", height: "10", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "m6 9 6 6 6-6" }) })
3201
+ }
3202
+ )
3203
+ ] })
3204
+ ] }),
3005
3205
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: editingProgress ? { visibility: "hidden", pointerEvents: "none" } : void 0, children: task.progress ? `${Math.round(task.progress)}%` : "0%" })
3006
3206
  ] }),
3007
3207
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
@@ -3158,15 +3358,17 @@ var NewTaskRow = ({ rowHeight, onConfirm, onCancel }) => {
3158
3358
  // src/components/TaskList/TaskList.tsx
3159
3359
  var import_jsx_runtime14 = require("react/jsx-runtime");
3160
3360
  var LINK_TYPE_ORDER = ["FS", "SS", "FF", "SF"];
3361
+ var MIN_TASK_LIST_WIDTH = 640;
3161
3362
  var TaskList = ({
3162
3363
  tasks,
3163
3364
  rowHeight,
3164
3365
  headerHeight,
3165
- taskListWidth = 542,
3366
+ taskListWidth = 640,
3166
3367
  onTaskChange,
3167
3368
  selectedTaskId,
3168
3369
  onTaskSelect,
3169
3370
  show = true,
3371
+ hasRightShadow = false,
3170
3372
  disableTaskNameEditing = false,
3171
3373
  disableDependencyEditing = false,
3172
3374
  onScrollToTask,
@@ -3305,11 +3507,13 @@ var TaskList = ({
3305
3507
  const [draggingIndex, setDraggingIndex] = (0, import_react12.useState)(null);
3306
3508
  const [dragOverIndex, setDragOverIndex] = (0, import_react12.useState)(null);
3307
3509
  const dragOriginIndexRef = (0, import_react12.useRef)(null);
3510
+ const dragTaskIdRef = (0, import_react12.useRef)(null);
3308
3511
  const handleDragStart = (0, import_react12.useCallback)((index, e) => {
3309
3512
  e.dataTransfer.effectAllowed = "move";
3310
3513
  setDraggingIndex(index);
3311
3514
  dragOriginIndexRef.current = index;
3312
- }, []);
3515
+ dragTaskIdRef.current = visibleTasks[index]?.id ?? null;
3516
+ }, [visibleTasks]);
3313
3517
  const handleDragOver = (0, import_react12.useCallback)((index, e) => {
3314
3518
  e.preventDefault();
3315
3519
  e.dataTransfer.dropEffect = "move";
@@ -3317,16 +3521,32 @@ var TaskList = ({
3317
3521
  }, []);
3318
3522
  const handleDrop = (0, import_react12.useCallback)((dropIndex, e) => {
3319
3523
  e.preventDefault();
3320
- const originIndex = dragOriginIndexRef.current;
3321
- if (originIndex === null || originIndex === dropIndex) {
3524
+ const originVisibleIndex = dragOriginIndexRef.current;
3525
+ const movedTaskId = dragTaskIdRef.current;
3526
+ if (originVisibleIndex === null || movedTaskId === null || originVisibleIndex === dropIndex) {
3527
+ setDraggingIndex(null);
3528
+ setDragOverIndex(null);
3529
+ dragOriginIndexRef.current = null;
3530
+ dragTaskIdRef.current = null;
3531
+ return;
3532
+ }
3533
+ const reorderPosition = getVisibleReorderPosition(
3534
+ orderedTasks,
3535
+ visibleTasks,
3536
+ movedTaskId,
3537
+ originVisibleIndex,
3538
+ dropIndex
3539
+ );
3540
+ if (!reorderPosition) {
3322
3541
  setDraggingIndex(null);
3323
3542
  setDragOverIndex(null);
3324
3543
  dragOriginIndexRef.current = null;
3544
+ dragTaskIdRef.current = null;
3325
3545
  return;
3326
3546
  }
3547
+ const { originOrderedIndex, insertIndex } = reorderPosition;
3327
3548
  const reordered = [...orderedTasks];
3328
- const [moved] = reordered.splice(originIndex, 1);
3329
- const insertIndex = dropIndex === visibleTasks.length ? visibleTasks.length - 1 : originIndex < dropIndex ? dropIndex - 1 : dropIndex;
3549
+ const [moved] = reordered.splice(originOrderedIndex, 1);
3330
3550
  const isChild = !!moved.parentId;
3331
3551
  const isParent = tasks.some((t) => t.parentId === moved.id);
3332
3552
  const taskType = isParent ? "PARENT" : isChild ? "CHILD" : "ROOT";
@@ -3336,10 +3556,11 @@ var TaskList = ({
3336
3556
  name: moved.name,
3337
3557
  type: taskType,
3338
3558
  parentId: moved.parentId,
3339
- originIndex,
3559
+ originIndex: originOrderedIndex,
3560
+ originVisibleIndex,
3340
3561
  dropIndex,
3341
3562
  insertIndex,
3342
- direction: originIndex < dropIndex ? "DOWN" : "UP"
3563
+ direction: originVisibleIndex < dropIndex ? "DOWN" : "UP"
3343
3564
  });
3344
3565
  console.log("[TASKS ARRAY LENGTH]", orderedTasks.length);
3345
3566
  let inferredParentId;
@@ -3426,11 +3647,13 @@ var TaskList = ({
3426
3647
  setDraggingIndex(null);
3427
3648
  setDragOverIndex(null);
3428
3649
  dragOriginIndexRef.current = null;
3429
- }, [orderedTasks, visibleTasks.length, onReorder, onTaskSelect]);
3650
+ dragTaskIdRef.current = null;
3651
+ }, [orderedTasks, visibleTasks, onReorder, onTaskSelect]);
3430
3652
  const handleDragEnd = (0, import_react12.useCallback)(() => {
3431
3653
  setDraggingIndex(null);
3432
3654
  setDragOverIndex(null);
3433
3655
  dragOriginIndexRef.current = null;
3656
+ dragTaskIdRef.current = null;
3434
3657
  }, []);
3435
3658
  const handleConfirmNewTask = (0, import_react12.useCallback)((name) => {
3436
3659
  const now = /* @__PURE__ */ new Date();
@@ -3454,18 +3677,20 @@ var TaskList = ({
3454
3677
  setIsCreating(false);
3455
3678
  }, [onAdd]);
3456
3679
  const handleCancelNewTask = (0, import_react12.useCallback)(() => setIsCreating(false), []);
3680
+ const effectiveTaskListWidth = Math.max(taskListWidth, MIN_TASK_LIST_WIDTH);
3457
3681
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3458
3682
  "div",
3459
3683
  {
3460
3684
  ref: overlayRef,
3461
- className: `gantt-tl-overlay${show ? "" : " gantt-tl-hidden"}`,
3462
- style: { width: `${taskListWidth}px` },
3685
+ className: `gantt-tl-overlay${show ? "" : " gantt-tl-hidden"}${hasRightShadow ? " gantt-tl-overlay-shadowed" : ""}`,
3686
+ style: { width: `${effectiveTaskListWidth}px`, minWidth: `${MIN_TASK_LIST_WIDTH}px` },
3463
3687
  children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "gantt-tl-table", children: [
3464
3688
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "gantt-tl-header", style: { height: `${headerHeight + 0.5}px` }, children: [
3465
3689
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "gantt-tl-headerCell gantt-tl-cell-number", children: "\u2116" }),
3466
3690
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "gantt-tl-headerCell gantt-tl-cell-name", children: "\u0418\u043C\u044F" }),
3467
3691
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "gantt-tl-headerCell gantt-tl-cell-date", children: "\u041D\u0430\u0447\u0430\u043B\u043E" }),
3468
3692
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "gantt-tl-headerCell gantt-tl-cell-date", children: "\u041E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u0435" }),
3693
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "gantt-tl-headerCell gantt-tl-cell-duration", children: "\u0414\u043D." }),
3469
3694
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "gantt-tl-headerCell gantt-tl-cell-progress", children: "%" }),
3470
3695
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "gantt-tl-headerCell gantt-tl-cell-deps", style: { position: "relative" }, children: [
3471
3696
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Popover, { open: typeMenuOpen, onOpenChange: setTypeMenuOpen, children: [
@@ -3591,7 +3816,7 @@ var GanttChart = (0, import_react13.forwardRef)(({
3591
3816
  disableConstraints,
3592
3817
  onCascade,
3593
3818
  showTaskList = false,
3594
- taskListWidth = 520,
3819
+ taskListWidth = 660,
3595
3820
  disableTaskNameEditing = false,
3596
3821
  disableDependencyEditing = false,
3597
3822
  highlightExpiredTasks = false,
@@ -3603,6 +3828,7 @@ var GanttChart = (0, import_react13.forwardRef)(({
3603
3828
  }, ref) => {
3604
3829
  const scrollContainerRef = (0, import_react13.useRef)(null);
3605
3830
  const [selectedTaskId, setSelectedTaskId] = (0, import_react13.useState)(null);
3831
+ const [taskListHasRightShadow, setTaskListHasRightShadow] = (0, import_react13.useState)(false);
3606
3832
  const [selectedChip, setSelectedChip] = (0, import_react13.useState)(null);
3607
3833
  const [collapsedParentIds, setCollapsedParentIds] = (0, import_react13.useState)(/* @__PURE__ */ new Set());
3608
3834
  const [editingTaskId, setEditingTaskId] = (0, import_react13.useState)(null);
@@ -3649,6 +3875,18 @@ var GanttChart = (0, import_react13.forwardRef)(({
3649
3875
  const scrollLeft = Math.round(todayOffset - containerWidth / 2 + dayWidth / 2);
3650
3876
  container.scrollLeft = Math.max(0, scrollLeft);
3651
3877
  }, []);
3878
+ (0, import_react13.useEffect)(() => {
3879
+ const container = scrollContainerRef.current;
3880
+ if (!container) return;
3881
+ const updateShadow = () => {
3882
+ setTaskListHasRightShadow(container.scrollLeft > 0);
3883
+ };
3884
+ updateShadow();
3885
+ container.addEventListener("scroll", updateShadow, { passive: true });
3886
+ return () => {
3887
+ container.removeEventListener("scroll", updateShadow);
3888
+ };
3889
+ }, []);
3652
3890
  const scrollToToday = (0, import_react13.useCallback)(() => {
3653
3891
  const container = scrollContainerRef.current;
3654
3892
  if (!container || dateRange.length === 0) return;
@@ -4052,6 +4290,7 @@ var GanttChart = (0, import_react13.forwardRef)(({
4052
4290
  selectedTaskId: selectedTaskId ?? void 0,
4053
4291
  onTaskSelect: handleTaskSelect,
4054
4292
  show: showTaskList,
4293
+ hasRightShadow: taskListHasRightShadow,
4055
4294
  disableTaskNameEditing,
4056
4295
  disableDependencyEditing,
4057
4296
  onScrollToTask: scrollToTask,
@@ -4215,10 +4454,12 @@ Button.displayName = "Button";
4215
4454
  getMultiMonthDays,
4216
4455
  getSuccessorChain,
4217
4456
  getTransitiveCascadeChain,
4457
+ getVisibleReorderPosition,
4218
4458
  isTaskParent,
4219
4459
  isToday,
4220
4460
  isWeekend,
4221
4461
  normalizeHierarchyTasks,
4462
+ normalizeTaskDates,
4222
4463
  parseUTCDate,
4223
4464
  pixelsToDate,
4224
4465
  recalculateIncomingLags,