gantt-task-react-v 1.1.16 → 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.
@@ -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
  );
@@ -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
  );
@@ -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.16",
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",