gantt-lib 0.80.0 → 0.81.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
@@ -5542,19 +5542,6 @@ var TaskListRow = import_react10.default.memo(
5542
5542
  );
5543
5543
  const isSelectedPredecessor = selectedChip != null && selectedChip.predecessorId === task.id;
5544
5544
  const isSelectedDependencyOwner = selectedChip != null && selectedChip.successorId === task.id;
5545
- const handleDeleteSelected = (0, import_react10.useCallback)(
5546
- (e) => {
5547
- e.stopPropagation();
5548
- if (!selectedChip) return;
5549
- onRemoveDependency?.(
5550
- selectedChip.successorId,
5551
- selectedChip.predecessorId,
5552
- selectedChip.linkType
5553
- );
5554
- onChipSelect?.(null);
5555
- },
5556
- [selectedChip?.successorId, selectedChip?.predecessorId, selectedChip?.linkType, onRemoveDependency, onChipSelect]
5557
- );
5558
5545
  const startDateISO = toISODate2(normalizedTask.startDate);
5559
5546
  const endDateISO = editingDuration ? getEndDate(normalizedTask.startDate, durationValue) : toISODate2(normalizedTask.endDate);
5560
5547
  const numberCell = /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
@@ -5766,7 +5753,7 @@ var TaskListRow = import_react10.default.memo(
5766
5753
  "aria-hidden": "true"
5767
5754
  }
5768
5755
  ),
