gantt-lib 0.145.1 → 0.146.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2233,8 +2233,8 @@ function recalculateProjectSchedule(snapshot, options) {
2233
2233
  }
2234
2234
  }
2235
2235
  const queue = snapshot.filter((task) => (indegree.get(task.id) ?? 0) === 0).map((task) => task.id);
2236
- while (queue.length > 0) {
2237
- const currentId = queue.shift();
2236
+ for (let queueIndex = 0; queueIndex < queue.length; queueIndex++) {
2237
+ const currentId = queue[queueIndex];
2238
2238
  for (const successorId of successorIdsByTask.get(currentId) ?? []) {
2239
2239
  const nextIndegree = (indegree.get(successorId) ?? 0) - 1;
2240
2240
  indegree.set(successorId, nextIndegree);
@@ -2285,7 +2285,13 @@ function recalculateProjectSchedule(snapshot, options) {
2285
2285
  queue.push(successorId);
2286
2286
  }
2287
2287
  }
2288
- const parentsByDepth = snapshot.filter((task) => isTaskParent(task.id, snapshot)).map((task) => {
2288
+ const childrenByParent = /* @__PURE__ */ new Map();
2289
+ for (const task of snapshot) if (task.parentId) {
2290
+ const children = childrenByParent.get(task.parentId) ?? [];
2291
+ children.push(task.id);
2292
+ childrenByParent.set(task.parentId, children);
2293
+ }
2294
+ const parentsByDepth = snapshot.filter((task) => childrenByParent.has(task.id)).map((task) => {
2289
2295
  let depth = 0;
2290
2296
  let current = task.parentId ? workingMap.get(task.parentId) : void 0;
2291
2297
  while (current) {
@@ -2294,17 +2300,21 @@ function recalculateProjectSchedule(snapshot, options) {
2294
2300
  }
2295
2301
  return { taskId: task.id, depth };
2296
2302
  }).sort((left, right) => right.depth - left.depth);
2297
- const workingTasks = () => Array.from(workingMap.values());
2298
2303
  for (const { taskId } of parentsByDepth) {
2299
2304
  const parent = workingMap.get(taskId);
2300
2305
  if (!parent || parent.locked) {
2301
2306
  continue;
2302
2307
  }
2303
- const { startDate, endDate } = computeParentDates(taskId, workingTasks());
2308
+ let startTime = Infinity, endTime = -Infinity;
2309
+ for (const childId of childrenByParent.get(taskId)) {
2310
+ const child = workingMap.get(childId);
2311
+ startTime = Math.min(startTime, new Date(child.startDate).getTime());
2312
+ endTime = Math.max(endTime, new Date(child.endDate).getTime());
2313
+ }
2304
2314
  workingMap.set(taskId, {
2305
2315
  ...parent,
2306
- startDate: toIsoDate(startDate),
2307
- endDate: toIsoDate(endDate)
2316
+ startDate: toIsoDate(new Date(startTime)),
2317
+ endDate: toIsoDate(new Date(endTime))
2308
2318
  });
2309
2319
  }
2310
2320
  return createChangedResult(snapshot, Array.from(workingMap.values()));
@@ -7706,6 +7716,7 @@ var DepChip = ({
7706
7716
  weekendPredicate
7707
7717
  }) => {
7708
7718
  const [popoverOpen, setPopoverOpen] = (0, import_react12.useState)(false);
7719
+ const [typeMenuOpen, setTypeMenuOpen] = (0, import_react12.useState)(false);
7709
7720
  const lagAbs = Math.abs(lag ?? 0);
7710
7721
  const [inputAbs, setInputAbs] = (0, import_react12.useState)(lagAbs === 0 ? "" : String(lagAbs));
7711
7722
  (0, import_react12.useEffect)(() => {
@@ -7827,6 +7838,69 @@ var DepChip = ({
7827
7838
  const Icon = LINK_TYPE_ICONS[dep.type];
7828
7839
  const depName = predecessorName ?? dep.taskId;
7829
7840
  const effectiveLag = lag ?? 0;
7841
+ const handleTypeChange = (0, import_react12.useCallback)(
7842
+ (newType) => {
7843
+ if (newType === dep.type) return;
7844
+ if (!onTasksChange || !allTasks) return;
7845
+ if ((task.dependencies ?? []).some((d) => d.taskId === dep.taskId && d.type === newType)) return;
7846
+ const taskById = new Map(allTasks.map((t) => [t.id, t]));
7847
+ const predecessor = taskById.get(dep.taskId);
7848
+ if (!predecessor) return;
7849
+ const predStart = parseUTCDate(predecessor.startDate);
7850
+ const predEnd = predecessor.type === "milestone" ? predStart : parseUTCDate(predecessor.endDate);
7851
+ const origStart = parseUTCDate(task.startDate);
7852
+ const origEnd = parseUTCDate(task.endDate);
7853
+ const durationMs = origEnd.getTime() - origStart.getTime();
7854
+ const normalizedLag = normalizeDependencyLag(
7855
+ newType,
7856
+ effectiveLag,
7857
+ predStart,
7858
+ predEnd,
7859
+ businessDays,
7860
+ weekendPredicate,
7861
+ task.type
7862
+ );
7863
+ const constraintDate = calculateSuccessorDate(
7864
+ predStart,
7865
+ predEnd,
7866
+ newType,
7867
+ normalizedLag,
7868
+ businessDays,
7869
+ weekendPredicate,
7870
+ task.type
7871
+ );
7872
+ let newStart, newEnd;
7873
+ if (newType === "FS" || newType === "SS") {
7874
+ newStart = constraintDate;
7875
+ if (businessDays) {
7876
+ const businessDuration = getBusinessDaysCount(origStart, origEnd, weekendPredicate);
7877
+ newEnd = addBusinessDays(constraintDate, businessDuration, weekendPredicate);
7878
+ } else {
7879
+ newEnd = new Date(constraintDate.getTime() + durationMs);
7880
+ }
7881
+ } else {
7882
+ newEnd = constraintDate;
7883
+ if (businessDays) {
7884
+ const businessDuration = getBusinessDaysCount(origStart, origEnd, weekendPredicate);
7885
+ newStart = subtractBusinessDays(constraintDate, businessDuration, weekendPredicate);
7886
+ } else {
7887
+ newStart = new Date(constraintDate.getTime() - durationMs);
7888
+ }
7889
+ }
7890
+ onTasksChange([
7891
+ {
7892
+ ...task,
7893
+ startDate: newStart.toISOString().split("T")[0],
7894
+ endDate: newEnd.toISOString().split("T")[0],
7895
+ dependencies: (task.dependencies ?? []).map(
7896
+ (existingDep) => existingDep.taskId === dep.taskId && existingDep.type === dep.type ? { ...existingDep, type: newType, lag: normalizedLag } : existingDep
7897
+ )
7898
+ }
7899
+ ]);
7900
+ onChipSelect?.({ successorId: taskId, predecessorId: dep.taskId, linkType: newType });
7901
+ },
7902
+ [dep.type, dep.taskId, task, allTasks, onTasksChange, businessDays, weekendPredicate, effectiveLag, onChipSelect, taskId]
7903
+ );
7830
7904
  const actionVerb = dep.type === "FS" || dep.type === "SS" ? "\u043D\u0430\u0447\u0430\u0442\u044C" : "\u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044C";
7831
7905
  const zeroPlaceholder = dep.type === "SF" ? "\u0447\u0451\u0442\u043A\u043E" : dep.type === "FF" ? "\u0432\u043C\u0435\u0441\u0442\u0435" : dep.type === "SS" ? "\u0432\u043C\u0435\u0441\u0442\u0435" : "\u0441\u0440\u0430\u0437\u0443";
7832
7906
  let afterWhat;
@@ -7958,27 +8032,71 @@ var DepChip = ({
7958
8032
  !disableDependencyEditing && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
7959
8033
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("hr", { className: "gantt-tl-dep-edit-divider" }),
7960
8034
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "gantt-tl-dep-edit-actions", children: [
7961
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
7962
- "button",
7963
- {
7964
- type: "button",
7965
- className: "gantt-tl-dep-edit-close",
7966
- onClick: () => {
7967
- setPopoverOpen(false);
7968
- onChipSelectClear();
7969
- },
7970
- children: "\u0417\u0430\u043A\u0440\u044B\u0442\u044C"
7971
- }
7972
- ),
7973
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
7974
- "button",
7975
- {
7976
- type: "button",
7977
- className: "gantt-tl-dep-edit-delete",
7978
- onClick: handleTrashClick,
7979
- children: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u0432\u044F\u0437\u044C"
7980
- }
7981
- )
8035
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Popover, { open: typeMenuOpen, onOpenChange: setTypeMenuOpen, children: [
8036
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8037
+ "button",
8038
+ {
8039
+ type: "button",
8040
+ className: "gantt-tl-dep-edit-type-trigger",
8041
+ onClick: (e) => e.stopPropagation(),
8042
+ "aria-label": "\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C \u0442\u0438\u043F \u0441\u0432\u044F\u0437\u0438",
8043
+ title: "\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C \u0442\u0438\u043F \u0441\u0432\u044F\u0437\u0438",
8044
+ children: [
8045
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, {}),
8046
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: LINK_TYPE_LABELS_RU2[dep.type] }),
8047
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gantt-tl-dep-edit-type-caret", "aria-hidden": "true", children: "\u25BE" })
8048
+ ]
8049
+ }
8050
+ ) }),
8051
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(PopoverContent, { portal: true, align: "start", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "gantt-tl-dep-type-menu", children: LINK_TYPE_ORDER.map((linkType) => {
8052
+ const TypeIcon = LINK_TYPE_ICONS[linkType];
8053
+ const isCurrent = linkType === dep.type;
8054
+ const isDuplicate = !isCurrent && (task.dependencies ?? []).some(
8055
+ (d) => d.taskId === dep.taskId && d.type === linkType
8056
+ );
8057
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8058
+ "button",
8059
+ {
8060
+ type: "button",
8061
+ className: `gantt-tl-dep-type-option${isCurrent ? " active" : ""}${isDuplicate ? " gantt-tl-dep-type-option-duplicate" : ""}`,
8062
+ disabled: isDuplicate,
8063
+ onClick: () => {
8064
+ handleTypeChange(linkType);
8065
+ setTypeMenuOpen(false);
8066
+ },
8067
+ title: isDuplicate ? "\u0421\u0432\u044F\u0437\u044C \u0442\u0430\u043A\u043E\u0433\u043E \u0442\u0438\u043F\u0430 \u0443\u0436\u0435 \u0435\u0441\u0442\u044C" : `\u0422\u0438\u043F \u0441\u0432\u044F\u0437\u0438 ${LINK_TYPE_LABELS_RU2[linkType]}`,
8068
+ children: [
8069
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(TypeIcon, {}),
8070
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: LINK_TYPE_LABELS_RU2[linkType] })
8071
+ ]
8072
+ },
8073
+ linkType
8074
+ );
8075
+ }) }) })
8076
+ ] }),
8077
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "gantt-tl-dep-edit-actions-right", children: [
8078
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8079
+ "button",
8080
+ {
8081
+ type: "button",
8082
+ className: "gantt-tl-dep-edit-close",
8083
+ onClick: () => {
8084
+ setPopoverOpen(false);
8085
+ onChipSelectClear();
8086
+ },
8087
+ children: "\u0417\u0430\u043A\u0440\u044B\u0442\u044C"
8088
+ }
8089
+ ),
8090
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8091
+ "button",
8092
+ {
8093
+ type: "button",
8094
+ className: "gantt-tl-dep-edit-delete",
8095
+ onClick: handleTrashClick,
8096
+ children: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u0432\u044F\u0437\u044C"
8097
+ }
8098
+ )
8099
+ ] })
7982
8100
  ] })
