gantt-lib 0.72.2 → 0.73.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.mjs CHANGED
@@ -1136,45 +1136,9 @@ function universalCascade(movedTask, newStart, newEnd, allTasks, businessDays =
1136
1136
  }
1137
1137
  return Array.from(resultMap.values());
1138
1138
  }
1139
- function reflowTasksOnModeSwitch(sourceTasks, toBusinessDays, weekendPredicate) {
1140
- const fromBusinessDays = !toBusinessDays;
1141
- let tasks = sourceTasks.map((t) => ({ ...t }));
1142
- const toISO = (d) => d.toISOString().split("T")[0];
1143
- for (const task of tasks) {
1144
- if (isTaskParent(task.id, tasks)) continue;
1145
- const start = normalizeUTCDate(/* @__PURE__ */ new Date(`${task.startDate}T00:00:00.000Z`));
1146
- const duration = getTaskDuration(task.startDate, task.endDate, fromBusinessDays, weekendPredicate);
1147
- let range;
1148
- if (toBusinessDays) {
1149
- const alignedStart = alignToWorkingDay(start, 1, weekendPredicate);
1150
- range = buildTaskRangeFromStart(alignedStart, duration, true, weekendPredicate);
1151
- } else {
1152
- range = buildTaskRangeFromStart(start, duration, false);
1153
- }
1154
- task.startDate = toISO(range.start);
1155
- task.endDate = toISO(range.end);
1156
- }
1157
- for (const task of tasks) {
1158
- if (!isTaskParent(task.id, tasks)) continue;
1159
- const { startDate, endDate } = computeParentDates(task.id, tasks);
1160
- task.startDate = toISO(startDate);
1161
- task.endDate = toISO(endDate);
1162
- }
1163
- if (toBusinessDays) {
1164
- const rootSeeds = tasks.filter(
1165
- (t) => !t.parentId && (!t.dependencies || t.dependencies.length === 0)
1166
- );
1167
- for (const seed of rootSeeds) {
1168
- const current = tasks.find((t) => t.id === seed.id);
1169
- const start = /* @__PURE__ */ new Date(`${current.startDate}T00:00:00.000Z`);
1170
- const end = /* @__PURE__ */ new Date(`${current.endDate}T00:00:00.000Z`);
1171
- const cascaded = universalCascade(current, start, end, tasks, toBusinessDays, weekendPredicate);
1172
- const updates = new Map(cascaded.map((t) => [t.id, t]));
1173
- tasks = tasks.map((t) => updates.get(t.id) ?? t);
1174
- }
1175
- }
1176
- return tasks;
1177
- }
1139
+
1140
+ // src/core/scheduling/modeSwitch.ts
1141
+ init_dateMath();
1178
1142
 
1179
1143
  // src/core/scheduling/execute.ts
1180
1144
  init_dateMath();
@@ -1481,6 +1445,44 @@ function recalculateProjectSchedule(snapshot, options) {
1481
1445
  return createChangedResult(snapshot, Array.from(workingMap.values()));
1482
1446
  }
1483
1447
 
1448
+ // src/core/scheduling/modeSwitch.ts
1449
+ function reflowTasksOnModeSwitch(sourceTasks, toBusinessDays, weekendPredicate) {
1450
+ const fromBusinessDays = !toBusinessDays;
1451
+ let tasks = sourceTasks.map((task) => ({
1452
+ ...task,
1453
+ dependencies: task.dependencies?.map((dependency) => ({ ...dependency }))
1454
+ }));
1455
+ const toISO = (date) => date.toISOString().split("T")[0];
1456
+ for (const task of tasks) {
1457
+ if (isTaskParent(task.id, tasks)) continue;
1458
+ const start = normalizeUTCDate(/* @__PURE__ */ new Date(`${task.startDate}T00:00:00.000Z`));
1459
+ const duration = getTaskDuration(task.startDate, task.endDate, fromBusinessDays, weekendPredicate);
1460
+ const range = toBusinessDays ? buildTaskRangeFromStart(
1461
+ alignToWorkingDay(start, 1, weekendPredicate),
1462
+ duration,
1463
+ true,
1464
+ weekendPredicate
1465
+ ) : buildTaskRangeFromStart(start, duration, false);
1466
+ task.startDate = toISO(range.start);
1467
+ task.endDate = toISO(range.end);
1468
+ }
1469
+ for (const task of tasks) {
1470
+ if (!isTaskParent(task.id, tasks)) continue;
1471
+ const { startDate, endDate } = computeParentDates(task.id, tasks);
1472
+ task.startDate = toISO(startDate);
1473
+ task.endDate = toISO(endDate);
1474
+ }
1475
+ const rescheduled = recalculateProjectSchedule(tasks, {
1476
+ businessDays: toBusinessDays,
1477
+ weekendPredicate
1478
+ });
1479
+ if (rescheduled.changedTasks.length === 0) {
1480
+ return tasks;
1481
+ }
1482
+ const updates = new Map(rescheduled.changedTasks.map((task) => [task.id, task]));
1483
+ return tasks.map((task) => updates.get(task.id) ?? task);
1484
+ }
1485
+
1484
1486
  // src/core/scheduling/validation.ts