5769
- !editingName && (onInsertAfter || onDelete || onPromoteTask || onDemoteTask || onUngroupTask || onDuplicateTask || onTasksChange || hasContextMenu) && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "gantt-tl-name-actions", children: [
5756
+ !editingName && (onInsertAfter || onDelete || onPromoteTask || onDemoteTask || onUngroupTask || onDuplicateTask || onTasksChange || hasContextMenu) && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: `gantt-tl-name-actions${contextMenuOpen ? " gantt-tl-name-actions-open" : ""}`, children: [
5770
5757
  onInsertAfter && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
5771
5758
  "button",
5772
5759
  {
@@ -6182,22 +6169,7 @@ var TaskListRow = import_react10.default.memo(
6182
6169
  }
6183
6170
  ),
6184
6171
  sourcePickerContent
6185
- ] }) : isSelectedPredecessor && !disableDependencyEditing ? (
6186
- /* Full-replacement: "Зависит от [name]" → hover → "Удалить" */
6187
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
6188
- "button",
6189
- {
6190
- type: "button",
6191
- className: "gantt-tl-dep-delete-label",
6192
- onClick: handleDeleteSelected,
6193
- "aria-label": "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u0432\u044F\u0437\u044C",
6194
- children: [
6195
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "gantt-tl-dep-delete-label-default", children: "\u0421\u0432\u044F\u0437\u0430\u043D\u043E \u0441" }),
6196
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "gantt-tl-dep-delete-label-hover", children: "\xD7 \u0443\u0434\u0430\u043B\u0438\u0442\u044C" })
6197
- ]
6198
- }
6199
- )
6200
- ) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
6172
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
6201
6173
  chips.length >= 2 ? (
6202
6174
  /* 2+ deps — show only "N связей" summary chip that opens a popover */
6203
6175
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Popover, { open: overflowOpen, onOpenChange: setOverflowOpen, children: [
@@ -7868,7 +7840,8 @@ var ResourceHeader = ({
7868
7840
  conflictCount,
7869
7841
  height,
7870
7842
  paddingBottom,
7871
- menuCommands
7843
+ menuCommands,
7844
+ onConflictBadgeClick
7872
7845
  }) => {
7873
7846
  const [menuOpen, setMenuOpen] = (0, import_react14.useState)(false);
7874
7847
  const visibleCommands = (0, import_react14.useMemo)(
@@ -7895,11 +7868,16 @@ var ResourceHeader = ({
7895
7868
  children: [
7896
7869
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gantt-resourceTimeline-resourceName", children: resource.name }),
7897
7870
  conflictCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
7898
- "span",
7871
+ "button",
7899
7872
  {
7873
+ type: "button",
7900
7874
  className: "gantt-resourceTimeline-conflictBadge",
7901
7875
  "aria-label": formatOverlapCount(conflictCount),
7902
7876
  title: formatOverlapCount(conflictCount),
7877
+ onClick: (event) => {
7878
+ event.stopPropagation();
7879
+ onConflictBadgeClick?.(resourceId);
7880
+ },
7903
7881
  children: conflictCount
7904
7882
  }
7905
7883
  ),
@@ -8009,6 +7987,8 @@ function ResourceTimelineChart({
8009
7987
  renderItem,
8010
7988
  getItemClassName,
8011
7989
  onResourceItemClick,
7990
+ onResourceItemMenuClick,
7991
+ activeResourceItemId,
8012
7992
  onResourceItemMove,
8013
7993
  onAddResource,
8014
7994
  enableAddResource = true,
@@ -8017,7 +7997,10 @@ function ResourceTimelineChart({
8017
7997
  const scrollContainerRef = (0, import_react14.useRef)(null);
8018
7998
  const gridRef = (0, import_react14.useRef)(null);
8019
7999
  const panStateRef = (0, import_react14.useRef)(null);
8000
+ const conflictNavigationIndexRef = (0, import_react14.useRef)(/* @__PURE__ */ new Map());
8001
+ const conflictHighlightTimeoutRef = (0, import_react14.useRef)(null);
8020
8002
  const [isCreatingResource, setIsCreatingResource] = (0, import_react14.useState)(false);
8003
+ const [activeConflictItemId, setActiveConflictItemId] = (0, import_react14.useState)(null);
8021
8004
  const validItems = (0, import_react14.useMemo)(() => collectValidItems(resources), [resources]);
8022
8005
  const dateRange = (0, import_react14.useMemo)(() => getMultiMonthDays(validItems), [validItems]);
8023
8006
  const monthStart = (0, import_react14.useMemo)(() => {
@@ -8052,6 +8035,23 @@ function ResourceTimelineChart({
8052
8035
  }
8053
8036
  return map;
8054
8037
  }, [layout.items]);
8038
+ const conflictItemsByResourceId = (0, import_react14.useMemo)(() => {
8039
+ const map = /* @__PURE__ */ new Map();
8040
+ for (const item of layout.items) {
8041
+ if (item.conflictRanges.length === 0) {
8042
+ continue;
8043
+ }
8044
+ const next = map.get(item.resourceId) ?? [];
8045
+ next.push(item);
8046
+ map.set(item.resourceId, next);
8047
+ }
8048
+ for (const items of map.values()) {
8049
+ items.sort(
8050
+ (left, right) => left.top - right.top || left.left - right.left || left.itemId.localeCompare(right.itemId)
8051
+ );
8052
+ }
8053
+ return map;
8054
+ }, [layout.items]);
8055
8055
  const canAddResource = enableAddResource && Boolean(onAddResource);
8056
8056
  const resourceAddRowHeight = laneHeight + DEFAULT_RESOURCE_ROW_GAP;
8057
8057
  const displayTotalHeight = layout.totalHeight + (canAddResource ? resourceAddRowHeight : 0);
@@ -8064,6 +8064,31 @@ function ResourceTimelineChart({
8064
8064
  setIsCreatingResource(false);
8065
8065
  }, [onAddResource]);
8066
8066
  const handleCancelNewResource = (0, import_react14.useCallback)(() => setIsCreatingResource(false), []);
8067
+ const handleConflictBadgeClick = (0, import_react14.useCallback)((resourceId) => {
8068
+ const conflictItems = conflictItemsByResourceId.get(resourceId) ?? [];
8069
+ if (conflictItems.length === 0) {
8070
+ return;
8071
+ }
8072
+ const currentIndex = conflictNavigationIndexRef.current.get(resourceId) ?? 0;
8073
+ const target = conflictItems[currentIndex % conflictItems.length];
8074
+ conflictNavigationIndexRef.current.set(resourceId, (currentIndex + 1) % conflictItems.length);
8075
+ const container = scrollContainerRef.current;
8076
+ if (container) {
8077
+ container.scrollTo({
8078
+ left: Math.max(0, Math.round(target.left - dayWidth * 2)),
8079
+ top: Math.max(0, Math.round(target.top - laneHeight)),
8080
+ behavior: "smooth"
8081
+ });
8082
+ }
8083
+ setActiveConflictItemId(target.itemId);
8084
+ if (conflictHighlightTimeoutRef.current) {
8085
+ window.clearTimeout(conflictHighlightTimeoutRef.current);
8086
+ }
8087
+ conflictHighlightTimeoutRef.current = window.setTimeout(() => {
8088
+ setActiveConflictItemId((current) => current === target.itemId ? null : current);
8089
+ conflictHighlightTimeoutRef.current = null;
8090
+ }, 1600);
8091
+ }, [conflictItemsByResourceId, dayWidth, laneHeight]);
8067
8092
  const { preview, startDrag } = useResourceItemDrag({
8068
8093
  dayWidth,
8069
8094
  monthStart,
@@ -8132,6 +8157,13 @@ function ResourceTimelineChart({
8132
8157
  window.removeEventListener("mouseup", handlePanEnd);
8133
8158
  };
8134
8159
  }, [allowVerticalPan]);
8160
+ (0, import_react14.useEffect)(() => {
8161
+ return () => {
8162
+ if (conflictHighlightTimeoutRef.current) {
8163
+ window.clearTimeout(conflictHighlightTimeoutRef.current);
8164
+ }
8165
+ };
8166
+ }, []);
8135
8167
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "gantt-container gantt-resourceTimeline", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8136
8168
  "div",
8137
8169
  {
@@ -8166,7 +8198,8 @@ function ResourceTimelineChart({
8166
8198
  conflictCount: row.conflictCount,
8167
8199
  height: row.resourceRowHeight + DEFAULT_RESOURCE_ROW_GAP,
8168
8200
  paddingBottom: DEFAULT_RESOURCE_ROW_GAP,
8169
- menuCommands: resourceMenuCommands
8201
+ menuCommands: resourceMenuCommands,
8202
+ onConflictBadgeClick: handleConflictBadgeClick
8170
8203
  },
8171
8204
  row.resourceId
8172
8205
  )),
@@ -8251,9 +8284,14 @@ function ResourceTimelineChart({
8251
8284
  (resourceItems) => resourceItems.map((layoutItem) => {
8252
8285
  const customClassName = getItemClassName?.(layoutItem.item);
8253
8286
  const isDraggingItem = preview?.itemId === layoutItem.itemId;
8287
+ const hasItemMenu = Boolean(onResourceItemMenuClick);
8288
+ const isActiveItem = activeResourceItemId === layoutItem.itemId;
8254
8289
  const className = [
8255
8290
  "gantt-resourceTimeline-item",
8291
+ hasItemMenu && "gantt-resourceTimeline-itemHasMenu",
8292
+ isActiveItem && "gantt-resourceTimeline-itemActive",
8256
8293
  isDraggingItem && "gantt-resourceTimeline-itemDragging",
8294
+ activeConflictItemId === layoutItem.itemId && "gantt-resourceTimeline-itemConflictActive",
8257
8295
  (readonly || layoutItem.item.locked) && "gantt-resourceTimeline-itemDisabled",
8258
8296
  customClassName
8259
8297
  ].filter(Boolean).join(" ");
@@ -8352,6 +8390,22 @@ function ResourceTimelineChart({
8352
8390
  },
8353
8391
  `conflict-overlay-${index}`
8354
8392
  )),
8393
+ hasItemMenu && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8394
+ "button",
8395
+ {
8396
+ type: "button",
8397
+ className: "gantt-resourceTimeline-itemMenuButton",
8398
+ "aria-label": "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u043D\u0430\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F",
8399
+ onMouseDown: (event) => {
8400
+ event.stopPropagation();
8401
+ },
8402
+ onClick: (event) => {
8403
+ event.stopPropagation();
8404
+ onResourceItemMenuClick?.(layoutItem.item);
8405
+ },
8406
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { "aria-hidden": "true", children: "\u22EE" })
8407
+ }
8408
+ ),
8355
8409
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "gantt-resourceTimeline-itemInner", children: renderItem ? renderItem(layoutItem.item, renderContext) : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "gantt-resourceTimeline-defaultItemContent", children: [
8356
8410
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "gantt-resourceTimeline-defaultItemMain", children: [
8357
8411
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(