gantt-task-react-v 1.1.15 → 1.1.17

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.
@@ -15621,7 +15621,7 @@ function isOverflowElement(element) {
15621
15621
  overflowX,
15622
15622
  overflowY,
15623
15623
  display
15624
- } = getComputedStyle(element);
15624
+ } = getComputedStyle$1(element);
15625
15625
  return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && !["inline", "contents"].includes(display);
15626
15626
  }
15627
15627
  function isTableElement(element) {
@@ -15629,7 +15629,7 @@ function isTableElement(element) {
15629
15629
  }
15630
15630
  function isContainingBlock(element) {
15631
15631
  const webkit = isWebKit();
15632
- const css = getComputedStyle(element);
15632
+ const css = getComputedStyle$1(element);
15633
15633
  return css.transform !== "none" || css.perspective !== "none" || (css.containerType ? css.containerType !== "normal" : false) || !webkit && (css.backdropFilter ? css.backdropFilter !== "none" : false) || !webkit && (css.filter ? css.filter !== "none" : false) || ["transform", "perspective", "filter"].some((value) => (css.willChange || "").includes(value)) || ["paint", "layout", "strict", "content"].some((value) => (css.contain || "").includes(value));
15634
15634
  }
15635
15635
  function getContainingBlock(element) {
@@ -15651,7 +15651,7 @@ function isWebKit() {
15651
15651
  function isLastTraversableNode(node) {
15652
15652
  return ["html", "body", "#document"].includes(getNodeName(node));
15653
15653
  }
15654
- function getComputedStyle(element) {
15654
+ function getComputedStyle$1(element) {
15655
15655
  return getWindow(element).getComputedStyle(element);
15656
15656
  }
15657
15657
  function getNodeScroll(element) {
@@ -15706,7 +15706,7 @@ function getOverflowAncestors(node, list, traverseIframes) {
15706
15706
  return list.concat(scrollableAncestor, getOverflowAncestors(scrollableAncestor, [], traverseIframes));
15707
15707
  }
15708
15708
  function getCssDimensions(element) {
15709
- const css = getComputedStyle(element);
15709
+ const css = getComputedStyle$1(element);
15710
15710
  let width = parseFloat(css.width) || 0;
15711
15711
  let height = parseFloat(css.height) || 0;
15712
15712
  const hasOffset = isHTMLElement(element);
@@ -15802,7 +15802,7 @@ function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetPar
15802
15802
  while (currentIFrame && offsetParent && offsetWin !== currentWin) {
15803
15803
  const iframeScale = getScale(currentIFrame);
15804
15804
  const iframeRect = currentIFrame.getBoundingClientRect();
15805
- const css = getComputedStyle(currentIFrame);
15805
+ const css = getComputedStyle$1(currentIFrame);
15806
15806
  const left = iframeRect.left + (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x;
15807
15807
  const top = iframeRect.top + (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y;
15808
15808
  x *= iframeScale.x;
@@ -15884,7 +15884,7 @@ function getDocumentRect(element) {
15884
15884
  const height = max(html.scrollHeight, html.clientHeight, body.scrollHeight, body.clientHeight);
15885
15885
  let x = -scroll2.scrollLeft + getWindowScrollBarX(element);
15886
15886
  const y = -scroll2.scrollTop;
15887
- if (getComputedStyle(body).direction === "rtl") {
15887
+ if (getComputedStyle$1(body).direction === "rtl") {
15888
15888
  x += max(html.clientWidth, body.clientWidth) - width;
15889
15889
  }
15890
15890
  return {
@@ -15957,7 +15957,7 @@ function hasFixedPositionAncestor(element, stopNode) {
15957
15957
  if (parentNode === stopNode || !isElement(parentNode) || isLastTraversableNode(parentNode)) {
15958
15958
  return false;
15959
15959
  }
15960
- return getComputedStyle(parentNode).position === "fixed" || hasFixedPositionAncestor(parentNode, stopNode);
15960
+ return getComputedStyle$1(parentNode).position === "fixed" || hasFixedPositionAncestor(parentNode, stopNode);
15961
15961
  }
15962
15962
  function getClippingElementAncestors(element, cache) {
15963
15963
  const cachedResult = cache.get(element);
@@ -15966,10 +15966,10 @@ function getClippingElementAncestors(element, cache) {
15966
15966
  }
15967
15967
  let result = getOverflowAncestors(element, [], false).filter((el) => isElement(el) && getNodeName(el) !== "body");
15968
15968
  let currentContainingBlockComputedStyle = null;
15969
- const elementIsFixed = getComputedStyle(element).position === "fixed";
15969
+ const elementIsFixed = getComputedStyle$1(element).position === "fixed";
15970
15970
  let currentNode = elementIsFixed ? getParentNode(element) : element;
15971
15971
  while (isElement(currentNode) && !isLastTraversableNode(currentNode)) {
15972
- const computedStyle = getComputedStyle(currentNode);
15972
+ const computedStyle = getComputedStyle$1(currentNode);
15973
15973
  const currentNodeIsContaining = isContainingBlock(currentNode);
15974
15974
  if (!currentNodeIsContaining && computedStyle.position === "fixed") {
15975
15975
  currentContainingBlockComputedStyle = null;
@@ -16052,7 +16052,7 @@ function getRectRelativeToOffsetParent(element, offsetParent, strategy) {
16052
16052
  };
16053
16053
  }
16054
16054
  function getTrueOffsetParent(element, polyfill) {
16055
- if (!isHTMLElement(element) || getComputedStyle(element).position === "fixed") {
16055
+ if (!isHTMLElement(element) || getComputedStyle$1(element).position === "fixed") {
16056
16056
  return null;
16057
16057
  }
16058
16058
  if (polyfill) {
@@ -16066,10 +16066,10 @@ function getOffsetParent(element, polyfill) {
16066
16066
  return window2;
16067
16067
  }
16068
16068
  let offsetParent = getTrueOffsetParent(element, polyfill);
16069
- while (offsetParent && isTableElement(offsetParent) && getComputedStyle(offsetParent).position === "static") {
16069
+ while (offsetParent && isTableElement(offsetParent) && getComputedStyle$1(offsetParent).position === "static") {
16070
16070
  offsetParent = getTrueOffsetParent(offsetParent, polyfill);
16071
16071
  }
16072
- if (offsetParent && (getNodeName(offsetParent) === "html" || getNodeName(offsetParent) === "body" && getComputedStyle(offsetParent).position === "static" && !isContainingBlock(offsetParent))) {
16072
+ if (offsetParent && (getNodeName(offsetParent) === "html" || getNodeName(offsetParent) === "body" && getComputedStyle$1(offsetParent).position === "static" && !isContainingBlock(offsetParent))) {
16073
16073
  return window2;
16074
16074
  }
16075
16075
  return offsetParent || getContainingBlock(element) || window2;
@@ -16087,7 +16087,7 @@ const getElementRects = async function(data) {
16087
16087
  };
16088
16088
  };
16089
16089
  function isRTL(element) {
16090
- return getComputedStyle(element).direction === "rtl";
16090
+ return getComputedStyle$1(element).direction === "rtl";
16091
16091
  }
16092
16092
  const platform = {
16093
16093
  convertOffsetParentRelativeRectToViewportRelativeRect,
@@ -16788,7 +16788,7 @@ function useDismiss(context, props) {
16788
16788
  const canScrollY = target.clientHeight > 0 && target.scrollHeight > target.clientHeight;
16789
16789
  let xCond = canScrollY && event.offsetX > target.clientWidth;
16790
16790
  if (canScrollY) {
16791
- const isRTL2 = getComputedStyle(target).direction === "rtl";
16791
+ const isRTL2 = getComputedStyle$1(target).direction === "rtl";
16792
16792
  if (isRTL2) {
16793
16793
  xCond = event.offsetX <= target.offsetWidth - target.clientWidth;
16794
16794
  }
@@ -18019,46 +18019,179 @@ function MenuOption(props) {
18019
18019
  },
18020
18020
  handleAction,
18021
18021
  option,
18022
- option: { icon: icon2, label: label2, disabled }
18022
+ option: { icon: icon2, label: label2, disabled, children }
18023
18023
  } = props;
18024
+ const [hovered, setHovered] = useState(false);
18025
+ const [coords, setCoords] = useState(null);
18026
+ const btnRef = useRef(null);
18027
+ const nestedRef = useRef(null);
18028
+ const closeTimeoutRef = useRef(null);
18024
18029
  const onClick = useCallback(
18025
18030
  (e) => {
18026
18031
  e.preventDefault();
18027
18032
  if (disabled) {
18028
18033
  return;
18029
18034
  }
18035
+ if (children && children.length > 0) {
18036
+ setHovered(true);
18037
+ return;
18038
+ }
18030
18039
  handleAction(option);
18031
18040
  onClose == null ? void 0 : onClose();
18032
18041
  },
18033
- [onClose, handleAction, option, disabled]
18042
+ [onClose, handleAction, option, disabled, children]
18034
18043
  );
18044
+ useEffect(() => {
18045
+ if (!hovered || !nestedRef.current)
18046
+ return;
18047
+ const cssVars = [
18048
+ "--gantt-context-menu-bg-color",
18049
+ "--gantt-context-menu-box-shadow",
18050
+ "--gantt-shape-border-radius",
18051
+ "--gantt-font-family",
18052
+ "--gantt-font-size",
18053
+ "--gantt-context-menu-empty-color"
18054
+ ];
18055
+ const sourceEl = btnRef.current || (typeof document !== "undefined" ? document.body : null);
18056
+ if (!sourceEl)
18057
+ return;
18058
+ const cs = getComputedStyle(sourceEl);
18059
+ cssVars.forEach((v) => {
18060
+ const val = cs.getPropertyValue(v).trim();
18061
+ if (val) {
18062
+ nestedRef.current.style.setProperty(v, val);
18063
+ }
18064
+ });
18065
+ }, [hovered, coords]);
18035
18066
  return /* @__PURE__ */ jsxs(
18036
- "button",
18067
+ "div",
18037
18068
  {
18038
- className: styles$1.menuOption,
18039
- "aria-disabled": disabled,
18040
- disabled,
18041
- style: {
18042
- height: contextMenuOptionHeight,
18043
- paddingLeft: contextMenuSidePadding,
18044
- paddingRight: contextMenuSidePadding,
18045
- color: "var(--gantt-context-menu-text-color)"
18069
+ onMouseEnter: () => {
18070
+ var _a, _b;
18071
+ if (closeTimeoutRef.current) {
18072
+ window.clearTimeout(closeTimeoutRef.current);
18073
+ closeTimeoutRef.current = null;
18074
+ }
18075
+ setHovered(true);
18076
+ const rect = (_a = btnRef.current) == null ? void 0 : _a.getBoundingClientRect();
18077
+ const parentMenu = (_b = btnRef.current) == null ? void 0 : _b.closest(
18078
+ '[role="menu"]'
18079
+ );
18080
+ const parentRect = (parentMenu == null ? void 0 : parentMenu.getBoundingClientRect()) ?? null;
18081
+ if (rect) {
18082
+ setCoords({
18083
+ left: parentRect ? parentRect.right : rect.right,
18084
+ top: rect.top,
18085
+ parentWidth: parentRect ? Math.round(parentRect.width) : void 0
18086
+ });
18087
+ }
18046
18088
  },
18047
- onClick,
18089
+ onMouseLeave: () => {
18090
+ closeTimeoutRef.current = window.setTimeout(() => {
18091
+ setHovered(false);
18092
+ setCoords(null);
18093
+ closeTimeoutRef.current = null;
18094
+ }, 200);
18095
+ },
18096
+ style: { position: "relative" },
18048
18097
  children: [
18049
- /* @__PURE__ */ jsx(
18050
- "div",
18098
+ /* @__PURE__ */ jsxs(
18099
+ "button",
18051
18100
  {
18052
- className: styles$1.icon,
18101
+ className: styles$1.menuOption,
18102
+ "aria-disabled": disabled,
18103
+ disabled,
18053
18104
  style: {
18054
- width: contextMenuIconWidth,
18055
- color: "var(--gantt-context-menu-text-color)",
18056
- opacity: disabled ? 0.3 : 0.5
18105
+ height: contextMenuOptionHeight,
18106
+ paddingLeft: contextMenuSidePadding,
18107
+ paddingRight: contextMenuSidePadding,
18108
+ color: "var(--gantt-context-menu-text-color)"
18057
18109
  },
18058
- children: icon2
18110
+ onClick,
18111
+ ref: btnRef,
18112
+ children: [
18113
+ /* @__PURE__ */ jsx(
18114
+ "div",
18115
+ {
18116
+ className: styles$1.icon,
18117
+ style: {
18118
+ width: contextMenuIconWidth,
18119
+ color: "var(--gantt-context-menu-text-color)",
18120
+ opacity: disabled ? 0.3 : 0.5
18121
+ },
18122
+ children: icon2
18123
+ }
18124
+ ),
18125
+ /* @__PURE__ */ jsx("div", { className: styles$1.label, children: label2 }),
18126
+ children && children.length > 0 && /* @__PURE__ */ jsx(
18127
+ "div",
18128
+ {
18129
+ style: {
18130
+ marginLeft: 8,
18131
+ opacity: 0.6,
18132
+ pointerEvents: "none"
18133
+ },
18134
+ children: /* @__PURE__ */ jsx("span", { style: { fontSize: "14px" }, children: "▶" })
18135
+ }
18136
+ )
18137
+ ]
18059
18138
  }
18060
18139
  ),
18061
- /* @__PURE__ */ jsx("div", { className: styles$1.label, children: label2 })
18140
+ children && children.length > 0 && hovered && typeof document !== "undefined" && coords && createPortal(
18141
+ /* @__PURE__ */ jsx(
18142
+ "div",
18143
+ {
18144
+ role: "menu",
18145
+ ref: nestedRef,
18146
+ onMouseEnter: () => {
18147
+ if (closeTimeoutRef.current) {
18148
+ window.clearTimeout(closeTimeoutRef.current);
18149
+ closeTimeoutRef.current = null;
18150
+ }
18151
+ setHovered(true);
18152
+ },
18153
+ onMouseLeave: () => {
18154
+ closeTimeoutRef.current = window.setTimeout(() => {
18155
+ setHovered(false);
18156
+ setCoords(null);
18157
+ closeTimeoutRef.current = null;
18158
+ }, 200);
18159
+ },
18160
+ style: {
18161
+ position: "fixed",
18162
+ // place submenu just to the right of parent menu
18163
+ left: (coords.left ?? 0) + 8,
18164
+ top: coords.top,
18165
+ // match the parent menu container look & behavior
18166
+ backgroundColor: "var(--gantt-context-menu-bg-color)",
18167
+ boxShadow: "var(--gantt-context-menu-box-shadow)",
18168
+ borderRadius: "var(--gantt-shape-border-radius)",
18169
+ fontFamily: "var(--gantt-font-family)",
18170
+ display: "flex",
18171
+ flexDirection: "column",
18172
+ width: "max-content",
18173
+ minWidth: coords && coords.parentWidth ? coords.parentWidth : 140,
18174
+ maxHeight: "calc(100vh - 24px)",
18175
+ overflowY: "auto",
18176
+ overflowX: "hidden",
18177
+ gap: 6,
18178
+ zIndex: 1e4,
18179
+ pointerEvents: "auto"
18180
+ },
18181
+ children: children.map((child, index2) => /* @__PURE__ */ jsx(
18182
+ MenuOption,
18183
+ {
18184
+ distances: props.distances,
18185
+ handleAction,
18186
+ option: child,
18187
+ onClose
18188
+ },
18189
+ index2
18190
+ ))
18191
+ }
18192
+ ),
18193
+ document.body
18194
+ )
18062
18195
  ]
18063
18196
  }
18064
18197
  );
@@ -18113,7 +18246,7 @@ function ContextMenu(props) {
18113
18246
  handleCloseContextMenu();
18114
18247
  }
18115
18248
  },
18116
- strategy: "absolute",
18249
+ strategy: "fixed",
18117
18250
  placement: "bottom-start",
18118
18251
  middleware: [
18119
18252
  flip(),
@@ -18138,73 +18271,116 @@ function ContextMenu(props) {
18138
18271
  dismiss,
18139
18272
  role
18140
18273
  ]);
18141
- const floatingRef = useRef();
18274
+ const floatingRef = useRef(null);
18142
18275
  const setFloatingRef = useCallback(
18143
18276
  (el) => {
18144
- floatingRef.current = el || void 0;
18277
+ floatingRef.current = el;
18145
18278
  setFloating(el);
18146
18279
  },
18147
18280
  [setFloating]
18148
18281
  );
18149
- return /* @__PURE__ */ jsxs(Fragment, { children: [
18150
- /* @__PURE__ */ jsx(
18151
- "div",
18152
- {
18153
- ...getReferenceProps(),
18154
- style: {
18155
- position: "absolute",
18156
- left: x,
18157
- top: y,
18158
- zIndex: 1
18159
- },
18160
- ref: setReference
18282
+ useEffect(() => {
18283
+ var _a2;
18284
+ if (!task)
18285
+ return;
18286
+ const cssVars = [
18287
+ "--gantt-context-menu-bg-color",
18288
+ "--gantt-context-menu-box-shadow",
18289
+ "--gantt-shape-border-radius",
18290
+ "--gantt-font-family",
18291
+ "--gantt-font-size",
18292
+ "--gantt-context-menu-empty-color"
18293
+ ];
18294
+ const refEl = ((_a2 = refs == null ? void 0 : refs.reference) == null ? void 0 : _a2.current) ?? null;
18295
+ const sourceEl = refEl || (typeof document !== "undefined" ? document.body : null);
18296
+ if (!sourceEl || !floatingRef.current)
18297
+ return;
18298
+ const cs = getComputedStyle(sourceEl);
18299
+ cssVars.forEach((v) => {
18300
+ const val = cs.getPropertyValue(v).trim();
18301
+ if (val) {
18302
+ floatingRef.current.style.setProperty(v, val);
18161
18303
  }
18162
- ),
18163
- task && /* @__PURE__ */ jsxs(
18164
- "div",
18165
- {
18166
- ref: setFloatingRef,
18167
- style: {
18168
- position: strategy,
18169
- top: menuY ?? 0,
18170
- left: menuX ?? 0,
18171
- width: "max-content",
18172
- backgroundColor: "var(--gantt-context-menu-bg-color)",
18173
- boxShadow: "var(--gantt-context-menu-box-shadow)",
18174
- borderRadius: "var(--gantt-shape-border-radius)",
18175
- fontFamily: "var(--gantt-font-family)",
18176
- display: "flex",
18177
- flexDirection: "column",
18178
- overflow: "hidden",
18179
- gap: 6,
18180
- zIndex: 10
18181
- },
18182
- ...getFloatingProps(),
18183
- children: [
18184
- optionsForRender.map((option, index2) => /* @__PURE__ */ jsx(
18185
- MenuOption,
18186
- {
18187
- onClose: handleCloseContextMenu,
18188
- distances,
18189
- handleAction: handleOptionAction,
18190
- option
18304
+ });
18305
+ }, [task, refs, menuX, menuY]);
18306
+ let viewportX = x;
18307
+ let viewportY = y;
18308
+ if (typeof document !== "undefined") {
18309
+ const ganttWrapper = document.querySelector(
18310
+ '[data-testid="gantt"]'
18311
+ );
18312
+ if (ganttWrapper) {
18313
+ const rect = ganttWrapper.getBoundingClientRect();
18314
+ viewportX = rect.left + x;
18315
+ viewportY = rect.top + y;
18316
+ }
18317
+ }
18318
+ const referenceNode = /* @__PURE__ */ jsx(
18319
+ "div",
18320
+ {
18321
+ ...getReferenceProps(),
18322
+ style: {
18323
+ position: "fixed",
18324
+ left: viewportX,
18325
+ top: viewportY,
18326
+ zIndex: 1,
18327
+ pointerEvents: "none"
18328
+ },
18329
+ ref: setReference
18330
+ }
18331
+ );
18332
+ const menuNode = /* @__PURE__ */ jsxs(
18333
+ "div",
18334
+ {
18335
+ ref: setFloatingRef,
18336
+ style: {
18337
+ position: strategy,
18338
+ top: menuY ?? 0,
18339
+ left: menuX ?? 0,
18340
+ width: "max-content",
18341
+ backgroundColor: "var(--gantt-context-menu-bg-color)",
18342
+ boxShadow: "var(--gantt-context-menu-box-shadow)",
18343
+ borderRadius: "var(--gantt-shape-border-radius)",
18344
+ fontFamily: "var(--gantt-font-family)",
18345
+ display: "flex",
18346
+ flexDirection: "column",
18347
+ // Allow the menu to scroll if it would otherwise overflow the viewport
18348
+ maxHeight: "calc(100vh - 24px)",
18349
+ overflowY: "auto",
18350
+ overflowX: "hidden",
18351
+ gap: 6,
18352
+ zIndex: 1e4,
18353
+ pointerEvents: "auto"
18354
+ },
18355
+ ...getFloatingProps(),
18356
+ children: [
18357
+ optionsForRender.map((option, index2) => /* @__PURE__ */ jsx(
18358
+ MenuOption,
18359
+ {
18360
+ onClose: handleCloseContextMenu,
18361
+ distances,
18362
+ handleAction: handleOptionAction,
18363
+ option
18364
+ },
18365
+ index2
18366
+ )),
18367
+ optionsForRender.length === 0 && /* @__PURE__ */ jsx(
18368
+ "div",
18369
+ {
18370
+ style: {
18371
+ padding: "6px 12px",
18372
+ color: "var(--gantt-context-menu-empty-color, #666)",
18373
+ fontSize: "var(--gantt-font-size)"
18191
18374
  },
18192
- index2
18193
- )),
18194
- optionsForRender.length === 0 && /* @__PURE__ */ jsx(
18195
- "div",
18196
- {
18197
- style: {
18198
- padding: "6px 12px",
18199
- color: "var(--gantt-context-menu-empty-color, #666)",
18200
- fontSize: "var(--gantt-font-size)"
18201
- },
18202
- children: "—"
18203
- }
18204
- )
18205
- ]
18206
- }
18207
- )
18375
+ children: "—"
18376
+ }
18377
+ )
18378
+ ]
18379
+ }
18380
+ );
18381
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
18382
+ referenceNode,
18383
+ task && typeof document !== "undefined" ? createPortal(menuNode, document.body) : task && menuNode
18208
18384
  ] });
18209
18385
  }
18210
18386
  const getParentTasks = (selectedTasks, tasksMap) => {
@@ -15638,7 +15638,7 @@
15638
15638
  overflowX,
15639
15639
  overflowY,
15640
15640
  display
15641
- } = getComputedStyle(element);
15641
+ } = getComputedStyle$1(element);
15642
15642
  return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && !["inline", "contents"].includes(display);
15643
15643
  }
15644
15644
  function isTableElement(element) {
@@ -15646,7 +15646,7 @@
15646
15646
  }
15647
15647
  function isContainingBlock(element) {
15648
15648
  const webkit = isWebKit();
15649
- const css = getComputedStyle(element);
15649
+ const css = getComputedStyle$1(element);
15650
15650
  return css.transform !== "none" || css.perspective !== "none" || (css.containerType ? css.containerType !== "normal" : false) || !webkit && (css.backdropFilter ? css.backdropFilter !== "none" : false) || !webkit && (css.filter ? css.filter !== "none" : false) || ["transform", "perspective", "filter"].some((value) => (css.willChange || "").includes(value)) || ["paint", "layout", "strict", "content"].some((value) => (css.contain || "").includes(value));
15651
15651
  }
15652
15652
  function getContainingBlock(element) {
@@ -15668,7 +15668,7 @@
15668
15668
  function isLastTraversableNode(node) {
15669
15669
  return ["html", "body", "#document"].includes(getNodeName(node));
15670
15670
  }
15671
- function getComputedStyle(element) {
15671
+ function getComputedStyle$1(element) {
15672
15672
  return getWindow(element).getComputedStyle(element);
15673
15673
  }
15674
15674
  function getNodeScroll(element) {
@@ -15723,7 +15723,7 @@
15723
15723
  return list.concat(scrollableAncestor, getOverflowAncestors(scrollableAncestor, [], traverseIframes));
15724
15724
  }
15725
15725
  function getCssDimensions(element) {
15726
- const css = getComputedStyle(element);
15726
+ const css = getComputedStyle$1(element);
15727
15727
  let width = parseFloat(css.width) || 0;
15728
15728
  let height = parseFloat(css.height) || 0;
15729
15729
  const hasOffset = isHTMLElement(element);
@@ -15819,7 +15819,7 @@
15819
15819
  while (currentIFrame && offsetParent && offsetWin !== currentWin) {
15820
15820
  const iframeScale = getScale(currentIFrame);
15821
15821
  const iframeRect = currentIFrame.getBoundingClientRect();
15822
- const css = getComputedStyle(currentIFrame);
15822
+ const css = getComputedStyle$1(currentIFrame);
15823
15823
  const left = iframeRect.left + (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x;
15824
15824
  const top = iframeRect.top + (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y;
15825
15825
  x *= iframeScale.x;
@@ -15901,7 +15901,7 @@
15901
15901
  const height = max(html.scrollHeight, html.clientHeight, body.scrollHeight, body.clientHeight);
15902
15902
  let x = -scroll2.scrollLeft + getWindowScrollBarX(element);
15903
15903
  const y = -scroll2.scrollTop;
15904
- if (getComputedStyle(body).direction === "rtl") {
15904
+ if (getComputedStyle$1(body).direction === "rtl") {
15905
15905
  x += max(html.clientWidth, body.clientWidth) - width;
15906
15906
  }
15907
15907
  return {
@@ -15974,7 +15974,7 @@
15974
15974
  if (parentNode === stopNode || !isElement(parentNode) || isLastTraversableNode(parentNode)) {
15975
15975
  return false;
15976
15976
  }
15977
- return getComputedStyle(parentNode).position === "fixed" || hasFixedPositionAncestor(parentNode, stopNode);
15977
+ return getComputedStyle$1(parentNode).position === "fixed" || hasFixedPositionAncestor(parentNode, stopNode);
15978
15978
  }
15979
15979
  function getClippingElementAncestors(element, cache) {
15980
15980
  const cachedResult = cache.get(element);
@@ -15983,10 +15983,10 @@
15983
15983
  }
15984
15984
  let result = getOverflowAncestors(element, [], false).filter((el) => isElement(el) && getNodeName(el) !== "body");
15985
15985
  let currentContainingBlockComputedStyle = null;
15986
- const elementIsFixed = getComputedStyle(element).position === "fixed";
15986
+ const elementIsFixed = getComputedStyle$1(element).position === "fixed";
15987
15987
  let currentNode = elementIsFixed ? getParentNode(element) : element;
15988
15988
  while (isElement(currentNode) && !isLastTraversableNode(currentNode)) {
15989
- const computedStyle = getComputedStyle(currentNode);
15989
+ const computedStyle = getComputedStyle$1(currentNode);
15990
15990
  const currentNodeIsContaining = isContainingBlock(currentNode);
15991
15991
  if (!currentNodeIsContaining && computedStyle.position === "fixed") {
15992
15992
  currentContainingBlockComputedStyle = null;
@@ -16069,7 +16069,7 @@
16069
16069
  };
16070
16070
  }
16071
16071
  function getTrueOffsetParent(element, polyfill) {
16072
- if (!isHTMLElement(element) || getComputedStyle(element).position === "fixed") {
16072
+ if (!isHTMLElement(element) || getComputedStyle$1(element).position === "fixed") {
16073
16073
  return null;
16074
16074
  }
16075
16075
  if (polyfill) {
@@ -16083,10 +16083,10 @@
16083
16083
  return window2;
16084
16084
  }
16085
16085
  let offsetParent = getTrueOffsetParent(element, polyfill);
16086
- while (offsetParent && isTableElement(offsetParent) && getComputedStyle(offsetParent).position === "static") {
16086
+ while (offsetParent && isTableElement(offsetParent) && getComputedStyle$1(offsetParent).position === "static") {
16087
16087
  offsetParent = getTrueOffsetParent(offsetParent, polyfill);
16088
16088
  }
16089
- if (offsetParent && (getNodeName(offsetParent) === "html" || getNodeName(offsetParent) === "body" && getComputedStyle(offsetParent).position === "static" && !isContainingBlock(offsetParent))) {
16089
+ if (offsetParent && (getNodeName(offsetParent) === "html" || getNodeName(offsetParent) === "body" && getComputedStyle$1(offsetParent).position === "static" && !isContainingBlock(offsetParent))) {
16090
16090
  return window2;
16091
16091
  }
16092
16092
  return offsetParent || getContainingBlock(element) || window2;
@@ -16104,7 +16104,7 @@
16104
16104
  };
16105
16105
  };
16106
16106
  function isRTL(element) {
16107
- return getComputedStyle(element).direction === "rtl";
16107
+ return getComputedStyle$1(element).direction === "rtl";
16108
16108
  }
16109
16109
  const platform = {
16110
16110
  convertOffsetParentRelativeRectToViewportRelativeRect,
@@ -16805,7 +16805,7 @@
16805
16805
  const canScrollY = target.clientHeight > 0 && target.scrollHeight > target.clientHeight;
16806
16806
  let xCond = canScrollY && event.offsetX > target.clientWidth;
16807
16807
  if (canScrollY) {
16808
- const isRTL2 = getComputedStyle(target).direction === "rtl";
16808
+ const isRTL2 = getComputedStyle$1(target).direction === "rtl";
16809
16809
  if (isRTL2) {
16810
16810
  xCond = event.offsetX <= target.offsetWidth - target.clientWidth;
16811
16811
  }
@@ -18036,46 +18036,179 @@
18036
18036
  },
18037
18037
  handleAction,
18038
18038
  option,
18039
- option: { icon: icon2, label: label2, disabled }
18039
+ option: { icon: icon2, label: label2, disabled, children }
18040
18040
  } = props;
18041
+ const [hovered, setHovered] = React.useState(false);
18042
+ const [coords, setCoords] = React.useState(null);
18043
+ const btnRef = React.useRef(null);
18044
+ const nestedRef = React.useRef(null);
18045
+ const closeTimeoutRef = React.useRef(null);
18041
18046
  const onClick = React.useCallback(
18042
18047
  (e) => {
18043
18048
  e.preventDefault();
18044
18049
  if (disabled) {
18045
18050
  return;
18046
18051
  }
18052
+ if (children && children.length > 0) {
18053
+ setHovered(true);
18054
+ return;
18055
+ }
18047
18056
  handleAction(option);
18048
18057
  onClose == null ? void 0 : onClose();
18049
18058
  },
18050
- [onClose, handleAction, option, disabled]
18059
+ [onClose, handleAction, option, disabled, children]
18051
18060
  );
18061
+ React.useEffect(() => {
18062
+ if (!hovered || !nestedRef.current)
18063
+ return;
18064
+ const cssVars = [
18065
+ "--gantt-context-menu-bg-color",
18066
+ "--gantt-context-menu-box-shadow",
18067
+ "--gantt-shape-border-radius",
18068
+ "--gantt-font-family",
18069
+ "--gantt-font-size",
18070
+ "--gantt-context-menu-empty-color"
18071
+ ];
18072
+ const sourceEl = btnRef.current || (typeof document !== "undefined" ? document.body : null);
18073
+ if (!sourceEl)
18074
+ return;
18075
+ const cs = getComputedStyle(sourceEl);
18076
+ cssVars.forEach((v) => {
18077
+ const val = cs.getPropertyValue(v).trim();
18078
+ if (val) {
18079
+ nestedRef.current.style.setProperty(v, val);
18080
+ }
18081
+ });
18082
+ }, [hovered, coords]);
18052
18083
  return /* @__PURE__ */ jsxRuntime.jsxs(
18053
- "button",
18084
+ "div",
18054
18085
  {
18055
- className: styles$1.menuOption,
18056
- "aria-disabled": disabled,
18057
- disabled,
18058
- style: {
18059
- height: contextMenuOptionHeight,
18060
- paddingLeft: contextMenuSidePadding,
18061
- paddingRight: contextMenuSidePadding,
18062
- color: "var(--gantt-context-menu-text-color)"
18086
+ onMouseEnter: () => {
18087
+ var _a, _b;
18088
+ if (closeTimeoutRef.current) {
18089
+ window.clearTimeout(closeTimeoutRef.current);
18090
+ closeTimeoutRef.current = null;
18091
+ }
18092
+ setHovered(true);
18093
+ const rect = (_a = btnRef.current) == null ? void 0 : _a.getBoundingClientRect();
18094
+ const parentMenu = (_b = btnRef.current) == null ? void 0 : _b.closest(
18095
+ '[role="menu"]'
18096
+ );
18097
+ const parentRect = (parentMenu == null ? void 0 : parentMenu.getBoundingClientRect()) ?? null;
18098
+ if (rect) {
18099
+ setCoords({
18100
+ left: parentRect ? parentRect.right : rect.right,
18101
+ top: rect.top,
18102
+ parentWidth: parentRect ? Math.round(parentRect.width) : void 0
18103
+ });
18104
+ }
18063
18105
  },
18064
- onClick,
18106
+ onMouseLeave: () => {
18107
+ closeTimeoutRef.current = window.setTimeout(() => {
18108
+ setHovered(false);
18109
+ setCoords(null);
18110
+ closeTimeoutRef.current = null;
18111
+ }, 200);
18112
+ },
18113
+ style: { position: "relative" },
18065
18114
  children: [
18066
- /* @__PURE__ */ jsxRuntime.jsx(
18067
- "div",
18115
+ /* @__PURE__ */ jsxRuntime.jsxs(
18116
+ "button",
18068
18117
  {
18069
- className: styles$1.icon,
18118
+ className: styles$1.menuOption,
18119
+ "aria-disabled": disabled,
18120
+ disabled,
18070
18121
  style: {
18071
- width: contextMenuIconWidth,
18072
- color: "var(--gantt-context-menu-text-color)",
18073
- opacity: disabled ? 0.3 : 0.5
18122
+ height: contextMenuOptionHeight,
18123
+ paddingLeft: contextMenuSidePadding,
18124
+ paddingRight: contextMenuSidePadding,
18125
+ color: "var(--gantt-context-menu-text-color)"
18074
18126
  },
18075
- children: icon2
18127
+ onClick,
18128
+ ref: btnRef,
18129
+ children: [
18130
+ /* @__PURE__ */ jsxRuntime.jsx(
18131
+ "div",
18132
+ {
18133
+ className: styles$1.icon,
18134
+ style: {
18135
+ width: contextMenuIconWidth,
18136
+ color: "var(--gantt-context-menu-text-color)",
18137
+ opacity: disabled ? 0.3 : 0.5
18138
+ },
18139
+ children: icon2
18140
+ }
18141
+ ),
18142
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles$1.label, children: label2 }),
18143
+ children && children.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
18144
+ "div",
18145
+ {
18146
+ style: {
18147
+ marginLeft: 8,
18148
+ opacity: 0.6,
18149
+ pointerEvents: "none"
18150
+ },
18151
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "14px" }, children: "▶" })
18152
+ }
18153
+ )
18154
+ ]
18076
18155
  }
18077
18156
  ),
18078
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles$1.label, children: label2 })
18157
+ children && children.length > 0 && hovered && typeof document !== "undefined" && coords && ReactDOM.createPortal(
18158
+ /* @__PURE__ */ jsxRuntime.jsx(
18159
+ "div",
18160
+ {
18161
+ role: "menu",
18162
+ ref: nestedRef,
18163
+ onMouseEnter: () => {
18164
+ if (closeTimeoutRef.current) {
18165
+ window.clearTimeout(closeTimeoutRef.current);
18166
+ closeTimeoutRef.current = null;
18167
+ }
18168
+ setHovered(true);
18169
+ },
18170
+ onMouseLeave: () => {
18171
+ closeTimeoutRef.current = window.setTimeout(() => {
18172
+ setHovered(false);
18173
+ setCoords(null);
18174
+ closeTimeoutRef.current = null;
18175
+ }, 200);
18176
+ },
18177
+ style: {
18178
+ position: "fixed",
18179
+ // place submenu just to the right of parent menu
18180
+ left: (coords.left ?? 0) + 8,
18181
+ top: coords.top,
18182
+ // match the parent menu container look & behavior
18183
+ backgroundColor: "var(--gantt-context-menu-bg-color)",
18184
+ boxShadow: "var(--gantt-context-menu-box-shadow)",
18185
+ borderRadius: "var(--gantt-shape-border-radius)",
18186
+ fontFamily: "var(--gantt-font-family)",
18187
+ display: "flex",
18188
+ flexDirection: "column",
18189
+ width: "max-content",
18190
+ minWidth: coords && coords.parentWidth ? coords.parentWidth : 140,
18191
+ maxHeight: "calc(100vh - 24px)",
18192
+ overflowY: "auto",
18193
+ overflowX: "hidden",
18194
+ gap: 6,
18195
+ zIndex: 1e4,
18196
+ pointerEvents: "auto"
18197
+ },
18198
+ children: children.map((child, index2) => /* @__PURE__ */ jsxRuntime.jsx(
18199
+ MenuOption,
18200
+ {
18201
+ distances: props.distances,
18202
+ handleAction,
18203
+ option: child,
18204
+ onClose
18205
+ },
18206
+ index2
18207
+ ))
18208
+ }
18209
+ ),
18210
+ document.body
18211
+ )
18079
18212
  ]
18080
18213
  }
18081
18214
  );
@@ -18130,7 +18263,7 @@
18130
18263
  handleCloseContextMenu();
18131
18264
  }
18132
18265
  },
18133
- strategy: "absolute",
18266
+ strategy: "fixed",
18134
18267
  placement: "bottom-start",
18135
18268
  middleware: [
18136
18269
  flip(),
@@ -18155,73 +18288,116 @@
18155
18288
  dismiss,
18156
18289
  role
18157
18290
  ]);
18158
- const floatingRef = React.useRef();
18291
+ const floatingRef = React.useRef(null);
18159
18292
  const setFloatingRef = React.useCallback(
18160
18293
  (el) => {
18161
- floatingRef.current = el || void 0;
18294
+ floatingRef.current = el;
18162
18295
  setFloating(el);
18163
18296
  },
18164
18297
  [setFloating]
18165
18298
  );
18166
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
18167
- /* @__PURE__ */ jsxRuntime.jsx(
18168
- "div",
18169
- {
18170
- ...getReferenceProps(),
18171
- style: {
18172
- position: "absolute",
18173
- left: x,
18174
- top: y,
18175
- zIndex: 1
18176
- },
18177
- ref: setReference
18299
+ React.useEffect(() => {
18300
+ var _a2;
18301
+ if (!task)
18302
+ return;
18303
+ const cssVars = [
18304
+ "--gantt-context-menu-bg-color",
18305
+ "--gantt-context-menu-box-shadow",
18306
+ "--gantt-shape-border-radius",
18307
+ "--gantt-font-family",
18308
+ "--gantt-font-size",
18309
+ "--gantt-context-menu-empty-color"
18310
+ ];
18311
+ const refEl = ((_a2 = refs == null ? void 0 : refs.reference) == null ? void 0 : _a2.current) ?? null;
18312
+ const sourceEl = refEl || (typeof document !== "undefined" ? document.body : null);
18313
+ if (!sourceEl || !floatingRef.current)
18314
+ return;
18315
+ const cs = getComputedStyle(sourceEl);
18316
+ cssVars.forEach((v) => {
18317
+ const val = cs.getPropertyValue(v).trim();
18318
+ if (val) {
18319
+ floatingRef.current.style.setProperty(v, val);
18178
18320
  }
18179
- ),
18180
- task && /* @__PURE__ */ jsxRuntime.jsxs(
18181
- "div",
18182
- {
18183
- ref: setFloatingRef,
18184
- style: {
18185
- position: strategy,
18186
- top: menuY ?? 0,
18187
- left: menuX ?? 0,
18188
- width: "max-content",
18189
- backgroundColor: "var(--gantt-context-menu-bg-color)",
18190
- boxShadow: "var(--gantt-context-menu-box-shadow)",
18191
- borderRadius: "var(--gantt-shape-border-radius)",
18192
- fontFamily: "var(--gantt-font-family)",
18193
- display: "flex",
18194
- flexDirection: "column",
18195
- overflow: "hidden",
18196
- gap: 6,
18197
- zIndex: 10
18198
- },
18199
- ...getFloatingProps(),
18200
- children: [
18201
- optionsForRender.map((option, index2) => /* @__PURE__ */ jsxRuntime.jsx(
18202
- MenuOption,
18203
- {
18204
- onClose: handleCloseContextMenu,
18205
- distances,
18206
- handleAction: handleOptionAction,
18207
- option
18321
+ });
18322
+ }, [task, refs, menuX, menuY]);
18323
+ let viewportX = x;
18324
+ let viewportY = y;
18325
+ if (typeof document !== "undefined") {
18326
+ const ganttWrapper = document.querySelector(
18327
+ '[data-testid="gantt"]'
18328
+ );
18329
+ if (ganttWrapper) {
18330
+ const rect = ganttWrapper.getBoundingClientRect();
18331
+ viewportX = rect.left + x;
18332
+ viewportY = rect.top + y;
18333
+ }
18334
+ }
18335
+ const referenceNode = /* @__PURE__ */ jsxRuntime.jsx(
18336
+ "div",
18337
+ {
18338
+ ...getReferenceProps(),
18339
+ style: {
18340
+ position: "fixed",
18341
+ left: viewportX,
18342
+ top: viewportY,
18343
+ zIndex: 1,
18344
+ pointerEvents: "none"
18345
+ },
18346
+ ref: setReference
18347
+ }
18348
+ );
18349
+ const menuNode = /* @__PURE__ */ jsxRuntime.jsxs(
18350
+ "div",
18351
+ {
18352
+ ref: setFloatingRef,
18353
+ style: {
18354
+ position: strategy,
18355
+ top: menuY ?? 0,
18356
+ left: menuX ?? 0,
18357
+ width: "max-content",
18358
+ backgroundColor: "var(--gantt-context-menu-bg-color)",
18359
+ boxShadow: "var(--gantt-context-menu-box-shadow)",
18360
+ borderRadius: "var(--gantt-shape-border-radius)",
18361
+ fontFamily: "var(--gantt-font-family)",
18362
+ display: "flex",
18363
+ flexDirection: "column",
18364
+ // Allow the menu to scroll if it would otherwise overflow the viewport
18365
+ maxHeight: "calc(100vh - 24px)",
18366
+ overflowY: "auto",
18367
+ overflowX: "hidden",
18368
+ gap: 6,
18369
+ zIndex: 1e4,
18370
+ pointerEvents: "auto"
18371
+ },
18372
+ ...getFloatingProps(),
18373
+ children: [
18374
+ optionsForRender.map((option, index2) => /* @__PURE__ */ jsxRuntime.jsx(
18375
+ MenuOption,
18376
+ {
18377
+ onClose: handleCloseContextMenu,
18378
+ distances,
18379
+ handleAction: handleOptionAction,
18380
+ option
18381
+ },
18382
+ index2
18383
+ )),
18384
+ optionsForRender.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(
18385
+ "div",
18386
+ {
18387
+ style: {
18388
+ padding: "6px 12px",
18389
+ color: "var(--gantt-context-menu-empty-color, #666)",
18390
+ fontSize: "var(--gantt-font-size)"
18208
18391
  },
18209
- index2
18210
- )),
18211
- optionsForRender.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(
18212
- "div",
18213
- {
18214
- style: {
18215
- padding: "6px 12px",
18216
- color: "var(--gantt-context-menu-empty-color, #666)",
18217
- fontSize: "var(--gantt-font-size)"
18218
- },
18219
- children: "—"
18220
- }
18221
- )
18222
- ]
18223
- }
18224
- )
18392
+ children: "—"
18393
+ }
18394
+ )
18395
+ ]
18396
+ }
18397
+ );
18398
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
18399
+ referenceNode,
18400
+ task && typeof document !== "undefined" ? ReactDOM.createPortal(menuNode, document.body) : task && menuNode
18225
18401
  ] });
18226
18402
  }
18227
18403
  const getParentTasks = (selectedTasks, tasksMap) => {
@@ -580,6 +580,8 @@ export type ContextMenuOptionType = {
580
580
  disabled?: boolean;
581
581
  label: ReactNode;
582
582
  icon?: ReactNode;
583
+ /** Optional nested submenu options. If present this option will render a nested submenu when hovered. */
584
+ children?: ContextMenuOptionType[];
583
585
  };
584
586
  export type CheckTaskIdExistsAtLevel = (newId: string, comparisonLevel?: number) => boolean;
585
587
  export type GetCopiedTaskId = (task: RenderTask, checkExists: (newId: string) => boolean) => string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gantt-task-react-v",
3
- "version": "1.1.15",
3
+ "version": "1.1.17",
4
4
  "description": "Interactive Gantt Chart for React with TypeScript.",
5
5
  "author": "aguilanbon",
6
6
  "homepage": "https://github.com/aguilanbon/gantt-task-react-v",