1485
1487
  function buildAdjacencyList(tasks) {
1486
1488
  const graph = /* @__PURE__ */ new Map();
@@ -4151,6 +4153,26 @@ var CopyIcon = () => /* @__PURE__ */ jsxs9(
4151
4153
  ]
4152
4154
  }
4153
4155
  );
4156
+ var UngroupIcon = () => /* @__PURE__ */ jsxs9(
4157
+ "svg",
4158
+ {
4159
+ xmlns: "http://www.w3.org/2000/svg",
4160
+ width: "16",
4161
+ height: "16",
4162
+ viewBox: "0 0 24 24",
4163
+ fill: "none",
4164
+ stroke: "currentColor",
4165
+ strokeWidth: "2",
4166
+ strokeLinecap: "round",
4167
+ strokeLinejoin: "round",
4168
+ children: [
4169
+ /* @__PURE__ */ jsx12("path", { d: "M3 12h10" }),
4170
+ /* @__PURE__ */ jsx12("path", { d: "m6 9-3 3 3 3" }),
4171
+ /* @__PURE__ */ jsx12("path", { d: "M13 6h8" }),
4172
+ /* @__PURE__ */ jsx12("path", { d: "M13 18h8" })
4173
+ ]
4174
+ }
4175
+ );
4154
4176
  var TASK_COLOR_PALETTE = [
4155
4177
  // { label: "Палисандр", value: "#A61E4D" },
4156
4178
  // { label: "Киноварь", value: "#E8590C" },
@@ -4583,6 +4605,7 @@ var TaskListRow = React9.memo(
4583
4605
  onToggleCollapse,
4584
4606
  onPromoteTask,
4585
4607
  onDemoteTask,
4608
+ onUngroupTask,
4586
4609
  onDuplicateTask,
4587
4610
  canDemoteTask = true,
4588
4611
  isLastChild = true,
@@ -4594,7 +4617,8 @@ var TaskListRow = React9.memo(
4594
4617
  businessDays,
4595
4618
  isFilterMatch = false,
4596
4619
  isFilterHideMode = false,
4597
- resolvedColumns
4620
+ resolvedColumns,
4621
+ taskListMenuCommands = []
4598
4622
  }) => {
4599
4623
  const [editingColumnId, setEditingColumnId] = useState4(null);
4600
4624
  const editingName = editingColumnId === "name";
@@ -4628,6 +4652,7 @@ var TaskListRow = React9.memo(
4628
4652
  [task.id, allTasks]
4629
4653
  );
4630
4654
  const isChild = task.parentId !== void 0;
4655
+ const isMilestoneRow = normalizedTask.type === "milestone";
4631
4656
  const weekendPredicate = useMemo7(
4632
4657
  () => createCustomDayPredicate({ customDays, isWeekend: isWeekend3 }),
4633
4658
  [customDays, isWeekend3]
@@ -5150,6 +5175,38 @@ var TaskListRow = React9.memo(
5150
5175
  },
5151
5176
  [allTasks, isParent, onTasksChange, task]
5152
5177
  );
5178
+ const handleUngroup = useCallback4(
5179
+ (e) => {
5180
+ e.stopPropagation();
5181
+ setContextMenuOpen(false);
5182
+ onUngroupTask?.(task.id);
5183
+ },
5184
+ [onUngroupTask, task.id]
5185
+ );
5186
+ const visibleCustomMenuCommands = useMemo7(
5187
+ () => taskListMenuCommands.filter(
5188
+ (command) => {
5189
+ const scope = command.scope ?? "all";
5190
+ if (scope === "group" && !isParent) return false;
5191
+ if (scope === "linear" && (isParent || isMilestoneRow)) return false;
5192
+ if (scope === "milestone" && !isMilestoneRow) return false;
5193
+ return command.isVisible?.(task) ?? true;
5194
+ }
5195
+ ),
5196
+ [taskListMenuCommands, task, isParent, isMilestoneRow]
5197
+ );
5198
+ const hasContextMenu = visibleCustomMenuCommands.length > 0 || !!onDuplicateTask || !!onDelete || !!onTasksChange || isParent && !!onUngroupTask;
5199
+ const handleCustomMenuCommandClick = useCallback4(
5200
+ (command) => (e) => {
5201
+ e.stopPropagation();
5202
+ if (command.closeOnSelect !== false) {
5203
+ setContextMenuOpen(false);
5204
+ setColorMenuOpen(false);
5205
+ }
5206
+ command.onSelect(task);
5207
+ },
5208
+ [task]
5209
+ );
5153
5210
  const handleAddClick = useCallback4(
5154
5211
  (e) => {
5155
5212
  e.stopPropagation();
@@ -5576,7 +5633,7 @@ var TaskListRow = React9.memo(
5576
5633
  "aria-hidden": "true"
5577
5634
  }
5578
5635
  ),
5579
- !editingName && (onInsertAfter || onDelete || onPromoteTask || onDemoteTask || onDuplicateTask || onTasksChange) && /* @__PURE__ */ jsxs9("div", { className: "gantt-tl-name-actions", children: [
5636
+ !editingName && (onInsertAfter || onDelete || onPromoteTask || onDemoteTask || onUngroupTask || onDuplicateTask || onTasksChange || hasContextMenu) && /* @__PURE__ */ jsxs9("div", { className: "gantt-tl-name-actions", children: [
5580
5637
  onInsertAfter && /* @__PURE__ */ jsx12(
5581
5638
  "button",
5582
5639
  {
@@ -5622,7 +5679,7 @@ var TaskListRow = React9.memo(
5622
5679
  onDemote: onDemoteTask ? handleDemote : void 0
5623
5680
  }
5624
5681
  ),
5625
- (onDuplicateTask || onDelete || onTasksChange) && /* @__PURE__ */ jsxs9(Popover, { open: contextMenuOpen, onOpenChange: (open) => {
5682
+ hasContextMenu && /* @__PURE__ */ jsxs9(Popover, { open: contextMenuOpen, onOpenChange: (open) => {
5626
5683
  setContextMenuOpen(open);
5627
5684
  if (!open) setColorMenuOpen(false);
5628
5685
  }, children: [
@@ -5715,6 +5772,32 @@ var TaskListRow = React9.memo(
5715
5772
  ]
5716
5773
  }
5717
5774
  ),
5775
+ visibleCustomMenuCommands.map((command) => /* @__PURE__ */ jsxs9(
5776
+ "button",
5777
+ {
5778
+ type: "button",
5779
+ className: `gantt-tl-context-menu-item${command.danger ? " gantt-tl-context-menu-item-danger" : ""}`,
5780
+ onClick: handleCustomMenuCommandClick(command),
5781
+ disabled: command.isDisabled?.(task) ?? false,
5782
+ children: [
5783
+ command.icon,
5784
+ command.label
5785
+ ]
5786
+ },
5787
+ command.id
5788
+ )),
5789
+ isParent && onUngroupTask && /* @__PURE__ */ jsxs9(
5790
+ "button",
5791
+ {
5792
+ type: "button",
5793
+ className: "gantt-tl-context-menu-item",
5794
+ onClick: handleUngroup,
5795
+ children: [
5796
+ /* @__PURE__ */ jsx12(UngroupIcon, {}),
5797
+ "\u0420\u0430\u0437\u0433\u0440\u0443\u043F\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C"
5798
+ ]
5799
+ }
5800
+ ),
5718
5801
  onDelete && /* @__PURE__ */ jsxs9(
5719
5802
  "button",
5720
5803
  {
@@ -6410,6 +6493,7 @@ var TaskList = ({
6410
6493
  onToggleCollapse: externalOnToggleCollapse,
6411
6494
  onPromoteTask,
6412
6495
  onDemoteTask,
6496
+ onUngroupTask,
6413
6497
  customDays,
6414
6498
  isWeekend: isWeekend3,
6415
6499
  businessDays,
@@ -6417,7 +6501,8 @@ var TaskList = ({
6417
6501
  filterMode = "highlight",
6418
6502
  filteredTaskIds = /* @__PURE__ */ new Set(),
6419
6503
  isFilterActive = false,
6420
- additionalColumns
6504
+ additionalColumns,
6505
+ taskListMenuCommands
6421
6506
  }) => {
6422
6507
  const [internalCollapsedParentIds, setInternalCollapsedParentIds] = useState6(/* @__PURE__ */ new Set());
6423
6508
  const collapsedParentIds = externalCollapsedParentIds ?? internalCollapsedParentIds;
@@ -7081,6 +7166,7 @@ var TaskList = ({
7081
7166
  onToggleCollapse: handleToggleCollapse,
7082
7167
  onPromoteTask,
7083
7168
  onDemoteTask: onDemoteTask ? handleDemoteWrapper : void 0,
7169
+ onUngroupTask,
7084
7170
  onDuplicateTask: onReorder ? handleDuplicateTask : void 0,
7085
7171
  canDemoteTask,
7086
7172
  isLastChild: lastChildIds.has(task.id),
@@ -7092,7 +7178,8 @@ var TaskList = ({
7092
7178
  businessDays,
7093
7179
  isFilterMatch: filterMode === "highlight" ? highlightedTaskIds.has(task.id) : false,
7094
7180
  isFilterHideMode: filterMode === "hide" && isFilterActive,
7095
- resolvedColumns
7181
+ resolvedColumns,
7182
+ taskListMenuCommands
7096
7183
  }
7097
7184
  ),
7098
7185
  pendingInsertDisplayTaskId === task.id && /* @__PURE__ */ jsx14(
@@ -7512,6 +7599,7 @@ function GanttChartInner(props, ref) {
7512
7599
  onReorder,
7513
7600
  onPromoteTask,
7514
7601
  onDemoteTask,
7602
+ onUngroupTask,
7515
7603
  enableAddTask = true,
7516
7604
  viewMode = "day",
7517
7605
  customDays,
@@ -7524,7 +7612,8 @@ function GanttChartInner(props, ref) {
7524
7612
  highlightedTaskIds,
7525
7613
  disableTaskDrag = false,
7526
7614
  showChart = true,
7527
- additionalColumns
7615
+ additionalColumns,
7616
+ taskListMenuCommands
7528
7617
  } = props;
7529
7618
  const containerRef = useRef7(null);
7530
7619
  const scrollContainerRef = useRef7(null);
@@ -7940,6 +8029,33 @@ function GanttChartInner(props, ref) {
7940
8029
  };
7941
8030
  onTasksChange?.([updatedDemotedTask]);
7942
8031
  }, [tasks, onTasksChange, onDemoteTask]);
8032
+ const handleUngroupTask = useCallback6((taskId) => {
8033
+ if (onUngroupTask) {
8034
+ onUngroupTask(taskId);
8035
+ return;
8036
+ }
8037
+ const parentTask = tasks.find((task) => task.id === taskId);
8038
+ if (!parentTask) return;
8039
+ const hasDirectChildren = tasks.some((task) => task.parentId === taskId);
8040
+ if (!hasDirectChildren) return;
8041
+ const changedTasks = [];
8042
+ for (const task of tasks) {
8043
+ if (task.id === taskId) continue;
8044
+ const nextParentId = task.parentId === taskId ? parentTask.parentId : task.parentId;
8045
+ const nextDependencies = task.dependencies?.filter((dep) => dep.taskId !== taskId);
8046
+ if (nextParentId !== task.parentId || nextDependencies?.length !== task.dependencies?.length) {
8047
+ changedTasks.push({
8048
+ ...task,
8049
+ parentId: nextParentId,
8050
+ dependencies: nextDependencies
8051
+ });
8052
+ }
8053
+ }
8054
+ if (changedTasks.length > 0) {
8055
+ onTasksChange?.(changedTasks);
8056
+ }
8057
+ onDelete?.(taskId);
8058
+ }, [tasks, onTasksChange, onDelete, onUngroupTask]);
7943
8059
  const panStateRef = useRef7(null);
7944
8060
  const handlePanStart = useCallback6((e) => {
7945
8061
  if (e.button !== 0) return;
@@ -8018,6 +8134,7 @@ function GanttChartInner(props, ref) {
8018
8134
  onToggleCollapse: handleToggleCollapse,
8019
8135
  onPromoteTask: onPromoteTask ?? handlePromoteTask,
8020
8136
  onDemoteTask: onDemoteTask ?? handleDemoteTask,
8137
+ onUngroupTask: onUngroupTask ?? handleUngroupTask,
8021
8138
  highlightedTaskIds: taskListHighlightedTaskIds,
8022
8139
  customDays,
8023
8140
  isWeekend: isWeekend3,
@@ -8025,7 +8142,8 @@ function GanttChartInner(props, ref) {
8025
8142
  filterMode,
8026
8143
  filteredTaskIds: matchedTaskIds,
8027
8144
  isFilterActive: !!taskFilter,
8028
- additionalColumns
8145
+ additionalColumns,
8146
+ taskListMenuCommands
8029
8147
  }
8030
8148
  ),
8031
8149
  /* @__PURE__ */ jsxs12(