7983
8101
  ] })
7984
8102
  ] })
@@ -9484,7 +9602,7 @@ var TaskListRow = import_react12.default.memo(
9484
9602
  {
9485
9603
  className: "gantt-tl-dep-overflow-list",
9486
9604
  onClick: (e) => e.stopPropagation(),
9487
- children: chips.map(({ dep, lag, predecessorName }) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
9605
+ children: chips.map(({ dep, lag, predecessorName }, chipIndex) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
9488
9606
  DepChip,
9489
9607
  {
9490
9608
  lag,
@@ -9506,7 +9624,7 @@ var TaskListRow = import_react12.default.memo(
9506
9624
  businessDays,
9507
9625
  weekendPredicate
9508
9626
  },
9509
- `${dep.taskId}-${dep.type}`
9627
+ `${dep.taskId}-${chipIndex}`
9510
9628
  ))
9511
9629
  }
9512
9630
  ) })
@@ -9534,7 +9652,8 @@ var TaskListRow = import_react12.default.memo(
9534
9652
  onTasksChange,
9535
9653
  businessDays,
9536
9654
  weekendPredicate
9537
- }
9655
+ },
9656
+ chips[0].dep.taskId
9538
9657
  )
9539
9658
  ) : null,
9540
9659
  !printDependencyLabels && !disableDependencyEditing && !isPicking && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(