better-mui-menu 1.5.0 → 1.5.2

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
@@ -80,14 +80,17 @@ export function FileMenu() {
80
80
  }
81
81
  ```
82
82
 
83
- ## Items shape
84
-
85
- `MenuItem` extends `@mui/material/MenuItemProps` (excluding `children`) so you can still pass `dense`, `disabled`, `aria-selected`, etc.
86
- The `better-mui-menu` adds:
87
-
88
- - `type?: 'item' | 'divider'` render a `Divider` when `'divider'` is supplied.
89
- - `id?: string` optional stable ID for ARIA attributes; one is generated automatically otherwise.
90
- - `label: ReactNode` the label shown in the menu row.
91
- - `startIcon` / `endIcon` pass either a `SvgIconComponent` (for example `Save`) or a JSX element (for example `<Save fontSize='large' sx={{ ml: 0.5 }} />`).
92
- - `items?: MenuItem[]` nested entries that render as submenus.
93
- - `onClick?: MenuItemProps['onClick']` callback function when the menu item is clicked.
83
+ ## MenuItem Reference
84
+
85
+ `MenuItem` extends `@mui/material/MenuItemProps` (excluding `children`) so you can still pass `disabled`, `sx`, `aria-*`, `data-*`, etc.
86
+
87
+ | Field | Applies to | Type / values | Required | Default | Notes |
88
+ | --- | --- | --- | --- | --- | --- |
89
+ | `type` | all entries | `'item' \| 'divider' \| 'header'` | no | `'item'` | Use `'divider'` for separators, `'header'` for section labels. |
90
+ | `id` | `'item'` | string | no | auto-generated | Used for stable ARIA/menu item IDs. |
91
+ | `label` | `'item'`, `'header'` | text or JSX | yes (`'item'`, `'header'`) | none | Not used for `'divider'`. |
92
+ | `startIcon` | `'item'` | MUI Icon or JSX | no | none | Example: `Save` or `<Save fontSize='small' />`. |
93
+ | `endIcon` | `'item'` | MUI Icon or JSX | no | none | For submenu triggers, a right-arrow icon is shown when omitted. |
94
+ | `items` | `'item'` | `MenuItem[]` | no | none | If present and non-empty, renders a nested submenu. |
95
+ | `onClick` | `'item'` | function | no | none | Runs on leaf item click; menu stack closes afterward. |
96
+ | `...MenuItemProps` | `'item'` | MUI `MenuItem` props (except `children`, `type`) | no | MUI defaults | Example: `disabled`, `dense`, `selected`, `data-*`. |
package/dist/index.cjs CHANGED
@@ -42,6 +42,8 @@ var ArrowRight_default = (0, import_utils.createSvgIcon)(/* @__PURE__ */ (0, imp
42
42
 
43
43
  // src/Menu/NestedMenuItem.tsx
44
44
  var import_material3 = require("@mui/material");
45
+ var import_Menu = require("@mui/material/Menu");
46
+ var import_styles = require("@mui/material/styles");
45
47
 
46
48
  // src/Menu/MenuEntry.tsx
47
49
  var import_react = require("react");
@@ -83,6 +85,16 @@ var MenuEntry = (0, import_react.forwardRef)(function MenuEntry2({ label, startI
83
85
  // src/Menu/NestedMenuItem.tsx
84
86
  var import_jsx_runtime3 = require("react/jsx-runtime");
85
87
  var isNodeInstance = (target) => target instanceof Node;
88
+ var SubMenuPaper = (0, import_styles.styled)(import_material3.Paper, {
89
+ name: "MuiMenu",
90
+ slot: "Paper",
91
+ overridesResolver: (_, styles) => styles.paper
92
+ })({});
93
+ var SubMenuList = (0, import_styles.styled)(import_material3.MenuList, {
94
+ name: "MuiMenu",
95
+ slot: "List",
96
+ overridesResolver: (_, styles) => styles.list
97
+ })({});
86
98
  var NestedMenuItem = (props) => {
87
99
  const {
88
100
  id: providedId,
@@ -206,6 +218,10 @@ var NestedMenuItem = (props) => {
206
218
  });
207
219
  };
208
220
  const renderedSubMenuItems = items && items.length > 0 ? renderItemsFromData() : renderChildren;
221
+ const subMenuPaperProps = menuProps?.slotProps?.paper || {};
222
+ const { className: subMenuPaperClassName, ...subMenuPaperRestProps } = subMenuPaperProps;
223
+ const subMenuListProps = menuProps?.slotProps?.list || {};
224
+ const { className: subMenuListClassName, ...subMenuListRestProps } = subMenuListProps;
209
225
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
210
226
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
211
227
  MenuEntry,
@@ -223,9 +239,12 @@ var NestedMenuItem = (props) => {
223
239
  if (e.key === "ArrowLeft") {
224
240
  handleClose();
225
241
  }
226
- if (e.key === "ArrowRight" || e.key === "Enter" || e.key === " ") {
242
+ if (e.key === "ArrowRight" || e.key === "Enter") {
227
243
  handleOpen(e);
228
244
  }
245
+ if ((e.key === "ArrowUp" || e.key === "ArrowDown") && open && document.activeElement === menuItemRef.current) {
246
+ handleClose();
247
+ }
229
248
  },
230
249
  "aria-haspopup": "menu",
231
250
  "aria-controls": subMenuId,
@@ -262,34 +281,44 @@ var NestedMenuItem = (props) => {
262
281
  if (isNodeInstance(e.relatedTarget) && menuItemRef.current?.contains(e.relatedTarget)) return;
263
282
  scheduleClose();
264
283
  },
265
- children: ({ TransitionProps }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_material3.Fade, { ...TransitionProps, timeout: transitionConfig.timeout, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_material3.Paper, { elevation: menuProps.elevation, sx: { overflow: "auto" }, ...menuProps?.slotProps?.paper || {}, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
266
- import_material3.MenuList,
284
+ children: ({ TransitionProps }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_material3.Fade, { ...TransitionProps, timeout: transitionConfig.timeout, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
285
+ SubMenuPaper,
267
286
  {
268
- id: subMenuId,
269
- "aria-labelledby": menuItemId,
270
- role: "menu",
271
- ...menuProps?.slotProps?.list || {},
272
- autoFocusItem: true,
273
- onKeyDown: (e) => {
274
- if (e.key === "ArrowLeft") {
275
- const { nativeEvent } = e;
276
- if (nativeEvent.__nestedMenuArrowLeftHandled) {
277
- return;
278
- }
279
- if (!subMenuRef.current?.contains(e.target)) {
280
- return;
281
- }
282
- nativeEvent.__nestedMenuArrowLeftHandled = true;
283
- e.preventDefault();
284
- e.stopPropagation();
285
- nativeEvent.stopImmediatePropagation();
286
- handleClose();
287
- menuItemRef.current?.focus();
287
+ elevation: menuProps.elevation,
288
+ className: [import_Menu.menuClasses.paper, subMenuPaperClassName].filter(Boolean).join(" "),
289
+ sx: { overflow: "auto" },
290
+ ...subMenuPaperRestProps,
291
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
292
+ SubMenuList,
293
+ {
294
+ id: subMenuId,
295
+ "aria-labelledby": menuItemId,
296
+ role: "menu",
297
+ className: [import_Menu.menuClasses.list, subMenuListClassName].filter(Boolean).join(" "),
298
+ ...subMenuListRestProps,
299
+ autoFocusItem: true,
300
+ onKeyDown: (e) => {
301
+ if (e.key === "ArrowLeft") {
302
+ const { nativeEvent } = e;
303
+ if (nativeEvent.__nestedMenuArrowLeftHandled) {
304
+ return;
305
+ }
306
+ if (!subMenuRef.current?.contains(e.target)) {
307
+ return;
308
+ }
309
+ nativeEvent.__nestedMenuArrowLeftHandled = true;
310
+ e.preventDefault();
311
+ e.stopPropagation();
312
+ nativeEvent.stopImmediatePropagation();
313
+ handleClose();
314
+ menuItemRef.current?.focus();
315
+ }
316
+ },
317
+ children: renderedSubMenuItems
288
318
  }
289
- },
290
- children: renderedSubMenuItems
319
+ )
291
320
  }
292
- ) }) })
321
+ ) })
293
322
  }
294
323
  )
295
324
  ] });
package/dist/index.js CHANGED
@@ -16,6 +16,8 @@ var ArrowRight_default = createSvgIcon(/* @__PURE__ */ _jsx("path", {
16
16
 
17
17
  // src/Menu/NestedMenuItem.tsx
18
18
  import { Divider, Fade as Fade2, ListSubheader, MenuItem as MuiMenuItem2, MenuList, Paper, Popper } from "@mui/material";
19
+ import { menuClasses } from "@mui/material/Menu";
20
+ import { styled as styled2 } from "@mui/material/styles";
19
21
 
20
22
  // src/Menu/MenuEntry.tsx
21
23
  import { forwardRef, isValidElement } from "react";
@@ -57,6 +59,16 @@ var MenuEntry = forwardRef(function MenuEntry2({ label, startIcon, endIcon, onCl
57
59
  // src/Menu/NestedMenuItem.tsx
58
60
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
59
61
  var isNodeInstance = (target) => target instanceof Node;
62
+ var SubMenuPaper = styled2(Paper, {
63
+ name: "MuiMenu",
64
+ slot: "Paper",
65
+ overridesResolver: (_, styles) => styles.paper
66
+ })({});
67
+ var SubMenuList = styled2(MenuList, {
68
+ name: "MuiMenu",
69
+ slot: "List",
70
+ overridesResolver: (_, styles) => styles.list
71
+ })({});
60
72
  var NestedMenuItem = (props) => {
61
73
  const {
62
74
  id: providedId,
@@ -180,6 +192,10 @@ var NestedMenuItem = (props) => {
180
192
  });
181
193
  };
182
194
  const renderedSubMenuItems = items && items.length > 0 ? renderItemsFromData() : renderChildren;
195
+ const subMenuPaperProps = menuProps?.slotProps?.paper || {};
196
+ const { className: subMenuPaperClassName, ...subMenuPaperRestProps } = subMenuPaperProps;
197
+ const subMenuListProps = menuProps?.slotProps?.list || {};
198
+ const { className: subMenuListClassName, ...subMenuListRestProps } = subMenuListProps;
183
199
  return /* @__PURE__ */ jsxs2(Fragment, { children: [
184
200
  /* @__PURE__ */ jsx2(
185
201
  MenuEntry,
@@ -197,9 +213,12 @@ var NestedMenuItem = (props) => {
197
213
  if (e.key === "ArrowLeft") {
198
214
  handleClose();
199
215
  }
200
- if (e.key === "ArrowRight" || e.key === "Enter" || e.key === " ") {
216
+ if (e.key === "ArrowRight" || e.key === "Enter") {
201
217
  handleOpen(e);
202
218
  }
219
+ if ((e.key === "ArrowUp" || e.key === "ArrowDown") && open && document.activeElement === menuItemRef.current) {
220
+ handleClose();
221
+ }
203
222
  },
204
223
  "aria-haspopup": "menu",
205
224
  "aria-controls": subMenuId,
@@ -236,34 +255,44 @@ var NestedMenuItem = (props) => {
236
255
  if (isNodeInstance(e.relatedTarget) && menuItemRef.current?.contains(e.relatedTarget)) return;
237
256
  scheduleClose();
238
257
  },
239
- children: ({ TransitionProps }) => /* @__PURE__ */ jsx2(Fade2, { ...TransitionProps, timeout: transitionConfig.timeout, children: /* @__PURE__ */ jsx2(Paper, { elevation: menuProps.elevation, sx: { overflow: "auto" }, ...menuProps?.slotProps?.paper || {}, children: /* @__PURE__ */ jsx2(
240
- MenuList,
258
+ children: ({ TransitionProps }) => /* @__PURE__ */ jsx2(Fade2, { ...TransitionProps, timeout: transitionConfig.timeout, children: /* @__PURE__ */ jsx2(
259
+ SubMenuPaper,
241
260
  {
242
- id: subMenuId,
243
- "aria-labelledby": menuItemId,
244
- role: "menu",
245
- ...menuProps?.slotProps?.list || {},
246
- autoFocusItem: true,
247
- onKeyDown: (e) => {
248
- if (e.key === "ArrowLeft") {
249
- const { nativeEvent } = e;
250
- if (nativeEvent.__nestedMenuArrowLeftHandled) {
251
- return;
252
- }
253
- if (!subMenuRef.current?.contains(e.target)) {
254
- return;
255
- }
256
- nativeEvent.__nestedMenuArrowLeftHandled = true;
257
- e.preventDefault();
258
- e.stopPropagation();
259
- nativeEvent.stopImmediatePropagation();
260
- handleClose();
261
- menuItemRef.current?.focus();
261
+ elevation: menuProps.elevation,
262
+ className: [menuClasses.paper, subMenuPaperClassName].filter(Boolean).join(" "),
263
+ sx: { overflow: "auto" },
264
+ ...subMenuPaperRestProps,
265
+ children: /* @__PURE__ */ jsx2(
266
+ SubMenuList,
267
+ {
268
+ id: subMenuId,
269
+ "aria-labelledby": menuItemId,
270
+ role: "menu",
271
+ className: [menuClasses.list, subMenuListClassName].filter(Boolean).join(" "),
272
+ ...subMenuListRestProps,
273
+ autoFocusItem: true,
274
+ onKeyDown: (e) => {
275
+ if (e.key === "ArrowLeft") {
276
+ const { nativeEvent } = e;
277
+ if (nativeEvent.__nestedMenuArrowLeftHandled) {
278
+ return;
279
+ }
280
+ if (!subMenuRef.current?.contains(e.target)) {
281
+ return;
282
+ }
283
+ nativeEvent.__nestedMenuArrowLeftHandled = true;
284
+ e.preventDefault();
285
+ e.stopPropagation();
286
+ nativeEvent.stopImmediatePropagation();
287
+ handleClose();
288
+ menuItemRef.current?.focus();
289
+ }
290
+ },
291
+ children: renderedSubMenuItems
262
292
  }
263
- },
264
- children: renderedSubMenuItems
293
+ )
265
294
  }
266
- ) }) })
295
+ ) })
267
296
  }
268
297
  )
269
298
  ] });
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.5.0",
12
+ "version": "1.5.2",
13
13
  "type": "module",
14
14
  "sideEffects": false,
15
15
  "files": [