gantt-lib 0.52.0 → 0.53.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
@@ -721,6 +721,22 @@ function validateDependencies(tasks) {
721
721
  }
722
722
  }
723
723
  }
724
+ for (const task of tasks) {
725
+ if (!task.dependencies) continue;
726
+ for (const dep of task.dependencies) {
727
+ if (!taskIds.has(dep.taskId)) {
728
+ continue;
729
+ }
730
+ if (areTasksHierarchicallyRelated(task.id, dep.taskId, tasks)) {
731
+ errors.push({
732
+ type: "constraint",
733
+ taskId: task.id,
734
+ message: `Dependencies between parent and child tasks are not allowed: ${dep.taskId} -> ${task.id}`,
735
+ relatedTaskIds: [dep.taskId, task.id]
736
+ });
737
+ }
738
+ }
739
+ }
724
740
  const cycleResult = detectCycles(tasks);
725
741
  if (cycleResult.hasCycle && cycleResult.cyclePath) {
726
742
  errors.push({
@@ -979,6 +995,28 @@ function findParentId(taskId, tasks) {
979
995
  const task = tasks.find((t) => t.id === taskId);
980
996
  return task?.parentId;
981
997
  }
998
+ function isAncestorTask(ancestorId, taskId, tasks) {
999
+ const taskById = new Map(tasks.map((task) => [task.id, task]));
1000
+ const visited = /* @__PURE__ */ new Set();
1001
+ let current = taskById.get(taskId);
1002
+ while (current?.parentId) {
1003
+ if (current.parentId === ancestorId) {
1004
+ return true;
1005
+ }
1006
+ if (visited.has(current.parentId)) {
1007
+ return false;
1008
+ }
1009
+ visited.add(current.parentId);
1010
+ current = taskById.get(current.parentId);
1011
+ }
1012
+ return false;
1013
+ }
1014
+ function areTasksHierarchicallyRelated(taskId1, taskId2, tasks) {
1015
+ if (taskId1 === taskId2) {
1016
+ return true;
1017
+ }
1018
+ return isAncestorTask(taskId1, taskId2, tasks) || isAncestorTask(taskId2, taskId1, tasks);
1019
+ }
982
1020
  function getAllDescendants(parentId, tasks) {
983
1021
  const descendants = [];
984
1022
  const visited = /* @__PURE__ */ new Set();
@@ -3257,6 +3295,20 @@ var DatePicker = ({
3257
3295
  },
3258
3296
  [selectedDate, updateFromDate, businessDays, isWeekend3]
3259
3297
  );
3298
+ const handleTriggerKeyDown = useCallback3((e) => {
3299
+ if (disabled) return;
3300
+ if (e.key === "ArrowUp") {
3301
+ e.preventDefault();
3302
+ e.stopPropagation();
3303
+ handleDayShift(1);
3304
+ return;
3305
+ }
3306
+ if (e.key === "ArrowDown") {
3307
+ e.preventDefault();
3308
+ e.stopPropagation();
3309
+ handleDayShift(-1);
3310
+ }
3311
+ }, [disabled, handleDayShift]);
3260
3312
  const handleKeyDown = useCallback3((e) => {
3261
3313
  if (!dateInputRef.current) return;
3262
3314
  const { value: inputVal } = dateInputRef.current;
@@ -3364,6 +3416,7 @@ var DatePicker = ({
3364
3416
  type: "button",
3365
3417
  className: `gantt-datepicker-trigger${className ? ` ${className}` : ""}`,
3366
3418
  disabled,
3419
+ onKeyDown: handleTriggerKeyDown,
3367
3420
  onClick: (e) => {
3368
3421
  e.stopPropagation();
3369
3422
  },
@@ -5834,7 +5887,7 @@ var TaskList = ({
5834
5887
  const [selectingPredecessorFor, setSelectingPredecessorFor] = useState6(null);
5835
5888
  const [dependencyPickMode, setDependencyPickMode] = useState6("successor");
5836
5889
  const [typeMenuOpen, setTypeMenuOpen] = useState6(false);
5837
- const [cycleError, setCycleError] = useState6(false);
5890
+ const [dependencyError, setDependencyError] = useState6(null);
5838
5891
  const overlayRef = useRef6(null);
5839
5892
  const [selectedChip, setSelectedChip] = useState6(null);
5840
5893
  const handleChipSelect = useCallback5((chip) => {
@@ -5869,6 +5922,12 @@ var TaskList = ({
5869
5922
  }, [selectingPredecessorFor, selectedChip, selectedTaskId, onTaskSelect, onSelectedChipChange]);
5870
5923
  const handleAddDependency = useCallback5((successorTaskId, predecessorTaskId, linkType) => {
5871
5924
  if (successorTaskId === predecessorTaskId) return;
5925
+ if (areTasksHierarchicallyRelated(successorTaskId, predecessorTaskId, tasks)) {
5926
+ setDependencyError("\u0421\u0432\u044F\u0437\u0438 \u043C\u0435\u0436\u0434\u0443 \u0440\u043E\u0434\u0438\u0442\u0435\u043B\u0435\u043C \u0438 \u043F\u043E\u0442\u043E\u043C\u043A\u043E\u043C \u0437\u0430\u043F\u0440\u0435\u0449\u0435\u043D\u044B");
5927
+ setTimeout(() => setDependencyError(null), 3e3);
5928
+ setSelectingPredecessorFor(null);
5929
+ return;
5930
+ }
5872
5931
  const successor = tasks.find((t) => t.id === successorTaskId);
5873
5932
  if (!successor) return;
5874
5933
  const alreadyExists = (successor.dependencies ?? []).some(
@@ -5884,8 +5943,11 @@ var TaskList = ({
5884
5943
  );
5885
5944
  const validation = validateDependencies(hypothetical);
5886
5945
  if (!validation.isValid) {
5887
- setCycleError(true);
5888
- setTimeout(() => setCycleError(false), 3e3);
5946
+ const hasHierarchyConstraint = validation.errors.some((error) => error.type === "constraint");
5947
+ setDependencyError(
5948
+ hasHierarchyConstraint ? "\u0421\u0432\u044F\u0437\u0438 \u043C\u0435\u0436\u0434\u0443 \u0440\u043E\u0434\u0438\u0442\u0435\u043B\u0435\u043C \u0438 \u043F\u043E\u0442\u043E\u043C\u043A\u043E\u043C \u0437\u0430\u043F\u0440\u0435\u0449\u0435\u043D\u044B" : "\u0426\u0438\u043A\u043B \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0435\u0439!"
5949
+ );
5950
+ setTimeout(() => setDependencyError(null), 3e3);
5889
5951
  return;
5890
5952
  }
5891
5953
  const updatedTask = hypothetical.find((t) => t.id === successorTaskId);
@@ -6209,7 +6271,7 @@ var TaskList = ({
6209
6271
  lt
6210
6272
  )) }) })
6211
6273
  ] }),
6212
- cycleError && /* @__PURE__ */ jsx14("div", { className: "gantt-tl-dep-error", children: "\u0426\u0438\u043A\u043B \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0435\u0439!" })
6274
+ dependencyError && /* @__PURE__ */ jsx14("div", { className: "gantt-tl-dep-error", children: dependencyError })
6213
6275
  ]
6214
6276
  },
6215
6277
  col.id
@@ -6556,20 +6618,8 @@ function GanttChartInner(props, ref) {
6556
6618
  }
6557
6619
  return;
6558
6620
  }
6559
- const isParent = isTaskParent(updatedTask.id, tasks);
6560
- if (isParent) {
6561
- const { startDate: parentStart, endDate: parentEnd } = computeParentDates(updatedTask.id, tasks);
6562
- const parentWithRecalcDates = {
6563
- ...updatedTask,
6564
- startDate: parentStart.toISOString().split("T")[0],
6565
- endDate: parentEnd.toISOString().split("T")[0]
6566
- };
6567
- const cascadedTasks = disableConstraints ? [parentWithRecalcDates] : universalCascade(parentWithRecalcDates, parentStart, parentEnd, tasks, businessDays, isCustomWeekend);
6568
- onTasksChange?.(cascadedTasks);
6569
- } else {
6570
- const cascadedTasks = disableConstraints ? [updatedTask] : universalCascade(updatedTask, newStart, newEnd, tasks, businessDays, isCustomWeekend);
6571
- onTasksChange?.(cascadedTasks);
6572
- }
6621
+ const cascadedTasks = disableConstraints ? [updatedTask] : universalCascade(updatedTask, newStart, newEnd, tasks, businessDays, isCustomWeekend);
6622
+ onTasksChange?.(cascadedTasks);
6573
6623
  }, [tasks, onTasksChange, disableConstraints, editingTaskId, businessDays, isCustomWeekend]);
6574
6624
  const handleDelete = useCallback6((taskId) => {
6575
6625
  const toDelete = /* @__PURE__ */ new Set([taskId]);
@@ -7010,6 +7060,7 @@ export {
7010
7060
  addBusinessDays,
7011
7061
  alignToWorkingDay,
7012
7062
  and,
7063
+ areTasksHierarchicallyRelated,
7013
7064
  buildAdjacencyList,
7014
7065
  buildTaskRangeFromEnd,
7015
7066
  buildTaskRangeFromStart,
@@ -7056,6 +7107,7 @@ export {
7056
7107
  getWeekSpans,
7057
7108
  getYearSpans,
7058
7109
  inDateRange,
7110
+ isAncestorTask,
7059
7111
  isTaskExpired,
7060
7112
  isTaskParent,
7061
7113
  isToday,