better-mui-menu 1.1.0 → 1.2.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # better-mui-menu
2
2
 
3
- [![CI](https://github.com/eggei/better-mui-menu/actions/workflows/ci.yml/badge.svg)](https://github.com/eggei/better-mui-menu/actions/workflows/ci.yml) [![npm version](https://img.shields.io/npm/v/better-mui-menu.svg?color=brightgreen)](https://www.npmjs.com/package/better-mui-menu) [![npm downloads](https://img.shields.io/npm/dt/better-mui-menu.svg?color=informational)](https://www.npmjs.com/package/better-mui-menu) [![License: MIT](https://img.shields.io/npm/l/better-mui-menu.svg)](https://opensource.org/licenses/MIT)
3
+ [![CI](https://github.com/eggei/better-mui-menu/actions/workflows/ci.yml/badge.svg)](https://github.com/eggei/better-mui-menu/actions/workflows/ci.yml) [![coverage](https://codecov.io/gh/eggei/better-mui-menu/graph/badge.svg)](https://codecov.io/gh/eggei/better-mui-menu) [![npm version](https://img.shields.io/npm/v/better-mui-menu.svg?color=brightgreen)](https://www.npmjs.com/package/better-mui-menu) [![npm downloads](https://img.shields.io/npm/dt/better-mui-menu.svg?color=informational)](https://www.npmjs.com/package/better-mui-menu) [![License: MIT](https://img.shields.io/npm/l/better-mui-menu.svg)](https://opensource.org/licenses/MIT)
4
4
 
5
5
  `better-mui-menu` is a lightweight drop-in for Material UI that keeps a normal `Menu` structure while adding nested menus and full keyboard accessibility so nothing breaks audits or expectations in an MUI app.
6
6
 
@@ -100,6 +100,7 @@ The library lives inside `package/better-mui-menu`.
100
100
 
101
101
  - `npm run dev` – rebuilds `src` into `dist` with `tsup --watch`.
102
102
  - `npm run build` – creates production bundles ready for publication.
103
- - `npm run test` – runs the Jest suite located at `src/Menu/Menu.test.tsx`.
103
+ - `npm run test` – runs the Jest suite located at `src/**/*.test.{ts,tsx}`.
104
+ - `npm run test:coverage` – runs tests with coverage and writes reports to `coverage/`.
104
105
 
105
106
  From the repository root you can use `npm run dev:lib` and `npm run dev:demo` together so the demo app consumes the rebuilt workspace link. Keep `npm run dev` (or `npm run build`) running before refreshing the demo because the Vite app imports the package via `file:`.
package/dist/index.cjs CHANGED
@@ -37,14 +37,12 @@ module.exports = __toCommonJS(index_exports);
37
37
  // src/Menu/index.tsx
38
38
  var import_Menu = __toESM(require("@mui/material/Menu"), 1);
39
39
  var import_Divider2 = __toESM(require("@mui/material/Divider"), 1);
40
- var import_MenuItem2 = __toESM(require("@mui/material/MenuItem"), 1);
41
- var import_react2 = require("react");
42
- var import_material3 = require("@mui/material");
40
+ var import_react3 = require("react");
43
41
 
44
42
  // src/Menu/NestedMenuItem.tsx
45
- var import_react = require("react");
43
+ var import_react2 = require("react");
46
44
  var import_Fade2 = __toESM(require("@mui/material/Fade"), 1);
47
- var import_MenuItem = __toESM(require("@mui/material/MenuItem"), 1);
45
+ var import_MenuItem2 = __toESM(require("@mui/material/MenuItem"), 1);
48
46
  var import_Divider = __toESM(require("@mui/material/Divider"), 1);
49
47
 
50
48
  // ../../node_modules/@mui/icons-material/esm/utils/createSvgIcon.js
@@ -57,6 +55,11 @@ var ArrowRight_default = (0, import_utils.createSvgIcon)(/* @__PURE__ */ (0, imp
57
55
  }), "ArrowRight");
58
56
 
59
57
  // src/Menu/NestedMenuItem.tsx
58
+ var import_material3 = require("@mui/material");
59
+
60
+ // src/Menu/MenuEntry.tsx
61
+ var import_react = require("react");
62
+ var import_MenuItem = __toESM(require("@mui/material/MenuItem"), 1);
60
63
  var import_material2 = require("@mui/material");
61
64
 
62
65
  // src/Menu/common.ts
@@ -71,12 +74,24 @@ var DEFAULT_ELEVATION = 8;
71
74
  var MenuItemContent = (0, import_material.styled)(import_material.Stack)({
72
75
  flexDirection: "row",
73
76
  alignItems: "center",
77
+ justifyContent: "space-between",
78
+ width: "100%",
74
79
  gap: "8px",
75
80
  padding: 0
76
81
  });
77
82
 
78
- // src/Menu/NestedMenuItem.tsx
83
+ // src/Menu/MenuEntry.tsx
79
84
  var import_jsx_runtime2 = require("react/jsx-runtime");
85
+ var MenuEntry = (0, import_react.forwardRef)(function MenuEntry2({ label, startIcon: StartIcon, endIcon: EndIcon, onClick, ...muiMenuItemProps }, ref) {
86
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_MenuItem.default, { ref, ...muiMenuItemProps, onClick, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(MenuItemContent, { children: [
87
+ StartIcon ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StartIcon, {}) : null,
88
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_material2.Typography, { sx: { flex: 1, fontFamily: "inherit" }, children: label }),
89
+ EndIcon ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EndIcon, {}) : null
90
+ ] }) });
91
+ });
92
+
93
+ // src/Menu/NestedMenuItem.tsx
94
+ var import_jsx_runtime3 = require("react/jsx-runtime");
80
95
  var isNodeInstance = (target) => target instanceof Node;
81
96
  var NestedMenuItem = (props) => {
82
97
  const {
@@ -86,29 +101,42 @@ var NestedMenuItem = (props) => {
86
101
  parentMenuClose,
87
102
  children,
88
103
  items,
89
- endIcon: _,
104
+ endIcon: EndIconComponent,
90
105
  menuProps,
91
106
  ...menuItemProps
92
107
  } = props;
93
- const [subMenuAnchorEl, setSubMenuAnchorEl] = (0, import_react.useState)(null);
108
+ const [subMenuAnchorEl, setSubMenuAnchorEl] = (0, import_react2.useState)(null);
94
109
  const open = Boolean(subMenuAnchorEl);
95
- const menuItemRef = (0, import_react.useRef)(null);
96
- const subMenuRef = (0, import_react.useRef)(null);
97
- const generatedId = (0, import_react.useId)();
110
+ const menuItemRef = (0, import_react2.useRef)(null);
111
+ const subMenuRef = (0, import_react2.useRef)(null);
112
+ const generatedId = (0, import_react2.useId)();
98
113
  const menuItemId = providedId ?? `nested-menu-trigger-${generatedId}`;
99
114
  const subMenuId = `${menuItemId}-submenu`;
100
- const closeTimerRef = (0, import_react.useRef)(null);
101
- const clearCloseTimer = (0, import_react.useCallback)(() => {
115
+ const closeTimerRef = (0, import_react2.useRef)(null);
116
+ const clearCloseTimer = (0, import_react2.useCallback)(() => {
102
117
  if (closeTimerRef.current) {
103
118
  clearTimeout(closeTimerRef.current);
104
119
  closeTimerRef.current = null;
105
120
  }
106
121
  }, []);
107
- const handleClose = (0, import_react.useCallback)(() => {
122
+ const handleClose = (0, import_react2.useCallback)(() => {
108
123
  clearCloseTimer();
109
124
  setSubMenuAnchorEl(null);
110
125
  }, [clearCloseTimer]);
111
- const scheduleClose = (0, import_react.useCallback)(() => {
126
+ (0, import_react2.useEffect)(closeSubMenuFasterThanItsParent, [menuProps.open, handleClose]);
127
+ function closeSubMenuFasterThanItsParent() {
128
+ if (!menuProps.open) {
129
+ handleClose();
130
+ }
131
+ }
132
+ (0, import_react2.useEffect)(defensivelyCleanupTimersOnUnmount, [clearCloseTimer]);
133
+ function defensivelyCleanupTimersOnUnmount() {
134
+ return () => {
135
+ clearCloseTimer();
136
+ };
137
+ }
138
+ ;
139
+ const scheduleClose = (0, import_react2.useCallback)(() => {
112
140
  clearCloseTimer();
113
141
  closeTimerRef.current = setTimeout(() => {
114
142
  handleClose();
@@ -118,16 +146,16 @@ var NestedMenuItem = (props) => {
118
146
  clearCloseTimer();
119
147
  setSubMenuAnchorEl(event.currentTarget);
120
148
  };
121
- const renderChildren = import_react.Children.map(children, (child) => {
122
- if (!(0, import_react.isValidElement)(child)) return child;
123
- if (child.type === import_MenuItem.default) {
149
+ const renderChildren = import_react2.Children.map(children, (child) => {
150
+ if (!(0, import_react2.isValidElement)(child)) return child;
151
+ if (child.type === import_MenuItem2.default) {
124
152
  const childOnClick = child.props.onClick;
125
153
  const clonedOnClick = (event) => {
126
154
  childOnClick?.(event);
127
155
  handleClose();
128
156
  parentMenuClose?.(event, "itemClick", menuItemId);
129
157
  };
130
- return (0, import_react.cloneElement)(child, { onClick: clonedOnClick });
158
+ return (0, import_react2.cloneElement)(child, { onClick: clonedOnClick });
131
159
  }
132
160
  return child;
133
161
  });
@@ -135,51 +163,59 @@ var NestedMenuItem = (props) => {
135
163
  if (!items || items.length === 0) return null;
136
164
  return items.map((item, index) => {
137
165
  if (item.type === "divider") {
138
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_Divider.default, {}, `divider-${index}`);
166
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_Divider.default, {}, `divider-${index}`);
139
167
  }
140
168
  const {
141
169
  type: __,
142
170
  items: entryItems,
143
- startIcon: NestedMenuItemStartIcon,
144
- endIcon: NestedMenuItemEndIcon,
171
+ startIcon: EntryStartIcon,
172
+ endIcon: EntryEndIcon,
145
173
  label: entryLabel,
146
- onClick,
147
- id
174
+ onClick: entryOnClick,
175
+ id,
176
+ ...entryMenuItemProps
148
177
  } = item;
149
178
  const entryId = id ?? `${menuItemId}-entry-${index}`;
150
179
  const entryKey = `nested-entry-${entryId}`;
151
180
  const entryLabelValue = entryLabel ?? entryId;
152
181
  if (entryItems && entryItems.length > 0) {
153
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
182
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
154
183
  NestedMenuItem,
155
184
  {
156
185
  id: entryId,
157
186
  label: entryLabelValue,
158
- startIcon: NestedMenuItemStartIcon,
159
- endIcon: NestedMenuItemEndIcon,
187
+ startIcon: EntryStartIcon,
188
+ endIcon: EntryEndIcon,
160
189
  parentMenuClose,
161
190
  items: entryItems,
162
- menuProps
191
+ menuProps,
192
+ ...entryMenuItemProps
163
193
  },
164
194
  entryKey
165
195
  );
166
196
  }
167
197
  const handleItemClick = (event) => {
168
- onClick?.(event);
198
+ entryOnClick?.(event);
169
199
  handleClose();
170
200
  parentMenuClose?.(event, "itemClick", entryId);
171
201
  };
172
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_MenuItem.default, { onClick: handleItemClick, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(MenuItemContent, { children: [
173
- NestedMenuItemStartIcon ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(NestedMenuItemStartIcon, {}) : null,
174
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_material2.Typography, { sx: { flex: 1 }, children: entryLabelValue }),
175
- NestedMenuItemEndIcon ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(NestedMenuItemEndIcon, {}) : null
176
- ] }) }, entryKey);
202
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
203
+ MenuEntry,
204
+ {
205
+ label: entryLabelValue,
206
+ startIcon: EntryStartIcon,
207
+ endIcon: EntryEndIcon,
208
+ ...entryMenuItemProps,
209
+ onClick: handleItemClick
210
+ },
211
+ entryKey
212
+ );
177
213
  });
178
214
  };
179
215
  const renderedSubMenuItems = items && items.length > 0 ? renderItemsFromData() : renderChildren;
180
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
181
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
182
- import_MenuItem.default,
216
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
217
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
218
+ MenuEntry,
183
219
  {
184
220
  "data-testid": `${menuItemId}-trigger`,
185
221
  id: menuItemId,
@@ -201,16 +237,14 @@ var NestedMenuItem = (props) => {
201
237
  "aria-haspopup": "menu",
202
238
  "aria-controls": subMenuId,
203
239
  "aria-expanded": open ? "true" : void 0,
204
- ...menuItemProps,
205
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(MenuItemContent, { children: [
206
- StartIconComponent ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StartIconComponent, {}) : null,
207
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_material2.Typography, { sx: { flex: 1 }, children: label }),
208
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ArrowRight_default, {})
209
- ] })
240
+ label,
241
+ startIcon: StartIconComponent,
242
+ endIcon: EndIconComponent ?? ArrowRight_default,
243
+ ...menuItemProps
210
244
  }
211
245
  ),
212
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
213
- import_material2.Popper,
246
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
247
+ import_material3.Popper,
214
248
  {
215
249
  "data-testid": `${menuItemId}-submenu`,
216
250
  open,
@@ -235,8 +269,8 @@ var NestedMenuItem = (props) => {
235
269
  if (isNodeInstance(e.relatedTarget) && menuItemRef.current?.contains(e.relatedTarget)) return;
236
270
  scheduleClose();
237
271
  },
238
- children: ({ TransitionProps }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_Fade2.default, { ...TransitionProps, timeout: transitionConfig.timeout, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_material2.Paper, { elevation: menuProps.elevation, ...menuProps?.slotProps?.paper || {}, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
239
- import_material2.MenuList,
272
+ children: ({ TransitionProps }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_Fade2.default, { ...TransitionProps, timeout: transitionConfig.timeout, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_material3.Paper, { elevation: menuProps.elevation, ...menuProps?.slotProps?.paper || {}, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
273
+ import_material3.MenuList,
240
274
  {
241
275
  id: subMenuId,
242
276
  "aria-labelledby": menuItemId,
@@ -269,53 +303,60 @@ var NestedMenuItem = (props) => {
269
303
  };
270
304
 
271
305
  // src/Menu/index.tsx
272
- var import_jsx_runtime3 = require("react/jsx-runtime");
306
+ var import_jsx_runtime4 = require("react/jsx-runtime");
273
307
  function Menu({ items, elevation = DEFAULT_ELEVATION, ...rest }) {
274
308
  const menuProps = { elevation, ...rest };
275
- const generatedMenuId = (0, import_react2.useId)();
309
+ const generatedMenuId = (0, import_react3.useId)();
276
310
  const renderedMenuEntries = items.map((item, index) => {
277
311
  if (item.type === "divider") {
278
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_Divider2.default, {}, `divider-${index}`);
312
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_Divider2.default, {}, `divider-${index}`);
279
313
  }
280
314
  const {
281
315
  type: _,
282
- items: nestedItems,
283
- startIcon: StartIconComponent,
284
- endIcon: EndIconComponent,
285
- label,
286
- onClick,
287
316
  id,
288
- ...menuItemProps
317
+ label,
318
+ items: childItems,
319
+ onClick: itemOnClick,
320
+ startIcon: StartIcon,
321
+ endIcon: EndIcon,
322
+ ...muiMenuItemProps
289
323
  } = item;
290
324
  const entryId = id ?? `${generatedMenuId}-entry-${index}`;
291
325
  const itemKey = `menu-item-id:${entryId}`;
292
326
  const displayLabel = label ?? entryId;
293
- if (nestedItems && nestedItems.length > 0) {
294
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
327
+ if (childItems && childItems.length > 0) {
328
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
295
329
  NestedMenuItem,
296
330
  {
297
331
  id: entryId,
298
332
  label: displayLabel,
299
- startIcon: StartIconComponent,
300
- endIcon: EndIconComponent,
333
+ startIcon: StartIcon,
334
+ endIcon: EndIcon,
301
335
  parentMenuClose: menuProps.onClose,
302
- items: nestedItems,
303
- menuProps
336
+ menuProps,
337
+ items: childItems,
338
+ ...muiMenuItemProps
304
339
  },
305
340
  itemKey
306
341
  );
307
342
  }
308
343
  const handleItemClick = (event) => {
309
- onClick?.(event);
344
+ itemOnClick?.(event);
310
345
  menuProps.onClose?.(event, "itemClick", entryId);
311
346
  };
312
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_MenuItem2.default, { onClick: handleItemClick, ...menuItemProps, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuItemContent, { children: [
313
- StartIconComponent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StartIconComponent, {}) : null,
314
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_material3.Typography, { sx: { flex: 1 }, children: displayLabel }),
315
- EndIconComponent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(EndIconComponent, {}) : null
316
- ] }) }, itemKey);
347
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
348
+ MenuEntry,
349
+ {
350
+ label: displayLabel,
351
+ startIcon: StartIcon,
352
+ endIcon: EndIcon,
353
+ ...muiMenuItemProps,
354
+ onClick: handleItemClick
355
+ },
356
+ itemKey
357
+ );
317
358
  });
318
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
359
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
319
360
  import_Menu.default,
320
361
  {
321
362
  "data-testid": "root-menu",
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/Menu/index.tsx","../src/Menu/NestedMenuItem.tsx","../../../node_modules/@mui/icons-material/esm/utils/createSvgIcon.js","../../../node_modules/@mui/icons-material/esm/ArrowRight.js","../src/Menu/common.ts"],"sourcesContent":["import { Menu } from './Menu'\n\nexport type { MenuItem } from './Menu/types'\nexport default Menu\n","import type { MenuProps } from '@mui/material/Menu';\nimport MuiMenu from '@mui/material/Menu';\nimport Divider from '@mui/material/Divider';\nimport MuiMenuItem from '@mui/material/MenuItem';\nimport React, { useId } from 'react';\nimport { Typography } from '@mui/material';\nimport { NestedMenuItem } from './NestedMenuItem';\nimport type { MenuItem } from './types';\nimport { DEFAULT_ELEVATION, MenuItemContent, transitionConfig } from './common';\n\nexport type Props = {\n items: MenuItem[];\n onClose?: (event: React.MouseEvent | React.KeyboardEvent, reason: 'itemClick' | 'escapeKeyDown' | 'backdropClick', menuItemId?: string\n ) => void;\n} & Omit<MenuProps, 'onClose'>;\n\nexport function Menu({ items, elevation = DEFAULT_ELEVATION, ...rest }: Props) {\n const menuProps: Omit<Props, 'items'> = { elevation, ...rest }; // setting the elevation for all nested menus here\n const generatedMenuId = useId();\n\n const renderedMenuEntries = items.map((item, index) => {\n if (item.type === 'divider') {\n return <Divider key={`divider-${index}`} />;\n }\n\n const {\n type: _,\n items: nestedItems,\n startIcon: StartIconComponent,\n endIcon: EndIconComponent,\n label,\n onClick,\n id,\n ...menuItemProps\n } = item;\n const entryId = id ?? `${generatedMenuId}-entry-${index}`;\n const itemKey = `menu-item-id:${entryId}`;\n const displayLabel = label ?? entryId;\n\n if (nestedItems && nestedItems.length > 0) {\n return (\n <NestedMenuItem\n key={itemKey}\n id={entryId}\n label={displayLabel}\n startIcon={StartIconComponent}\n endIcon={EndIconComponent}\n parentMenuClose={menuProps.onClose}\n items={nestedItems}\n menuProps={menuProps}\n />\n );\n }\n\n const handleItemClick = (event: React.MouseEvent<HTMLLIElement>) => {\n onClick?.(event);\n menuProps.onClose?.(event, \"itemClick\", entryId);\n };\n\n return (\n <MuiMenuItem key={itemKey} onClick={handleItemClick} {...menuItemProps}>\n <MenuItemContent>\n {StartIconComponent ? <StartIconComponent /> : null}\n <Typography sx={{ flex: 1 }}>{displayLabel}</Typography>\n {EndIconComponent ? <EndIconComponent /> : null}\n </MenuItemContent>\n </MuiMenuItem>\n );\n });\n\n return (\n <MuiMenu\n data-testid='root-menu'\n {...menuProps}\n transitionDuration={menuProps.transitionDuration || transitionConfig.timeout}\n slots={{ transition: transitionConfig.type, ...menuProps.slots }}\n >\n {renderedMenuEntries}\n </MuiMenu>\n );\n}\n","import type { FC, ReactNode, MouseEvent, KeyboardEvent } from 'react';\nimport { Children, cloneElement, isValidElement, useCallback, useId, useRef, useState } from 'react';\nimport Fade from '@mui/material/Fade';\nimport type { MenuItemProps } from '@mui/material/MenuItem';\nimport MuiMenuItem from '@mui/material/MenuItem';\nimport Divider from '@mui/material/Divider';\nimport ArrowRightIcon from '@mui/icons-material/ArrowRight';\nimport type { SvgIconComponent } from '@mui/icons-material';\nimport type { MenuListProps, MenuProps, PaperProps } from '@mui/material';\nimport { MenuList, Paper, Popper, Typography } from '@mui/material';\nimport type { MenuItem } from './types';\nimport { CLOSE_DELAY, MenuItemContent, transitionConfig } from './common';\nimport type { Props as BetterMenuProps } from '.';\n\ntype NestedMenuItemProps = MenuItemProps & {\n label: ReactNode;\n startIcon?: SvgIconComponent;\n endIcon?: SvgIconComponent;\n parentMenuClose: BetterMenuProps['onClose'];\n children?: ReactNode;\n items?: MenuItem[];\n menuProps: MenuProps;\n};\n\nconst isNodeInstance = (target: EventTarget | null): target is Node => target instanceof Node;\n\nexport const NestedMenuItem: FC<NestedMenuItemProps> = props => {\n const {\n id: providedId,\n label,\n startIcon: StartIconComponent,\n parentMenuClose,\n children,\n items,\n endIcon: _,\n menuProps,\n ...menuItemProps\n } = props;\n const [subMenuAnchorEl, setSubMenuAnchorEl] = useState<null | HTMLElement>(null);\n const open = Boolean(subMenuAnchorEl);\n const menuItemRef = useRef<HTMLLIElement>(null);\n const subMenuRef = useRef<HTMLDivElement>(null);\n const generatedId = useId();\n const menuItemId = providedId ?? `nested-menu-trigger-${generatedId}`;\n const subMenuId = `${menuItemId}-submenu`;\n\n const closeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const clearCloseTimer = useCallback(() => {\n if (closeTimerRef.current) {\n clearTimeout(closeTimerRef.current);\n closeTimerRef.current = null;\n }\n }, []);\n\n const handleClose = useCallback(() => {\n clearCloseTimer();\n setSubMenuAnchorEl(null);\n }, [clearCloseTimer]);\n\n const scheduleClose = useCallback(() => {\n clearCloseTimer();\n closeTimerRef.current = setTimeout(() => {\n handleClose();\n }, CLOSE_DELAY);\n }, [clearCloseTimer, handleClose]);\n\n const handleOpen = (event: MouseEvent<HTMLLIElement> | KeyboardEvent<HTMLLIElement>) => {\n clearCloseTimer();\n setSubMenuAnchorEl(event.currentTarget);\n };\n\n\n // eslint-disable-next-line react-hooks/refs\n const renderChildren = Children.map(children, child => {\n if (!isValidElement(child)) return child;\n\n // Ensure we only process MUI MenuItem children\n if (child.type === MuiMenuItem) {\n const childOnClick = (child.props as MenuItemProps).onClick;\n // Merge any user-defined click logic with the submenu closing behavior.\n const clonedOnClick = (event: MouseEvent<HTMLLIElement>) => {\n childOnClick?.(event);\n handleClose(); // Close the submenu\n parentMenuClose?.(event, \"itemClick\", menuItemId);\n };\n return cloneElement(child, { onClick: clonedOnClick } as Partial<MenuItemProps>);\n }\n return child;\n });\n\n const renderItemsFromData = () => {\n if (!items || items.length === 0) return null;\n\n return items.map((item, index) => {\n if (item.type === 'divider') {\n\n return <Divider key={`divider-${index}`} />;\n }\n\n const {\n type: __,\n items: entryItems,\n startIcon: NestedMenuItemStartIcon,\n endIcon: NestedMenuItemEndIcon,\n label: entryLabel,\n onClick,\n id,\n } = item;\n const entryId = id ?? `${menuItemId}-entry-${index}`;\n const entryKey = `nested-entry-${entryId}`;\n const entryLabelValue = entryLabel ?? entryId;\n\n if (entryItems && entryItems.length > 0) {\n return (\n <NestedMenuItem\n key={entryKey}\n id={entryId}\n label={entryLabelValue}\n startIcon={NestedMenuItemStartIcon}\n endIcon={NestedMenuItemEndIcon}\n parentMenuClose={parentMenuClose}\n items={entryItems}\n menuProps={menuProps}\n />\n );\n }\n\n const handleItemClick = (event: MouseEvent<HTMLLIElement>) => {\n onClick?.(event);\n handleClose();\n parentMenuClose?.(event, \"itemClick\", entryId);\n };\n\n return (\n <MuiMenuItem key={entryKey} onClick={handleItemClick}>\n <MenuItemContent>\n {NestedMenuItemStartIcon ? <NestedMenuItemStartIcon /> : null}\n <Typography sx={{ flex: 1 }}>{entryLabelValue}</Typography>\n {NestedMenuItemEndIcon ? <NestedMenuItemEndIcon /> : null}\n </MenuItemContent>\n </MuiMenuItem>\n );\n });\n };\n\n const renderedSubMenuItems = items && items.length > 0 ? renderItemsFromData() : renderChildren;\n\n return (\n <>\n <MuiMenuItem\n data-testid={`${menuItemId}-trigger`}\n id={menuItemId}\n ref={menuItemRef}\n onMouseEnter={handleOpen}\n onMouseLeave={e => {\n // CRITICAL FEATURE:\n // Checking whether cursor left the menu item onto the related menu. If so, do not close.\n if (isNodeInstance(e.relatedTarget) && subMenuRef.current?.contains(e.relatedTarget)) return;\n // If the cursor leaves to anywhere else, close the submenu.\n scheduleClose();\n }}\n onKeyDown={e => {\n e.preventDefault();\n if (e.key === 'ArrowLeft') {\n handleClose();\n }\n if (e.key === 'ArrowRight' || e.key === 'Enter' || e.key === ' ') {\n handleOpen(e);\n }\n }}\n aria-haspopup='menu'\n aria-controls={subMenuId}\n aria-expanded={open ? 'true' : undefined}\n {...menuItemProps}\n >\n <MenuItemContent>\n {StartIconComponent ? <StartIconComponent /> : null}\n <Typography sx={{ flex: 1 }}>{label}</Typography>\n <ArrowRightIcon />\n </MenuItemContent>\n </MuiMenuItem>\n\n {/**\n * CRITICAL FEATURE: Menu over Menu fails with focus management and keyboard navigations. \n * It is way more complex to make Menu to work compared to Popper. So, I made the decision to use Popper for submenus.\n * This way we can manage focus and keyboard navigation simpler, and we can also avoid some weird edge cases.\n * If you change this to regular Menu, you will realize that you need to add many custom JS logic to manage focus and keyboard navigation,\n * as well as to block Menu's internal logic.\n */}\n <Popper\n data-testid={`${menuItemId}-submenu`}\n open={open}\n ref={subMenuRef}\n anchorEl={subMenuAnchorEl}\n transition\n sx={{ zIndex: t => t.zIndex.modal + 1 }}\n placement='right-start'\n onKeyDown={e => {\n if (e.key === 'ArrowLeft') {\n e.preventDefault();\n handleClose();\n menuItemRef.current?.focus();\n }\n if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n e.preventDefault();\n e.stopPropagation();\n }\n }}\n onMouseEnter={clearCloseTimer}\n onMouseLeave={e => {\n // CRITICAL FEATURE:\n // Checking whether cursor left the submenu onto the related trigger item. If so, do not close.\n if (isNodeInstance(e.relatedTarget) && menuItemRef.current?.contains(e.relatedTarget)) return;\n // If the cursor leaves to anywhere else, close the submenu.\n scheduleClose();\n }}\n >\n {({ TransitionProps }) => (\n <Fade {...TransitionProps} timeout={transitionConfig.timeout}>\n <Paper elevation={menuProps.elevation} {...(menuProps?.slotProps?.paper as PaperProps) || {}}>\n <MenuList\n id={subMenuId}\n aria-labelledby={menuItemId}\n role='menu'\n {...(menuProps?.slotProps?.list as MenuListProps) || {}}\n // What's under is not allowed to be overridden for now\n autoFocusItem\n onKeyDown={e => {\n if (e.key === 'ArrowLeft') {\n const { nativeEvent } = e as KeyboardEvent<HTMLUListElement> & {\n nativeEvent: KeyboardEvent & {\n // our custom flag to avoid duplicate handling in nested menus\n __nestedMenuArrowLeftHandled?: boolean;\n };\n };\n if (nativeEvent.__nestedMenuArrowLeftHandled) {\n // return early if we have already handled this event in a nested menu\n // prevents duplicate logic when the browser bubbles the same keypress through multiple nodes\n return;\n }\n if (!subMenuRef.current?.contains(e.target as Node)) {\n // confirm the event’s target is still inside this submenu;\n // if the keypress originated elsewhere, we don’t continue so other menus can handle it\n return;\n }\n // Mark this event as handled to prevent parent menus from also processing it\n nativeEvent.__nestedMenuArrowLeftHandled = true;\n // preventDefault is needed so the browser doesn’t do its own “arrow left” behavior (like moving the text caret\n // or changing focus) while the menu is managing the navigation. Without this, the closed submenu or focused item\n // might jump unexpectedly even though the menu logic is running.\n e.preventDefault();\n // stopPropagation stops the event from bubbling up to parent DOM nodes that might also have onKeyDown, which could\n // otherwise fight the menu’s own logic or trigger duplicate navigation even though we guard with __nestedMenuArrowLeftHandled\n e.stopPropagation();\n // stopImmediatePropagation keeps any other keydown handlers registered on this same DOM node from running after ours.\n // This is preventing all submenus to close when pressing ArrowLeft in a, say, third level menu. With this, ArrowLeft will only\n // close the current submenu. The __nestedMenuArrowLeftHandled flag only guards against bubbling; this stopImmediatePropagation()\n // ensures no other handlers wired to the same node interfere once we decide to close and focus.\n nativeEvent.stopImmediatePropagation();\n // close the sub menu\n handleClose();\n // move focus back to the parent MenuItem, letting the user continue navigation up the menu hierarchy\n menuItemRef.current?.focus();\n }\n }}\n >\n {renderedSubMenuItems}\n </MenuList>\n </Paper>\n </Fade>\n )}\n </Popper>\n </>\n );\n};\n","'use client';\n\nexport { createSvgIcon as default } from '@mui/material/utils';","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"m10 17 5-5-5-5z\"\n}), 'ArrowRight');","import { Stack, styled } from '@mui/material';\nimport Fade from '@mui/material/Fade';\n\nexport const CLOSE_DELAY = 50;\nexport const transitionConfig = {\n type: Fade,\n timeout: { enter: CLOSE_DELAY + 50, exit: CLOSE_DELAY + 50 },\n};\nexport const DEFAULT_ELEVATION = 8; // this is also what MUI menu uses as default\n\nexport const MenuItemContent = styled(Stack)({\n flexDirection: 'row',\n alignItems: 'center',\n gap: '8px',\n padding: 0,\n});\n"],"mappings":"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","names":["import_Divider","import_MenuItem","import_react","import_material","import_Fade","_jsx","import_material","Fade","import_jsx_runtime","MuiMenuItem","Divider","Fade","import_jsx_runtime","Divider","MuiMenuItem","MuiMenu"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/Menu/index.tsx","../src/Menu/NestedMenuItem.tsx","../../../node_modules/@mui/icons-material/esm/utils/createSvgIcon.js","../../../node_modules/@mui/icons-material/esm/ArrowRight.js","../src/Menu/MenuEntry.tsx","../src/Menu/common.ts"],"sourcesContent":["import { Menu } from './Menu'\n\nexport type { MenuItem } from './Menu/types'\nexport default Menu\n","import type { MenuProps } from '@mui/material/Menu';\nimport MuiMenu from '@mui/material/Menu';\nimport Divider from '@mui/material/Divider';\nimport React, { useId } from 'react';\nimport { NestedMenuItem } from './NestedMenuItem';\nimport { MenuEntry } from './MenuEntry';\nimport type { MenuItem } from './types';\nimport { DEFAULT_ELEVATION, transitionConfig } from './common';\n\nexport type Props = {\n items: MenuItem[];\n onClose?: (event: React.MouseEvent | React.KeyboardEvent, reason: 'itemClick' | 'escapeKeyDown' | 'backdropClick', menuItemId?: string\n ) => void;\n} & Omit<MenuProps, 'onClose'>;\n\nexport function Menu({ items, elevation = DEFAULT_ELEVATION, ...rest }: Props) {\n const menuProps: Omit<Props, 'items'> = { elevation, ...rest }; // setting the elevation for all nested menus here\n const generatedMenuId = useId();\n\n const renderedMenuEntries = items.map((item, index) => {\n if (item.type === 'divider') {\n return <Divider key={`divider-${index}`} />;\n }\n\n const {\n type: _,\n id,\n label,\n items: childItems,\n onClick: itemOnClick,\n startIcon: StartIcon,\n endIcon: EndIcon,\n ...muiMenuItemProps\n } = item;\n const entryId = id ?? `${generatedMenuId}-entry-${index}`;\n const itemKey = `menu-item-id:${entryId}`;\n const displayLabel = label ?? entryId;\n\n if (childItems && childItems.length > 0) {\n return (\n <NestedMenuItem\n key={itemKey}\n id={entryId}\n label={displayLabel}\n startIcon={StartIcon}\n endIcon={EndIcon}\n parentMenuClose={menuProps.onClose}\n menuProps={menuProps}\n items={childItems}\n {...muiMenuItemProps}\n />\n );\n }\n\n const handleItemClick = (event: React.MouseEvent<HTMLLIElement>) => {\n itemOnClick?.(event);\n menuProps.onClose?.(event, \"itemClick\", entryId);\n };\n\n return (\n <MenuEntry\n key={itemKey}\n label={displayLabel}\n startIcon={StartIcon}\n endIcon={EndIcon}\n {...muiMenuItemProps}\n onClick={handleItemClick}\n />\n );\n });\n\n return (\n <MuiMenu\n data-testid='root-menu'\n {...menuProps}\n transitionDuration={menuProps.transitionDuration || transitionConfig.timeout}\n slots={{ transition: transitionConfig.type, ...menuProps.slots }}\n >\n {renderedMenuEntries}\n </MuiMenu>\n );\n}\n","import type { FC, ReactNode, MouseEvent, KeyboardEvent } from 'react';\nimport { Children, cloneElement, isValidElement, useCallback, useEffect, useId, useRef, useState } from 'react';\nimport Fade from '@mui/material/Fade';\nimport type { MenuItemProps } from '@mui/material/MenuItem';\nimport MuiMenuItem from '@mui/material/MenuItem';\nimport Divider from '@mui/material/Divider';\nimport ArrowRightIcon from '@mui/icons-material/ArrowRight';\nimport type { SvgIconComponent } from '@mui/icons-material';\nimport type { MenuListProps, MenuProps, PaperProps } from '@mui/material';\nimport { MenuList, Paper, Popper } from '@mui/material';\nimport type { MenuItem } from './types';\nimport { MenuEntry } from './MenuEntry';\nimport { CLOSE_DELAY, transitionConfig } from './common';\nimport type { Props as BetterMenuProps } from '.';\n\ntype NestedMenuItemProps = MenuItemProps & {\n label: ReactNode;\n startIcon?: SvgIconComponent;\n endIcon?: SvgIconComponent;\n parentMenuClose: BetterMenuProps['onClose'];\n children?: ReactNode;\n items?: MenuItem[];\n menuProps: MenuProps;\n};\n\nconst isNodeInstance = (target: EventTarget | null): target is Node => target instanceof Node;\n\nexport const NestedMenuItem: FC<NestedMenuItemProps> = props => {\n const {\n id: providedId,\n label,\n startIcon: StartIconComponent,\n parentMenuClose,\n children,\n items,\n endIcon: EndIconComponent,\n menuProps,\n ...menuItemProps\n } = props;\n const [subMenuAnchorEl, setSubMenuAnchorEl] = useState<null | HTMLElement>(null);\n const open = Boolean(subMenuAnchorEl);\n const menuItemRef = useRef<HTMLLIElement>(null);\n const subMenuRef = useRef<HTMLDivElement>(null);\n const generatedId = useId();\n const menuItemId = providedId ?? `nested-menu-trigger-${generatedId}`;\n const subMenuId = `${menuItemId}-submenu`;\n\n const closeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const clearCloseTimer = useCallback(() => {\n if (closeTimerRef.current) {\n clearTimeout(closeTimerRef.current);\n closeTimerRef.current = null;\n }\n }, []);\n\n const handleClose = useCallback(() => {\n clearCloseTimer();\n setSubMenuAnchorEl(null);\n }, [clearCloseTimer]);\n\n // CRITICAL FEATURE: This will ensure that all menus close simultaneously when an item is closed. Without this,\n // when an item is clicked in the third- or deeper level menu, menus will close at slightly different times, \n // creating a weird staggered closing effect.\n useEffect(closeSubMenuFasterThanItsParent, [menuProps.open, handleClose]);\n function closeSubMenuFasterThanItsParent() {\n if (!menuProps.open) {\n handleClose();\n }\n }\n\n\n useEffect(defensivelyCleanupTimersOnUnmount, [clearCloseTimer]);\n function defensivelyCleanupTimersOnUnmount() {\n return () => {\n clearCloseTimer();\n }\n };\n\n const scheduleClose = useCallback(() => {\n clearCloseTimer();\n closeTimerRef.current = setTimeout(() => {\n handleClose();\n }, CLOSE_DELAY);\n }, [clearCloseTimer, handleClose]);\n\n const handleOpen = (event: MouseEvent<HTMLLIElement> | KeyboardEvent<HTMLLIElement>) => {\n clearCloseTimer();\n setSubMenuAnchorEl(event.currentTarget);\n };\n\n\n // eslint-disable-next-line react-hooks/refs\n const renderChildren = Children.map(children, child => {\n if (!isValidElement(child)) return child;\n\n // Ensure we only process MUI MenuItem children\n if (child.type === MuiMenuItem) {\n const childOnClick = (child.props as MenuItemProps).onClick;\n // Merge any user-defined click logic with the submenu closing behavior.\n const clonedOnClick = (event: MouseEvent<HTMLLIElement>) => {\n childOnClick?.(event);\n handleClose(); // Close the submenu\n parentMenuClose?.(event, \"itemClick\", menuItemId);\n };\n return cloneElement(child, { onClick: clonedOnClick } as Partial<MenuItemProps>);\n }\n return child;\n });\n\n const renderItemsFromData = () => {\n if (!items || items.length === 0) return null;\n\n return items.map((item, index) => {\n if (item.type === 'divider') {\n\n return <Divider key={`divider-${index}`} />;\n }\n\n const {\n type: __,\n items: entryItems,\n startIcon: EntryStartIcon,\n endIcon: EntryEndIcon,\n label: entryLabel,\n onClick: entryOnClick,\n id,\n ...entryMenuItemProps\n } = item;\n const entryId = id ?? `${menuItemId}-entry-${index}`;\n const entryKey = `nested-entry-${entryId}`;\n const entryLabelValue = entryLabel ?? entryId;\n\n if (entryItems && entryItems.length > 0) {\n return (\n <NestedMenuItem\n key={entryKey}\n id={entryId}\n label={entryLabelValue}\n startIcon={EntryStartIcon}\n endIcon={EntryEndIcon}\n parentMenuClose={parentMenuClose}\n items={entryItems}\n menuProps={menuProps}\n {...entryMenuItemProps}\n />\n );\n }\n\n const handleItemClick = (event: MouseEvent<HTMLLIElement>) => {\n entryOnClick?.(event);\n handleClose();\n parentMenuClose?.(event, \"itemClick\", entryId);\n };\n\n return (\n <MenuEntry\n key={entryKey}\n label={entryLabelValue}\n startIcon={EntryStartIcon}\n endIcon={EntryEndIcon}\n {...entryMenuItemProps}\n onClick={handleItemClick}\n />\n );\n });\n };\n\n const renderedSubMenuItems = items && items.length > 0 ? renderItemsFromData() : renderChildren;\n\n return (\n <>\n <MenuEntry\n data-testid={`${menuItemId}-trigger`}\n id={menuItemId}\n ref={menuItemRef}\n onMouseEnter={handleOpen}\n onMouseLeave={e => {\n // CRITICAL FEATURE:\n // Checking whether cursor left the menu item onto the related menu. If so, do not close.\n if (isNodeInstance(e.relatedTarget) && subMenuRef.current?.contains(e.relatedTarget)) return;\n // If the cursor leaves to anywhere else, close the submenu.\n scheduleClose();\n }}\n onKeyDown={e => {\n e.preventDefault();\n if (e.key === 'ArrowLeft') {\n handleClose();\n }\n if (e.key === 'ArrowRight' || e.key === 'Enter' || e.key === ' ') {\n handleOpen(e);\n }\n }}\n aria-haspopup='menu'\n aria-controls={subMenuId}\n aria-expanded={open ? 'true' : undefined}\n label={label}\n startIcon={StartIconComponent}\n endIcon={EndIconComponent ?? ArrowRightIcon}\n {...menuItemProps}\n />\n\n {/**\n * CRITICAL FEATURE: Menu over Menu fails with focus management and keyboard navigations. \n * It is way more complex to make Menu to work compared to Popper. So, I made the decision to use Popper for submenus.\n * This way we can manage focus and keyboard navigation simpler, and we can also avoid some weird edge cases.\n * If you change this to regular Menu, you will realize that you need to add many custom JS logic to manage focus and keyboard navigation,\n * as well as to block Menu's internal logic.\n */}\n <Popper\n data-testid={`${menuItemId}-submenu`}\n open={open}\n ref={subMenuRef}\n anchorEl={subMenuAnchorEl}\n transition\n sx={{ zIndex: t => t.zIndex.modal + 1 }}\n placement='right-start'\n onKeyDown={e => {\n if (e.key === 'ArrowLeft') {\n e.preventDefault();\n handleClose();\n menuItemRef.current?.focus();\n }\n if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n e.preventDefault();\n e.stopPropagation();\n }\n }}\n onMouseEnter={clearCloseTimer}\n onMouseLeave={e => {\n // CRITICAL FEATURE:\n // Checking whether cursor left the submenu onto the related trigger item. If so, do not close.\n if (isNodeInstance(e.relatedTarget) && menuItemRef.current?.contains(e.relatedTarget)) return;\n // If the cursor leaves to anywhere else, close the submenu.\n scheduleClose();\n }}\n >\n {({ TransitionProps }) => (\n <Fade {...TransitionProps} timeout={transitionConfig.timeout}>\n <Paper elevation={menuProps.elevation} {...(menuProps?.slotProps?.paper as PaperProps) || {}}>\n <MenuList\n id={subMenuId}\n aria-labelledby={menuItemId}\n role='menu'\n {...(menuProps?.slotProps?.list as MenuListProps) || {}}\n // What's under is not allowed to be overridden for now\n autoFocusItem\n onKeyDown={e => {\n if (e.key === 'ArrowLeft') {\n const { nativeEvent } = e as KeyboardEvent<HTMLUListElement> & {\n nativeEvent: KeyboardEvent & {\n // our custom flag to avoid duplicate handling in nested menus\n __nestedMenuArrowLeftHandled?: boolean;\n };\n };\n if (nativeEvent.__nestedMenuArrowLeftHandled) {\n // return early if we have already handled this event in a nested menu\n // prevents duplicate logic when the browser bubbles the same keypress through multiple nodes\n return;\n }\n if (!subMenuRef.current?.contains(e.target as Node)) {\n // confirm the event’s target is still inside this submenu;\n // if the keypress originated elsewhere, we don’t continue so other menus can handle it\n return;\n }\n // Mark this event as handled to prevent parent menus from also processing it\n nativeEvent.__nestedMenuArrowLeftHandled = true;\n // preventDefault is needed so the browser doesn’t do its own “arrow left” behavior (like moving the text caret\n // or changing focus) while the menu is managing the navigation. Without this, the closed submenu or focused item\n // might jump unexpectedly even though the menu logic is running.\n e.preventDefault();\n // stopPropagation stops the event from bubbling up to parent DOM nodes that might also have onKeyDown, which could\n // otherwise fight the menu’s own logic or trigger duplicate navigation even though we guard with __nestedMenuArrowLeftHandled\n e.stopPropagation();\n // stopImmediatePropagation keeps any other keydown handlers registered on this same DOM node from running after ours.\n // This is preventing all submenus to close when pressing ArrowLeft in a, say, third level menu. With this, ArrowLeft will only\n // close the current submenu. The __nestedMenuArrowLeftHandled flag only guards against bubbling; this stopImmediatePropagation()\n // ensures no other handlers wired to the same node interfere once we decide to close and focus.\n nativeEvent.stopImmediatePropagation();\n // close the sub menu\n handleClose();\n // move focus back to the parent MenuItem, letting the user continue navigation up the menu hierarchy\n menuItemRef.current?.focus();\n }\n }}\n >\n {renderedSubMenuItems}\n </MenuList>\n </Paper>\n </Fade>\n )}\n </Popper>\n </>\n );\n};\n","'use client';\n\nexport { createSvgIcon as default } from '@mui/material/utils';","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"m10 17 5-5-5-5z\"\n}), 'ArrowRight');","import type { ReactNode } from 'react';\nimport { forwardRef } from 'react';\nimport type { MenuItemProps } from '@mui/material/MenuItem';\nimport MuiMenuItem from '@mui/material/MenuItem';\nimport { Typography } from '@mui/material';\nimport type { SvgIconComponent } from '@mui/icons-material';\nimport { MenuItemContent } from './common';\n\ntype MenuEntryProps = Omit<MenuItemProps, 'children'> & {\n label: ReactNode;\n startIcon?: SvgIconComponent;\n endIcon?: SvgIconComponent;\n};\n\nexport const MenuEntry = forwardRef<HTMLLIElement, MenuEntryProps>(function MenuEntry(\n { label, startIcon: StartIcon, endIcon: EndIcon, onClick, ...muiMenuItemProps },\n ref\n) {\n return (\n <MuiMenuItem ref={ref} {...muiMenuItemProps} onClick={onClick}>\n <MenuItemContent>\n {StartIcon ? <StartIcon /> : null}\n <Typography sx={{ flex: 1, fontFamily: 'inherit' }}>{label}</Typography>\n {EndIcon ? <EndIcon /> : null}\n </MenuItemContent>\n </MuiMenuItem>\n );\n});\n","import { Stack, styled } from '@mui/material';\nimport Fade from '@mui/material/Fade';\n\nexport const CLOSE_DELAY = 50;\nexport const transitionConfig = {\n type: Fade,\n timeout: { enter: CLOSE_DELAY + 50, exit: CLOSE_DELAY + 50 },\n};\nexport const DEFAULT_ELEVATION = 8; // this is also what MUI menu uses as default\n\nexport const MenuItemContent = styled(Stack)({\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n width: '100%',\n gap: '8px',\n padding: 0,\n});\n"],"mappings":"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","names":["import_Divider","import_react","import_react","import_Fade","import_MenuItem","_jsx","import_material","import_material","Fade","import_jsx_runtime","MenuEntry","MuiMenuItem","import_jsx_runtime","MuiMenuItem","Divider","Fade","import_jsx_runtime","Divider","MuiMenu"]}
package/dist/index.js CHANGED
@@ -1,14 +1,12 @@
1
1
  // src/Menu/index.tsx
2
2
  import MuiMenu from "@mui/material/Menu";
3
3
  import Divider2 from "@mui/material/Divider";
4
- import MuiMenuItem2 from "@mui/material/MenuItem";
5
4
  import { useId as useId2 } from "react";
6
- import { Typography as Typography2 } from "@mui/material";
7
5
 
8
6
  // src/Menu/NestedMenuItem.tsx
9
- import { Children, cloneElement, isValidElement, useCallback, useId, useRef, useState } from "react";
7
+ import { Children, cloneElement, isValidElement, useCallback, useEffect, useId, useRef, useState } from "react";
10
8
  import Fade2 from "@mui/material/Fade";
11
- import MuiMenuItem from "@mui/material/MenuItem";
9
+ import MuiMenuItem2 from "@mui/material/MenuItem";
12
10
  import Divider from "@mui/material/Divider";
13
11
 
14
12
  // ../../node_modules/@mui/icons-material/esm/utils/createSvgIcon.js
@@ -21,7 +19,12 @@ var ArrowRight_default = createSvgIcon(/* @__PURE__ */ _jsx("path", {
21
19
  }), "ArrowRight");
22
20
 
23
21
  // src/Menu/NestedMenuItem.tsx
24
- import { MenuList, Paper, Popper, Typography } from "@mui/material";
22
+ import { MenuList, Paper, Popper } from "@mui/material";
23
+
24
+ // src/Menu/MenuEntry.tsx
25
+ import { forwardRef } from "react";
26
+ import MuiMenuItem from "@mui/material/MenuItem";
27
+ import { Typography } from "@mui/material";
25
28
 
26
29
  // src/Menu/common.ts
27
30
  import { Stack, styled } from "@mui/material";
@@ -35,12 +38,24 @@ var DEFAULT_ELEVATION = 8;
35
38
  var MenuItemContent = styled(Stack)({
36
39
  flexDirection: "row",
37
40
  alignItems: "center",
41
+ justifyContent: "space-between",
42
+ width: "100%",
38
43
  gap: "8px",
39
44
  padding: 0
40
45
  });
41
46
 
47
+ // src/Menu/MenuEntry.tsx
48
+ import { jsx, jsxs } from "react/jsx-runtime";
49
+ var MenuEntry = forwardRef(function MenuEntry2({ label, startIcon: StartIcon, endIcon: EndIcon, onClick, ...muiMenuItemProps }, ref) {
50
+ return /* @__PURE__ */ jsx(MuiMenuItem, { ref, ...muiMenuItemProps, onClick, children: /* @__PURE__ */ jsxs(MenuItemContent, { children: [
51
+ StartIcon ? /* @__PURE__ */ jsx(StartIcon, {}) : null,
52
+ /* @__PURE__ */ jsx(Typography, { sx: { flex: 1, fontFamily: "inherit" }, children: label }),
53
+ EndIcon ? /* @__PURE__ */ jsx(EndIcon, {}) : null
54
+ ] }) });
55
+ });
56
+
42
57
  // src/Menu/NestedMenuItem.tsx
43
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
58
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
44
59
  var isNodeInstance = (target) => target instanceof Node;
45
60
  var NestedMenuItem = (props) => {
46
61
  const {
@@ -50,7 +65,7 @@ var NestedMenuItem = (props) => {
50
65
  parentMenuClose,
51
66
  children,
52
67
  items,
53
- endIcon: _,
68
+ endIcon: EndIconComponent,
54
69
  menuProps,
55
70
  ...menuItemProps
56
71
  } = props;
@@ -72,6 +87,19 @@ var NestedMenuItem = (props) => {
72
87
  clearCloseTimer();
73
88
  setSubMenuAnchorEl(null);
74
89
  }, [clearCloseTimer]);
90
+ useEffect(closeSubMenuFasterThanItsParent, [menuProps.open, handleClose]);
91
+ function closeSubMenuFasterThanItsParent() {
92
+ if (!menuProps.open) {
93
+ handleClose();
94
+ }
95
+ }
96
+ useEffect(defensivelyCleanupTimersOnUnmount, [clearCloseTimer]);
97
+ function defensivelyCleanupTimersOnUnmount() {
98
+ return () => {
99
+ clearCloseTimer();
100
+ };
101
+ }
102
+ ;
75
103
  const scheduleClose = useCallback(() => {
76
104
  clearCloseTimer();
77
105
  closeTimerRef.current = setTimeout(() => {
@@ -84,7 +112,7 @@ var NestedMenuItem = (props) => {
84
112
  };
85
113
  const renderChildren = Children.map(children, (child) => {
86
114
  if (!isValidElement(child)) return child;
87
- if (child.type === MuiMenuItem) {
115
+ if (child.type === MuiMenuItem2) {
88
116
  const childOnClick = child.props.onClick;
89
117
  const clonedOnClick = (event) => {
90
118
  childOnClick?.(event);
@@ -99,51 +127,59 @@ var NestedMenuItem = (props) => {
99
127
  if (!items || items.length === 0) return null;
100
128
  return items.map((item, index) => {
101
129
  if (item.type === "divider") {
102
- return /* @__PURE__ */ jsx(Divider, {}, `divider-${index}`);
130
+ return /* @__PURE__ */ jsx2(Divider, {}, `divider-${index}`);
103
131
  }
104
132
  const {
105
133
  type: __,
106
134
  items: entryItems,
107
- startIcon: NestedMenuItemStartIcon,
108
- endIcon: NestedMenuItemEndIcon,
135
+ startIcon: EntryStartIcon,
136
+ endIcon: EntryEndIcon,
109
137
  label: entryLabel,
110
- onClick,
111
- id
138
+ onClick: entryOnClick,
139
+ id,
140
+ ...entryMenuItemProps
112
141
  } = item;
113
142
  const entryId = id ?? `${menuItemId}-entry-${index}`;
114
143
  const entryKey = `nested-entry-${entryId}`;
115
144
  const entryLabelValue = entryLabel ?? entryId;
116
145
  if (entryItems && entryItems.length > 0) {
117
- return /* @__PURE__ */ jsx(
146
+ return /* @__PURE__ */ jsx2(
118
147
  NestedMenuItem,
119
148
  {
120
149
  id: entryId,
121
150
  label: entryLabelValue,
122
- startIcon: NestedMenuItemStartIcon,
123
- endIcon: NestedMenuItemEndIcon,
151
+ startIcon: EntryStartIcon,
152
+ endIcon: EntryEndIcon,
124
153
  parentMenuClose,
125
154
  items: entryItems,
126
- menuProps
155
+ menuProps,
156
+ ...entryMenuItemProps
127
157
  },
128
158
  entryKey
129
159
  );
130
160
  }
131
161
  const handleItemClick = (event) => {
132
- onClick?.(event);
162
+ entryOnClick?.(event);
133
163
  handleClose();
134
164
  parentMenuClose?.(event, "itemClick", entryId);
135
165
  };
136
- return /* @__PURE__ */ jsx(MuiMenuItem, { onClick: handleItemClick, children: /* @__PURE__ */ jsxs(MenuItemContent, { children: [
137
- NestedMenuItemStartIcon ? /* @__PURE__ */ jsx(NestedMenuItemStartIcon, {}) : null,
138
- /* @__PURE__ */ jsx(Typography, { sx: { flex: 1 }, children: entryLabelValue }),
139
- NestedMenuItemEndIcon ? /* @__PURE__ */ jsx(NestedMenuItemEndIcon, {}) : null
140
- ] }) }, entryKey);
166
+ return /* @__PURE__ */ jsx2(
167
+ MenuEntry,
168
+ {
169
+ label: entryLabelValue,
170
+ startIcon: EntryStartIcon,
171
+ endIcon: EntryEndIcon,
172
+ ...entryMenuItemProps,
173
+ onClick: handleItemClick
174
+ },
175
+ entryKey
176
+ );
141
177
  });
142
178
  };
143
179
  const renderedSubMenuItems = items && items.length > 0 ? renderItemsFromData() : renderChildren;
144
- return /* @__PURE__ */ jsxs(Fragment, { children: [
145
- /* @__PURE__ */ jsx(
146
- MuiMenuItem,
180
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
181
+ /* @__PURE__ */ jsx2(
182
+ MenuEntry,
147
183
  {
148
184
  "data-testid": `${menuItemId}-trigger`,
149
185
  id: menuItemId,
@@ -165,15 +201,13 @@ var NestedMenuItem = (props) => {
165
201
  "aria-haspopup": "menu",
166
202
  "aria-controls": subMenuId,
167
203
  "aria-expanded": open ? "true" : void 0,
168
- ...menuItemProps,
169
- children: /* @__PURE__ */ jsxs(MenuItemContent, { children: [
170
- StartIconComponent ? /* @__PURE__ */ jsx(StartIconComponent, {}) : null,
171
- /* @__PURE__ */ jsx(Typography, { sx: { flex: 1 }, children: label }),
172
- /* @__PURE__ */ jsx(ArrowRight_default, {})
173
- ] })
204
+ label,
205
+ startIcon: StartIconComponent,
206
+ endIcon: EndIconComponent ?? ArrowRight_default,
207
+ ...menuItemProps
174
208
  }
175
209
  ),
176
- /* @__PURE__ */ jsx(
210
+ /* @__PURE__ */ jsx2(
177
211
  Popper,
178
212
  {
179
213
  "data-testid": `${menuItemId}-submenu`,
@@ -199,7 +233,7 @@ var NestedMenuItem = (props) => {
199
233
  if (isNodeInstance(e.relatedTarget) && menuItemRef.current?.contains(e.relatedTarget)) return;
200
234
  scheduleClose();
201
235
  },
202
- children: ({ TransitionProps }) => /* @__PURE__ */ jsx(Fade2, { ...TransitionProps, timeout: transitionConfig.timeout, children: /* @__PURE__ */ jsx(Paper, { elevation: menuProps.elevation, ...menuProps?.slotProps?.paper || {}, children: /* @__PURE__ */ jsx(
236
+ children: ({ TransitionProps }) => /* @__PURE__ */ jsx2(Fade2, { ...TransitionProps, timeout: transitionConfig.timeout, children: /* @__PURE__ */ jsx2(Paper, { elevation: menuProps.elevation, ...menuProps?.slotProps?.paper || {}, children: /* @__PURE__ */ jsx2(
203
237
  MenuList,
204
238
  {
205
239
  id: subMenuId,
@@ -233,53 +267,60 @@ var NestedMenuItem = (props) => {
233
267
  };
234
268
 
235
269
  // src/Menu/index.tsx
236
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
270
+ import { jsx as jsx3 } from "react/jsx-runtime";
237
271
  function Menu({ items, elevation = DEFAULT_ELEVATION, ...rest }) {
238
272
  const menuProps = { elevation, ...rest };
239
273
  const generatedMenuId = useId2();
240
274
  const renderedMenuEntries = items.map((item, index) => {
241
275
  if (item.type === "divider") {
242
- return /* @__PURE__ */ jsx2(Divider2, {}, `divider-${index}`);
276
+ return /* @__PURE__ */ jsx3(Divider2, {}, `divider-${index}`);
243
277
  }
244
278
  const {
245
279
  type: _,
246
- items: nestedItems,
247
- startIcon: StartIconComponent,
248
- endIcon: EndIconComponent,
249
- label,
250
- onClick,
251
280
  id,
252
- ...menuItemProps
281
+ label,
282
+ items: childItems,
283
+ onClick: itemOnClick,
284
+ startIcon: StartIcon,
285
+ endIcon: EndIcon,
286
+ ...muiMenuItemProps
253
287
  } = item;
254
288
  const entryId = id ?? `${generatedMenuId}-entry-${index}`;
255
289
  const itemKey = `menu-item-id:${entryId}`;
256
290
  const displayLabel = label ?? entryId;
257
- if (nestedItems && nestedItems.length > 0) {
258
- return /* @__PURE__ */ jsx2(
291
+ if (childItems && childItems.length > 0) {
292
+ return /* @__PURE__ */ jsx3(
259
293
  NestedMenuItem,
260
294
  {
261
295
  id: entryId,
262
296
  label: displayLabel,
263
- startIcon: StartIconComponent,
264
- endIcon: EndIconComponent,
297
+ startIcon: StartIcon,
298
+ endIcon: EndIcon,
265
299
  parentMenuClose: menuProps.onClose,
266
- items: nestedItems,
267
- menuProps
300
+ menuProps,
301
+ items: childItems,
302
+ ...muiMenuItemProps
268
303
  },
269
304
  itemKey
270
305
  );
271
306
  }
272
307
  const handleItemClick = (event) => {
273
- onClick?.(event);
308
+ itemOnClick?.(event);
274
309
  menuProps.onClose?.(event, "itemClick", entryId);
275
310
  };
276
- return /* @__PURE__ */ jsx2(MuiMenuItem2, { onClick: handleItemClick, ...menuItemProps, children: /* @__PURE__ */ jsxs2(MenuItemContent, { children: [
277
- StartIconComponent ? /* @__PURE__ */ jsx2(StartIconComponent, {}) : null,
278
- /* @__PURE__ */ jsx2(Typography2, { sx: { flex: 1 }, children: displayLabel }),
279
- EndIconComponent ? /* @__PURE__ */ jsx2(EndIconComponent, {}) : null
280
- ] }) }, itemKey);
311
+ return /* @__PURE__ */ jsx3(
312
+ MenuEntry,
313
+ {
314
+ label: displayLabel,
315
+ startIcon: StartIcon,
316
+ endIcon: EndIcon,
317
+ ...muiMenuItemProps,
318
+ onClick: handleItemClick
319
+ },
320
+ itemKey
321
+ );
281
322
  });
282
- return /* @__PURE__ */ jsx2(
323
+ return /* @__PURE__ */ jsx3(
283
324
  MuiMenu,
284
325
  {
285
326
  "data-testid": "root-menu",
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/Menu/index.tsx","../src/Menu/NestedMenuItem.tsx","../../../node_modules/@mui/icons-material/esm/utils/createSvgIcon.js","../../../node_modules/@mui/icons-material/esm/ArrowRight.js","../src/Menu/common.ts","../src/index.ts"],"sourcesContent":["import type { MenuProps } from '@mui/material/Menu';\nimport MuiMenu from '@mui/material/Menu';\nimport Divider from '@mui/material/Divider';\nimport MuiMenuItem from '@mui/material/MenuItem';\nimport React, { useId } from 'react';\nimport { Typography } from '@mui/material';\nimport { NestedMenuItem } from './NestedMenuItem';\nimport type { MenuItem } from './types';\nimport { DEFAULT_ELEVATION, MenuItemContent, transitionConfig } from './common';\n\nexport type Props = {\n items: MenuItem[];\n onClose?: (event: React.MouseEvent | React.KeyboardEvent, reason: 'itemClick' | 'escapeKeyDown' | 'backdropClick', menuItemId?: string\n ) => void;\n} & Omit<MenuProps, 'onClose'>;\n\nexport function Menu({ items, elevation = DEFAULT_ELEVATION, ...rest }: Props) {\n const menuProps: Omit<Props, 'items'> = { elevation, ...rest }; // setting the elevation for all nested menus here\n const generatedMenuId = useId();\n\n const renderedMenuEntries = items.map((item, index) => {\n if (item.type === 'divider') {\n return <Divider key={`divider-${index}`} />;\n }\n\n const {\n type: _,\n items: nestedItems,\n startIcon: StartIconComponent,\n endIcon: EndIconComponent,\n label,\n onClick,\n id,\n ...menuItemProps\n } = item;\n const entryId = id ?? `${generatedMenuId}-entry-${index}`;\n const itemKey = `menu-item-id:${entryId}`;\n const displayLabel = label ?? entryId;\n\n if (nestedItems && nestedItems.length > 0) {\n return (\n <NestedMenuItem\n key={itemKey}\n id={entryId}\n label={displayLabel}\n startIcon={StartIconComponent}\n endIcon={EndIconComponent}\n parentMenuClose={menuProps.onClose}\n items={nestedItems}\n menuProps={menuProps}\n />\n );\n }\n\n const handleItemClick = (event: React.MouseEvent<HTMLLIElement>) => {\n onClick?.(event);\n menuProps.onClose?.(event, \"itemClick\", entryId);\n };\n\n return (\n <MuiMenuItem key={itemKey} onClick={handleItemClick} {...menuItemProps}>\n <MenuItemContent>\n {StartIconComponent ? <StartIconComponent /> : null}\n <Typography sx={{ flex: 1 }}>{displayLabel}</Typography>\n {EndIconComponent ? <EndIconComponent /> : null}\n </MenuItemContent>\n </MuiMenuItem>\n );\n });\n\n return (\n <MuiMenu\n data-testid='root-menu'\n {...menuProps}\n transitionDuration={menuProps.transitionDuration || transitionConfig.timeout}\n slots={{ transition: transitionConfig.type, ...menuProps.slots }}\n >\n {renderedMenuEntries}\n </MuiMenu>\n );\n}\n","import type { FC, ReactNode, MouseEvent, KeyboardEvent } from 'react';\nimport { Children, cloneElement, isValidElement, useCallback, useId, useRef, useState } from 'react';\nimport Fade from '@mui/material/Fade';\nimport type { MenuItemProps } from '@mui/material/MenuItem';\nimport MuiMenuItem from '@mui/material/MenuItem';\nimport Divider from '@mui/material/Divider';\nimport ArrowRightIcon from '@mui/icons-material/ArrowRight';\nimport type { SvgIconComponent } from '@mui/icons-material';\nimport type { MenuListProps, MenuProps, PaperProps } from '@mui/material';\nimport { MenuList, Paper, Popper, Typography } from '@mui/material';\nimport type { MenuItem } from './types';\nimport { CLOSE_DELAY, MenuItemContent, transitionConfig } from './common';\nimport type { Props as BetterMenuProps } from '.';\n\ntype NestedMenuItemProps = MenuItemProps & {\n label: ReactNode;\n startIcon?: SvgIconComponent;\n endIcon?: SvgIconComponent;\n parentMenuClose: BetterMenuProps['onClose'];\n children?: ReactNode;\n items?: MenuItem[];\n menuProps: MenuProps;\n};\n\nconst isNodeInstance = (target: EventTarget | null): target is Node => target instanceof Node;\n\nexport const NestedMenuItem: FC<NestedMenuItemProps> = props => {\n const {\n id: providedId,\n label,\n startIcon: StartIconComponent,\n parentMenuClose,\n children,\n items,\n endIcon: _,\n menuProps,\n ...menuItemProps\n } = props;\n const [subMenuAnchorEl, setSubMenuAnchorEl] = useState<null | HTMLElement>(null);\n const open = Boolean(subMenuAnchorEl);\n const menuItemRef = useRef<HTMLLIElement>(null);\n const subMenuRef = useRef<HTMLDivElement>(null);\n const generatedId = useId();\n const menuItemId = providedId ?? `nested-menu-trigger-${generatedId}`;\n const subMenuId = `${menuItemId}-submenu`;\n\n const closeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const clearCloseTimer = useCallback(() => {\n if (closeTimerRef.current) {\n clearTimeout(closeTimerRef.current);\n closeTimerRef.current = null;\n }\n }, []);\n\n const handleClose = useCallback(() => {\n clearCloseTimer();\n setSubMenuAnchorEl(null);\n }, [clearCloseTimer]);\n\n const scheduleClose = useCallback(() => {\n clearCloseTimer();\n closeTimerRef.current = setTimeout(() => {\n handleClose();\n }, CLOSE_DELAY);\n }, [clearCloseTimer, handleClose]);\n\n const handleOpen = (event: MouseEvent<HTMLLIElement> | KeyboardEvent<HTMLLIElement>) => {\n clearCloseTimer();\n setSubMenuAnchorEl(event.currentTarget);\n };\n\n\n // eslint-disable-next-line react-hooks/refs\n const renderChildren = Children.map(children, child => {\n if (!isValidElement(child)) return child;\n\n // Ensure we only process MUI MenuItem children\n if (child.type === MuiMenuItem) {\n const childOnClick = (child.props as MenuItemProps).onClick;\n // Merge any user-defined click logic with the submenu closing behavior.\n const clonedOnClick = (event: MouseEvent<HTMLLIElement>) => {\n childOnClick?.(event);\n handleClose(); // Close the submenu\n parentMenuClose?.(event, \"itemClick\", menuItemId);\n };\n return cloneElement(child, { onClick: clonedOnClick } as Partial<MenuItemProps>);\n }\n return child;\n });\n\n const renderItemsFromData = () => {\n if (!items || items.length === 0) return null;\n\n return items.map((item, index) => {\n if (item.type === 'divider') {\n\n return <Divider key={`divider-${index}`} />;\n }\n\n const {\n type: __,\n items: entryItems,\n startIcon: NestedMenuItemStartIcon,\n endIcon: NestedMenuItemEndIcon,\n label: entryLabel,\n onClick,\n id,\n } = item;\n const entryId = id ?? `${menuItemId}-entry-${index}`;\n const entryKey = `nested-entry-${entryId}`;\n const entryLabelValue = entryLabel ?? entryId;\n\n if (entryItems && entryItems.length > 0) {\n return (\n <NestedMenuItem\n key={entryKey}\n id={entryId}\n label={entryLabelValue}\n startIcon={NestedMenuItemStartIcon}\n endIcon={NestedMenuItemEndIcon}\n parentMenuClose={parentMenuClose}\n items={entryItems}\n menuProps={menuProps}\n />\n );\n }\n\n const handleItemClick = (event: MouseEvent<HTMLLIElement>) => {\n onClick?.(event);\n handleClose();\n parentMenuClose?.(event, \"itemClick\", entryId);\n };\n\n return (\n <MuiMenuItem key={entryKey} onClick={handleItemClick}>\n <MenuItemContent>\n {NestedMenuItemStartIcon ? <NestedMenuItemStartIcon /> : null}\n <Typography sx={{ flex: 1 }}>{entryLabelValue}</Typography>\n {NestedMenuItemEndIcon ? <NestedMenuItemEndIcon /> : null}\n </MenuItemContent>\n </MuiMenuItem>\n );\n });\n };\n\n const renderedSubMenuItems = items && items.length > 0 ? renderItemsFromData() : renderChildren;\n\n return (\n <>\n <MuiMenuItem\n data-testid={`${menuItemId}-trigger`}\n id={menuItemId}\n ref={menuItemRef}\n onMouseEnter={handleOpen}\n onMouseLeave={e => {\n // CRITICAL FEATURE:\n // Checking whether cursor left the menu item onto the related menu. If so, do not close.\n if (isNodeInstance(e.relatedTarget) && subMenuRef.current?.contains(e.relatedTarget)) return;\n // If the cursor leaves to anywhere else, close the submenu.\n scheduleClose();\n }}\n onKeyDown={e => {\n e.preventDefault();\n if (e.key === 'ArrowLeft') {\n handleClose();\n }\n if (e.key === 'ArrowRight' || e.key === 'Enter' || e.key === ' ') {\n handleOpen(e);\n }\n }}\n aria-haspopup='menu'\n aria-controls={subMenuId}\n aria-expanded={open ? 'true' : undefined}\n {...menuItemProps}\n >\n <MenuItemContent>\n {StartIconComponent ? <StartIconComponent /> : null}\n <Typography sx={{ flex: 1 }}>{label}</Typography>\n <ArrowRightIcon />\n </MenuItemContent>\n </MuiMenuItem>\n\n {/**\n * CRITICAL FEATURE: Menu over Menu fails with focus management and keyboard navigations. \n * It is way more complex to make Menu to work compared to Popper. So, I made the decision to use Popper for submenus.\n * This way we can manage focus and keyboard navigation simpler, and we can also avoid some weird edge cases.\n * If you change this to regular Menu, you will realize that you need to add many custom JS logic to manage focus and keyboard navigation,\n * as well as to block Menu's internal logic.\n */}\n <Popper\n data-testid={`${menuItemId}-submenu`}\n open={open}\n ref={subMenuRef}\n anchorEl={subMenuAnchorEl}\n transition\n sx={{ zIndex: t => t.zIndex.modal + 1 }}\n placement='right-start'\n onKeyDown={e => {\n if (e.key === 'ArrowLeft') {\n e.preventDefault();\n handleClose();\n menuItemRef.current?.focus();\n }\n if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n e.preventDefault();\n e.stopPropagation();\n }\n }}\n onMouseEnter={clearCloseTimer}\n onMouseLeave={e => {\n // CRITICAL FEATURE:\n // Checking whether cursor left the submenu onto the related trigger item. If so, do not close.\n if (isNodeInstance(e.relatedTarget) && menuItemRef.current?.contains(e.relatedTarget)) return;\n // If the cursor leaves to anywhere else, close the submenu.\n scheduleClose();\n }}\n >\n {({ TransitionProps }) => (\n <Fade {...TransitionProps} timeout={transitionConfig.timeout}>\n <Paper elevation={menuProps.elevation} {...(menuProps?.slotProps?.paper as PaperProps) || {}}>\n <MenuList\n id={subMenuId}\n aria-labelledby={menuItemId}\n role='menu'\n {...(menuProps?.slotProps?.list as MenuListProps) || {}}\n // What's under is not allowed to be overridden for now\n autoFocusItem\n onKeyDown={e => {\n if (e.key === 'ArrowLeft') {\n const { nativeEvent } = e as KeyboardEvent<HTMLUListElement> & {\n nativeEvent: KeyboardEvent & {\n // our custom flag to avoid duplicate handling in nested menus\n __nestedMenuArrowLeftHandled?: boolean;\n };\n };\n if (nativeEvent.__nestedMenuArrowLeftHandled) {\n // return early if we have already handled this event in a nested menu\n // prevents duplicate logic when the browser bubbles the same keypress through multiple nodes\n return;\n }\n if (!subMenuRef.current?.contains(e.target as Node)) {\n // confirm the event’s target is still inside this submenu;\n // if the keypress originated elsewhere, we don’t continue so other menus can handle it\n return;\n }\n // Mark this event as handled to prevent parent menus from also processing it\n nativeEvent.__nestedMenuArrowLeftHandled = true;\n // preventDefault is needed so the browser doesn’t do its own “arrow left” behavior (like moving the text caret\n // or changing focus) while the menu is managing the navigation. Without this, the closed submenu or focused item\n // might jump unexpectedly even though the menu logic is running.\n e.preventDefault();\n // stopPropagation stops the event from bubbling up to parent DOM nodes that might also have onKeyDown, which could\n // otherwise fight the menu’s own logic or trigger duplicate navigation even though we guard with __nestedMenuArrowLeftHandled\n e.stopPropagation();\n // stopImmediatePropagation keeps any other keydown handlers registered on this same DOM node from running after ours.\n // This is preventing all submenus to close when pressing ArrowLeft in a, say, third level menu. With this, ArrowLeft will only\n // close the current submenu. The __nestedMenuArrowLeftHandled flag only guards against bubbling; this stopImmediatePropagation()\n // ensures no other handlers wired to the same node interfere once we decide to close and focus.\n nativeEvent.stopImmediatePropagation();\n // close the sub menu\n handleClose();\n // move focus back to the parent MenuItem, letting the user continue navigation up the menu hierarchy\n menuItemRef.current?.focus();\n }\n }}\n >\n {renderedSubMenuItems}\n </MenuList>\n </Paper>\n </Fade>\n )}\n </Popper>\n </>\n );\n};\n","'use client';\n\nexport { createSvgIcon as default } from '@mui/material/utils';","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"m10 17 5-5-5-5z\"\n}), 'ArrowRight');","import { Stack, styled } from '@mui/material';\nimport Fade from '@mui/material/Fade';\n\nexport const CLOSE_DELAY = 50;\nexport const transitionConfig = {\n type: Fade,\n timeout: { enter: CLOSE_DELAY + 50, exit: CLOSE_DELAY + 50 },\n};\nexport const DEFAULT_ELEVATION = 8; // this is also what MUI menu uses as default\n\nexport const MenuItemContent = styled(Stack)({\n flexDirection: 'row',\n alignItems: 'center',\n gap: '8px',\n padding: 0,\n});\n","import { Menu } from './Menu'\n\nexport type { MenuItem } from './Menu/types'\nexport default Menu\n"],"mappings":"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","names":["Divider","MuiMenuItem","useId","Typography","Fade","Fade","jsx","jsxs","useId","Divider","MuiMenuItem","Typography"]}
1
+ {"version":3,"sources":["../src/Menu/index.tsx","../src/Menu/NestedMenuItem.tsx","../../../node_modules/@mui/icons-material/esm/utils/createSvgIcon.js","../../../node_modules/@mui/icons-material/esm/ArrowRight.js","../src/Menu/MenuEntry.tsx","../src/Menu/common.ts","../src/index.ts"],"sourcesContent":["import type { MenuProps } from '@mui/material/Menu';\nimport MuiMenu from '@mui/material/Menu';\nimport Divider from '@mui/material/Divider';\nimport React, { useId } from 'react';\nimport { NestedMenuItem } from './NestedMenuItem';\nimport { MenuEntry } from './MenuEntry';\nimport type { MenuItem } from './types';\nimport { DEFAULT_ELEVATION, transitionConfig } from './common';\n\nexport type Props = {\n items: MenuItem[];\n onClose?: (event: React.MouseEvent | React.KeyboardEvent, reason: 'itemClick' | 'escapeKeyDown' | 'backdropClick', menuItemId?: string\n ) => void;\n} & Omit<MenuProps, 'onClose'>;\n\nexport function Menu({ items, elevation = DEFAULT_ELEVATION, ...rest }: Props) {\n const menuProps: Omit<Props, 'items'> = { elevation, ...rest }; // setting the elevation for all nested menus here\n const generatedMenuId = useId();\n\n const renderedMenuEntries = items.map((item, index) => {\n if (item.type === 'divider') {\n return <Divider key={`divider-${index}`} />;\n }\n\n const {\n type: _,\n id,\n label,\n items: childItems,\n onClick: itemOnClick,\n startIcon: StartIcon,\n endIcon: EndIcon,\n ...muiMenuItemProps\n } = item;\n const entryId = id ?? `${generatedMenuId}-entry-${index}`;\n const itemKey = `menu-item-id:${entryId}`;\n const displayLabel = label ?? entryId;\n\n if (childItems && childItems.length > 0) {\n return (\n <NestedMenuItem\n key={itemKey}\n id={entryId}\n label={displayLabel}\n startIcon={StartIcon}\n endIcon={EndIcon}\n parentMenuClose={menuProps.onClose}\n menuProps={menuProps}\n items={childItems}\n {...muiMenuItemProps}\n />\n );\n }\n\n const handleItemClick = (event: React.MouseEvent<HTMLLIElement>) => {\n itemOnClick?.(event);\n menuProps.onClose?.(event, \"itemClick\", entryId);\n };\n\n return (\n <MenuEntry\n key={itemKey}\n label={displayLabel}\n startIcon={StartIcon}\n endIcon={EndIcon}\n {...muiMenuItemProps}\n onClick={handleItemClick}\n />\n );\n });\n\n return (\n <MuiMenu\n data-testid='root-menu'\n {...menuProps}\n transitionDuration={menuProps.transitionDuration || transitionConfig.timeout}\n slots={{ transition: transitionConfig.type, ...menuProps.slots }}\n >\n {renderedMenuEntries}\n </MuiMenu>\n );\n}\n","import type { FC, ReactNode, MouseEvent, KeyboardEvent } from 'react';\nimport { Children, cloneElement, isValidElement, useCallback, useEffect, useId, useRef, useState } from 'react';\nimport Fade from '@mui/material/Fade';\nimport type { MenuItemProps } from '@mui/material/MenuItem';\nimport MuiMenuItem from '@mui/material/MenuItem';\nimport Divider from '@mui/material/Divider';\nimport ArrowRightIcon from '@mui/icons-material/ArrowRight';\nimport type { SvgIconComponent } from '@mui/icons-material';\nimport type { MenuListProps, MenuProps, PaperProps } from '@mui/material';\nimport { MenuList, Paper, Popper } from '@mui/material';\nimport type { MenuItem } from './types';\nimport { MenuEntry } from './MenuEntry';\nimport { CLOSE_DELAY, transitionConfig } from './common';\nimport type { Props as BetterMenuProps } from '.';\n\ntype NestedMenuItemProps = MenuItemProps & {\n label: ReactNode;\n startIcon?: SvgIconComponent;\n endIcon?: SvgIconComponent;\n parentMenuClose: BetterMenuProps['onClose'];\n children?: ReactNode;\n items?: MenuItem[];\n menuProps: MenuProps;\n};\n\nconst isNodeInstance = (target: EventTarget | null): target is Node => target instanceof Node;\n\nexport const NestedMenuItem: FC<NestedMenuItemProps> = props => {\n const {\n id: providedId,\n label,\n startIcon: StartIconComponent,\n parentMenuClose,\n children,\n items,\n endIcon: EndIconComponent,\n menuProps,\n ...menuItemProps\n } = props;\n const [subMenuAnchorEl, setSubMenuAnchorEl] = useState<null | HTMLElement>(null);\n const open = Boolean(subMenuAnchorEl);\n const menuItemRef = useRef<HTMLLIElement>(null);\n const subMenuRef = useRef<HTMLDivElement>(null);\n const generatedId = useId();\n const menuItemId = providedId ?? `nested-menu-trigger-${generatedId}`;\n const subMenuId = `${menuItemId}-submenu`;\n\n const closeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const clearCloseTimer = useCallback(() => {\n if (closeTimerRef.current) {\n clearTimeout(closeTimerRef.current);\n closeTimerRef.current = null;\n }\n }, []);\n\n const handleClose = useCallback(() => {\n clearCloseTimer();\n setSubMenuAnchorEl(null);\n }, [clearCloseTimer]);\n\n // CRITICAL FEATURE: This will ensure that all menus close simultaneously when an item is closed. Without this,\n // when an item is clicked in the third- or deeper level menu, menus will close at slightly different times, \n // creating a weird staggered closing effect.\n useEffect(closeSubMenuFasterThanItsParent, [menuProps.open, handleClose]);\n function closeSubMenuFasterThanItsParent() {\n if (!menuProps.open) {\n handleClose();\n }\n }\n\n\n useEffect(defensivelyCleanupTimersOnUnmount, [clearCloseTimer]);\n function defensivelyCleanupTimersOnUnmount() {\n return () => {\n clearCloseTimer();\n }\n };\n\n const scheduleClose = useCallback(() => {\n clearCloseTimer();\n closeTimerRef.current = setTimeout(() => {\n handleClose();\n }, CLOSE_DELAY);\n }, [clearCloseTimer, handleClose]);\n\n const handleOpen = (event: MouseEvent<HTMLLIElement> | KeyboardEvent<HTMLLIElement>) => {\n clearCloseTimer();\n setSubMenuAnchorEl(event.currentTarget);\n };\n\n\n // eslint-disable-next-line react-hooks/refs\n const renderChildren = Children.map(children, child => {\n if (!isValidElement(child)) return child;\n\n // Ensure we only process MUI MenuItem children\n if (child.type === MuiMenuItem) {\n const childOnClick = (child.props as MenuItemProps).onClick;\n // Merge any user-defined click logic with the submenu closing behavior.\n const clonedOnClick = (event: MouseEvent<HTMLLIElement>) => {\n childOnClick?.(event);\n handleClose(); // Close the submenu\n parentMenuClose?.(event, \"itemClick\", menuItemId);\n };\n return cloneElement(child, { onClick: clonedOnClick } as Partial<MenuItemProps>);\n }\n return child;\n });\n\n const renderItemsFromData = () => {\n if (!items || items.length === 0) return null;\n\n return items.map((item, index) => {\n if (item.type === 'divider') {\n\n return <Divider key={`divider-${index}`} />;\n }\n\n const {\n type: __,\n items: entryItems,\n startIcon: EntryStartIcon,\n endIcon: EntryEndIcon,\n label: entryLabel,\n onClick: entryOnClick,\n id,\n ...entryMenuItemProps\n } = item;\n const entryId = id ?? `${menuItemId}-entry-${index}`;\n const entryKey = `nested-entry-${entryId}`;\n const entryLabelValue = entryLabel ?? entryId;\n\n if (entryItems && entryItems.length > 0) {\n return (\n <NestedMenuItem\n key={entryKey}\n id={entryId}\n label={entryLabelValue}\n startIcon={EntryStartIcon}\n endIcon={EntryEndIcon}\n parentMenuClose={parentMenuClose}\n items={entryItems}\n menuProps={menuProps}\n {...entryMenuItemProps}\n />\n );\n }\n\n const handleItemClick = (event: MouseEvent<HTMLLIElement>) => {\n entryOnClick?.(event);\n handleClose();\n parentMenuClose?.(event, \"itemClick\", entryId);\n };\n\n return (\n <MenuEntry\n key={entryKey}\n label={entryLabelValue}\n startIcon={EntryStartIcon}\n endIcon={EntryEndIcon}\n {...entryMenuItemProps}\n onClick={handleItemClick}\n />\n );\n });\n };\n\n const renderedSubMenuItems = items && items.length > 0 ? renderItemsFromData() : renderChildren;\n\n return (\n <>\n <MenuEntry\n data-testid={`${menuItemId}-trigger`}\n id={menuItemId}\n ref={menuItemRef}\n onMouseEnter={handleOpen}\n onMouseLeave={e => {\n // CRITICAL FEATURE:\n // Checking whether cursor left the menu item onto the related menu. If so, do not close.\n if (isNodeInstance(e.relatedTarget) && subMenuRef.current?.contains(e.relatedTarget)) return;\n // If the cursor leaves to anywhere else, close the submenu.\n scheduleClose();\n }}\n onKeyDown={e => {\n e.preventDefault();\n if (e.key === 'ArrowLeft') {\n handleClose();\n }\n if (e.key === 'ArrowRight' || e.key === 'Enter' || e.key === ' ') {\n handleOpen(e);\n }\n }}\n aria-haspopup='menu'\n aria-controls={subMenuId}\n aria-expanded={open ? 'true' : undefined}\n label={label}\n startIcon={StartIconComponent}\n endIcon={EndIconComponent ?? ArrowRightIcon}\n {...menuItemProps}\n />\n\n {/**\n * CRITICAL FEATURE: Menu over Menu fails with focus management and keyboard navigations. \n * It is way more complex to make Menu to work compared to Popper. So, I made the decision to use Popper for submenus.\n * This way we can manage focus and keyboard navigation simpler, and we can also avoid some weird edge cases.\n * If you change this to regular Menu, you will realize that you need to add many custom JS logic to manage focus and keyboard navigation,\n * as well as to block Menu's internal logic.\n */}\n <Popper\n data-testid={`${menuItemId}-submenu`}\n open={open}\n ref={subMenuRef}\n anchorEl={subMenuAnchorEl}\n transition\n sx={{ zIndex: t => t.zIndex.modal + 1 }}\n placement='right-start'\n onKeyDown={e => {\n if (e.key === 'ArrowLeft') {\n e.preventDefault();\n handleClose();\n menuItemRef.current?.focus();\n }\n if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n e.preventDefault();\n e.stopPropagation();\n }\n }}\n onMouseEnter={clearCloseTimer}\n onMouseLeave={e => {\n // CRITICAL FEATURE:\n // Checking whether cursor left the submenu onto the related trigger item. If so, do not close.\n if (isNodeInstance(e.relatedTarget) && menuItemRef.current?.contains(e.relatedTarget)) return;\n // If the cursor leaves to anywhere else, close the submenu.\n scheduleClose();\n }}\n >\n {({ TransitionProps }) => (\n <Fade {...TransitionProps} timeout={transitionConfig.timeout}>\n <Paper elevation={menuProps.elevation} {...(menuProps?.slotProps?.paper as PaperProps) || {}}>\n <MenuList\n id={subMenuId}\n aria-labelledby={menuItemId}\n role='menu'\n {...(menuProps?.slotProps?.list as MenuListProps) || {}}\n // What's under is not allowed to be overridden for now\n autoFocusItem\n onKeyDown={e => {\n if (e.key === 'ArrowLeft') {\n const { nativeEvent } = e as KeyboardEvent<HTMLUListElement> & {\n nativeEvent: KeyboardEvent & {\n // our custom flag to avoid duplicate handling in nested menus\n __nestedMenuArrowLeftHandled?: boolean;\n };\n };\n if (nativeEvent.__nestedMenuArrowLeftHandled) {\n // return early if we have already handled this event in a nested menu\n // prevents duplicate logic when the browser bubbles the same keypress through multiple nodes\n return;\n }\n if (!subMenuRef.current?.contains(e.target as Node)) {\n // confirm the event’s target is still inside this submenu;\n // if the keypress originated elsewhere, we don’t continue so other menus can handle it\n return;\n }\n // Mark this event as handled to prevent parent menus from also processing it\n nativeEvent.__nestedMenuArrowLeftHandled = true;\n // preventDefault is needed so the browser doesn’t do its own “arrow left” behavior (like moving the text caret\n // or changing focus) while the menu is managing the navigation. Without this, the closed submenu or focused item\n // might jump unexpectedly even though the menu logic is running.\n e.preventDefault();\n // stopPropagation stops the event from bubbling up to parent DOM nodes that might also have onKeyDown, which could\n // otherwise fight the menu’s own logic or trigger duplicate navigation even though we guard with __nestedMenuArrowLeftHandled\n e.stopPropagation();\n // stopImmediatePropagation keeps any other keydown handlers registered on this same DOM node from running after ours.\n // This is preventing all submenus to close when pressing ArrowLeft in a, say, third level menu. With this, ArrowLeft will only\n // close the current submenu. The __nestedMenuArrowLeftHandled flag only guards against bubbling; this stopImmediatePropagation()\n // ensures no other handlers wired to the same node interfere once we decide to close and focus.\n nativeEvent.stopImmediatePropagation();\n // close the sub menu\n handleClose();\n // move focus back to the parent MenuItem, letting the user continue navigation up the menu hierarchy\n menuItemRef.current?.focus();\n }\n }}\n >\n {renderedSubMenuItems}\n </MenuList>\n </Paper>\n </Fade>\n )}\n </Popper>\n </>\n );\n};\n","'use client';\n\nexport { createSvgIcon as default } from '@mui/material/utils';","\"use client\";\n\nimport createSvgIcon from \"./utils/createSvgIcon.js\";\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon(/*#__PURE__*/_jsx(\"path\", {\n d: \"m10 17 5-5-5-5z\"\n}), 'ArrowRight');","import type { ReactNode } from 'react';\nimport { forwardRef } from 'react';\nimport type { MenuItemProps } from '@mui/material/MenuItem';\nimport MuiMenuItem from '@mui/material/MenuItem';\nimport { Typography } from '@mui/material';\nimport type { SvgIconComponent } from '@mui/icons-material';\nimport { MenuItemContent } from './common';\n\ntype MenuEntryProps = Omit<MenuItemProps, 'children'> & {\n label: ReactNode;\n startIcon?: SvgIconComponent;\n endIcon?: SvgIconComponent;\n};\n\nexport const MenuEntry = forwardRef<HTMLLIElement, MenuEntryProps>(function MenuEntry(\n { label, startIcon: StartIcon, endIcon: EndIcon, onClick, ...muiMenuItemProps },\n ref\n) {\n return (\n <MuiMenuItem ref={ref} {...muiMenuItemProps} onClick={onClick}>\n <MenuItemContent>\n {StartIcon ? <StartIcon /> : null}\n <Typography sx={{ flex: 1, fontFamily: 'inherit' }}>{label}</Typography>\n {EndIcon ? <EndIcon /> : null}\n </MenuItemContent>\n </MuiMenuItem>\n );\n});\n","import { Stack, styled } from '@mui/material';\nimport Fade from '@mui/material/Fade';\n\nexport const CLOSE_DELAY = 50;\nexport const transitionConfig = {\n type: Fade,\n timeout: { enter: CLOSE_DELAY + 50, exit: CLOSE_DELAY + 50 },\n};\nexport const DEFAULT_ELEVATION = 8; // this is also what MUI menu uses as default\n\nexport const MenuItemContent = styled(Stack)({\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n width: '100%',\n gap: '8px',\n padding: 0,\n});\n","import { Menu } from './Menu'\n\nexport type { MenuItem } from './Menu/types'\nexport default Menu\n"],"mappings":"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","names":["Divider","useId","Fade","MuiMenuItem","MenuEntry","jsx","jsxs","MuiMenuItem","Fade","jsx","useId","Divider"]}
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "bugs": {
10
10
  "url": "https://github.com/eggei/better-mui-menu/issues"
11
11
  },
12
- "version": "1.1.0",
12
+ "version": "1.2.0",
13
13
  "type": "module",
14
14
  "sideEffects": false,
15
15
  "files": [
@@ -30,7 +30,8 @@
30
30
  "build": "tsup",
31
31
  "dev": "tsup --watch",
32
32
  "prepublishOnly": "npm --prefix ../.. run test && npm run build",
33
- "test": "jest"
33
+ "test": "jest",
34
+ "test:coverage": "jest --coverage --coverageReporters=json-summary --coverageReporters=lcov --coverageReporters=text --coverageReporters=clover"
34
35
  },
35
36
  "keywords": [
36
37
  "mui",