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 +3 -2
- package/dist/index.cjs +110 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +96 -55
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# better-mui-menu
|
|
2
2
|
|
|
3
|
-
[](https://github.com/eggei/better-mui-menu/actions/workflows/ci.yml) [](https://www.npmjs.com/package/better-mui-menu) [](https://www.npmjs.com/package/better-mui-menu) [](https://opensource.org/licenses/MIT)
|
|
3
|
+
[](https://github.com/eggei/better-mui-menu/actions/workflows/ci.yml) [](https://codecov.io/gh/eggei/better-mui-menu) [](https://www.npmjs.com/package/better-mui-menu) [](https://www.npmjs.com/package/better-mui-menu) [](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
|
|
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
|
|
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
|
|
43
|
+
var import_react2 = require("react");
|
|
46
44
|
var import_Fade2 = __toESM(require("@mui/material/Fade"), 1);
|
|
47
|
-
var
|
|
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/
|
|
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,
|
|
108
|
+
const [subMenuAnchorEl, setSubMenuAnchorEl] = (0, import_react2.useState)(null);
|
|
94
109
|
const open = Boolean(subMenuAnchorEl);
|
|
95
|
-
const menuItemRef = (0,
|
|
96
|
-
const subMenuRef = (0,
|
|
97
|
-
const generatedId = (0,
|
|
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,
|
|
101
|
-
const clearCloseTimer = (0,
|
|
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,
|
|
122
|
+
const handleClose = (0, import_react2.useCallback)(() => {
|
|
108
123
|
clearCloseTimer();
|
|
109
124
|
setSubMenuAnchorEl(null);
|
|
110
125
|
}, [clearCloseTimer]);
|
|
111
|
-
|
|
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 =
|
|
122
|
-
if (!(0,
|
|
123
|
-
if (child.type ===
|
|
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,
|
|
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,
|
|
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:
|
|
144
|
-
endIcon:
|
|
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,
|
|
182
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
154
183
|
NestedMenuItem,
|
|
155
184
|
{
|
|
156
185
|
id: entryId,
|
|
157
186
|
label: entryLabelValue,
|
|
158
|
-
startIcon:
|
|
159
|
-
endIcon:
|
|
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
|
-
|
|
198
|
+
entryOnClick?.(event);
|
|
169
199
|
handleClose();
|
|
170
200
|
parentMenuClose?.(event, "itemClick", entryId);
|
|
171
201
|
};
|
|
172
|
-
return /* @__PURE__ */ (0,
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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,
|
|
181
|
-
/* @__PURE__ */ (0,
|
|
182
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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,
|
|
213
|
-
|
|
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,
|
|
239
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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 (
|
|
294
|
-
return /* @__PURE__ */ (0,
|
|
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:
|
|
300
|
-
endIcon:
|
|
333
|
+
startIcon: StartIcon,
|
|
334
|
+
endIcon: EndIcon,
|
|
301
335
|
parentMenuClose: menuProps.onClose,
|
|
302
|
-
|
|
303
|
-
|
|
336
|
+
menuProps,
|
|
337
|
+
items: childItems,
|
|
338
|
+
...muiMenuItemProps
|
|
304
339
|
},
|
|
305
340
|
itemKey
|
|
306
341
|
);
|
|
307
342
|
}
|
|
308
343
|
const handleItemClick = (event) => {
|
|
309
|
-
|
|
344
|
+
itemOnClick?.(event);
|
|
310
345
|
menuProps.onClose?.(event, "itemClick", entryId);
|
|
311
346
|
};
|
|
312
|
-
return /* @__PURE__ */ (0,
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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,
|
|
359
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
319
360
|
import_Menu.default,
|
|
320
361
|
{
|
|
321
362
|
"data-testid": "root-menu",
|
package/dist/index.cjs.map
CHANGED
|
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCA,kBAAoB;AACpB,IAAAA,kBAAoB;AACpB,IAAAC,mBAAwB;AACxB,IAAAC,gBAA6B;AAC7B,IAAAC,mBAA2B;;;ACJ3B,mBAA6F;AAC7F,IAAAC,eAAiB;AAEjB,sBAAwB;AACxB,qBAAoB;;;ACHpB,mBAAyC;;;ACCzC,yBAA4B;AAC5B,IAAO,yBAAQ,4BAA2B,uCAAAC,KAAK,QAAQ;AAAA,EACrD,GAAG;AACL,CAAC,GAAG,YAAY;;;AFGhB,IAAAC,mBAAoD;;;AGTpD,sBAA8B;AAC9B,kBAAiB;AAEV,IAAM,cAAc;AACpB,IAAM,mBAAmB;AAAA,EAC9B,MAAM,YAAAC;AAAA,EACN,SAAS,EAAE,OAAO,cAAc,IAAI,MAAM,cAAc,GAAG;AAC7D;AACO,IAAM,oBAAoB;AAE1B,IAAM,sBAAkB,wBAAO,qBAAK,EAAE;AAAA,EAC3C,eAAe;AAAA,EACf,YAAY;AAAA,EACZ,KAAK;AAAA,EACL,SAAS;AACX,CAAC;;;AHkFc,IAAAC,sBAAA;AAzEf,IAAM,iBAAiB,CAAC,WAA+C,kBAAkB;AAElF,IAAM,iBAA0C,WAAS;AAC9D,QAAM;AAAA,IACJ,IAAI;AAAA,IACJ;AAAA,IACA,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS;AAAA,IACT;AAAA,IACA,GAAG;AAAA,EACL,IAAI;AACJ,QAAM,CAAC,iBAAiB,kBAAkB,QAAI,uBAA6B,IAAI;AAC/E,QAAM,OAAO,QAAQ,eAAe;AACpC,QAAM,kBAAc,qBAAsB,IAAI;AAC9C,QAAM,iBAAa,qBAAuB,IAAI;AAC9C,QAAM,kBAAc,oBAAM;AAC1B,QAAM,aAAa,cAAc,uBAAuB,WAAW;AACnE,QAAM,YAAY,GAAG,UAAU;AAE/B,QAAM,oBAAgB,qBAA6C,IAAI;AAEvE,QAAM,sBAAkB,0BAAY,MAAM;AACxC,QAAI,cAAc,SAAS;AACzB,mBAAa,cAAc,OAAO;AAClC,oBAAc,UAAU;AAAA,IAC1B;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,kBAAc,0BAAY,MAAM;AACpC,oBAAgB;AAChB,uBAAmB,IAAI;AAAA,EACzB,GAAG,CAAC,eAAe,CAAC;AAEpB,QAAM,oBAAgB,0BAAY,MAAM;AACtC,oBAAgB;AAChB,kBAAc,UAAU,WAAW,MAAM;AACvC,kBAAY;AAAA,IACd,GAAG,WAAW;AAAA,EAChB,GAAG,CAAC,iBAAiB,WAAW,CAAC;AAEjC,QAAM,aAAa,CAAC,UAAoE;AACtF,oBAAgB;AAChB,uBAAmB,MAAM,aAAa;AAAA,EACxC;AAIA,QAAM,iBAAiB,sBAAS,IAAI,UAAU,WAAS;AACrD,QAAI,KAAC,6BAAe,KAAK,EAAG,QAAO;AAGnC,QAAI,MAAM,SAAS,gBAAAC,SAAa;AAC9B,YAAM,eAAgB,MAAM,MAAwB;AAEpD,YAAM,gBAAgB,CAAC,UAAqC;AAC1D,uBAAe,KAAK;AACpB,oBAAY;AACZ,0BAAkB,OAAO,aAAa,UAAU;AAAA,MAClD;AACA,iBAAO,2BAAa,OAAO,EAAE,SAAS,cAAc,CAA2B;AAAA,IACjF;AACA,WAAO;AAAA,EACT,CAAC;AAED,QAAM,sBAAsB,MAAM;AAChC,QAAI,CAAC,SAAS,MAAM,WAAW,EAAG,QAAO;AAEzC,WAAO,MAAM,IAAI,CAAC,MAAM,UAAU;AAChC,UAAI,KAAK,SAAS,WAAW;AAE3B,eAAO,6CAAC,eAAAC,SAAA,IAAa,WAAW,KAAK,EAAI;AAAA,MAC3C;AAEA,YAAM;AAAA,QACJ,MAAM;AAAA,QACN,OAAO;AAAA,QACP,WAAW;AAAA,QACX,SAAS;AAAA,QACT,OAAO;AAAA,QACP;AAAA,QACA;AAAA,MACF,IAAI;AACJ,YAAM,UAAU,MAAM,GAAG,UAAU,UAAU,KAAK;AAClD,YAAM,WAAW,gBAAgB,OAAO;AACxC,YAAM,kBAAkB,cAAc;AAEtC,UAAI,cAAc,WAAW,SAAS,GAAG;AACvC,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,IAAI;AAAA,YACJ,OAAO;AAAA,YACP,WAAW;AAAA,YACX,SAAS;AAAA,YACT;AAAA,YACA,OAAO;AAAA,YACP;AAAA;AAAA,UAPK;AAAA,QAQP;AAAA,MAEJ;AAEA,YAAM,kBAAkB,CAAC,UAAqC;AAC5D,kBAAU,KAAK;AACf,oBAAY;AACZ,0BAAkB,OAAO,aAAa,OAAO;AAAA,MAC/C;AAEA,aACE,6CAAC,gBAAAD,SAAA,EAA2B,SAAS,iBACnC,wDAAC,mBACE;AAAA,kCAA0B,6CAAC,2BAAwB,IAAK;AAAA,QACzD,6CAAC,+BAAW,IAAI,EAAE,MAAM,EAAE,GAAI,2BAAgB;AAAA,QAC7C,wBAAwB,6CAAC,yBAAsB,IAAK;AAAA,SACvD,KALgB,QAMlB;AAAA,IAEJ,CAAC;AAAA,EACH;AAEA,QAAM,uBAAuB,SAAS,MAAM,SAAS,IAAI,oBAAoB,IAAI;AAEjF,SACE,8EACE;AAAA;AAAA,MAAC,gBAAAA;AAAA,MAAA;AAAA,QACC,eAAa,GAAG,UAAU;AAAA,QAC1B,IAAI;AAAA,QACJ,KAAK;AAAA,QACL,cAAc;AAAA,QACd,cAAc,OAAK;AAGjB,cAAI,eAAe,EAAE,aAAa,KAAK,WAAW,SAAS,SAAS,EAAE,aAAa,EAAG;AAEtF,wBAAc;AAAA,QAChB;AAAA,QACA,WAAW,OAAK;AACd,YAAE,eAAe;AACjB,cAAI,EAAE,QAAQ,aAAa;AACzB,wBAAY;AAAA,UACd;AACA,cAAI,EAAE,QAAQ,gBAAgB,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;AAChE,uBAAW,CAAC;AAAA,UACd;AAAA,QACF;AAAA,QACA,iBAAc;AAAA,QACd,iBAAe;AAAA,QACf,iBAAe,OAAO,SAAS;AAAA,QAC9B,GAAG;AAAA,QAEJ,wDAAC,mBACE;AAAA,+BAAqB,6CAAC,sBAAmB,IAAK;AAAA,UAC/C,6CAAC,+BAAW,IAAI,EAAE,MAAM,EAAE,GAAI,iBAAM;AAAA,UACpC,6CAAC,sBAAe;AAAA,WAClB;AAAA;AAAA,IACF;AAAA,IASA;AAAA,MAAC;AAAA;AAAA,QACC,eAAa,GAAG,UAAU;AAAA,QAC1B;AAAA,QACA,KAAK;AAAA,QACL,UAAU;AAAA,QACV,YAAU;AAAA,QACV,IAAI,EAAE,QAAQ,OAAK,EAAE,OAAO,QAAQ,EAAE;AAAA,QACtC,WAAU;AAAA,QACV,WAAW,OAAK;AACd,cAAI,EAAE,QAAQ,aAAa;AACzB,cAAE,eAAe;AACjB,wBAAY;AACZ,wBAAY,SAAS,MAAM;AAAA,UAC7B;AACA,cAAI,EAAE,QAAQ,aAAa,EAAE,QAAQ,aAAa;AAChD,cAAE,eAAe;AACjB,cAAE,gBAAgB;AAAA,UACpB;AAAA,QACF;AAAA,QACA,cAAc;AAAA,QACd,cAAc,OAAK;AAGjB,cAAI,eAAe,EAAE,aAAa,KAAK,YAAY,SAAS,SAAS,EAAE,aAAa,EAAG;AAEvF,wBAAc;AAAA,QAChB;AAAA,QAEC,WAAC,EAAE,gBAAgB,MAClB,6CAAC,aAAAE,SAAA,EAAM,GAAG,iBAAiB,SAAS,iBAAiB,SACnD,uDAAC,0BAAM,WAAW,UAAU,WAAY,GAAI,WAAW,WAAW,SAAwB,CAAC,GACzF;AAAA,UAAC;AAAA;AAAA,YACC,IAAI;AAAA,YACJ,mBAAiB;AAAA,YACjB,MAAK;AAAA,YACJ,GAAI,WAAW,WAAW,QAA0B,CAAC;AAAA,YAEtD,eAAa;AAAA,YACb,WAAW,OAAK;AACd,kBAAI,EAAE,QAAQ,aAAa;AACzB,sBAAM,EAAE,YAAY,IAAI;AAMxB,oBAAI,YAAY,8BAA8B;AAG5C;AAAA,gBACF;AACA,oBAAI,CAAC,WAAW,SAAS,SAAS,EAAE,MAAc,GAAG;AAGnD;AAAA,gBACF;AAEA,4BAAY,+BAA+B;AAI3C,kBAAE,eAAe;AAGjB,kBAAE,gBAAgB;AAKlB,4BAAY,yBAAyB;AAErC,4BAAY;AAEZ,4BAAY,SAAS,MAAM;AAAA,cAC7B;AAAA,YACF;AAAA,YAEC;AAAA;AAAA,QACH,GACF,GACF;AAAA;AAAA,IAEJ;AAAA,KACF;AAEJ;;;AD7Pa,IAAAC,sBAAA;AANN,SAAS,KAAK,EAAE,OAAO,YAAY,mBAAmB,GAAG,KAAK,GAAU;AAC7E,QAAM,YAAkC,EAAE,WAAW,GAAG,KAAK;AAC7D,QAAM,sBAAkB,qBAAM;AAE9B,QAAM,sBAAsB,MAAM,IAAI,CAAC,MAAM,UAAU;AACrD,QAAI,KAAK,SAAS,WAAW;AAC3B,aAAO,6CAAC,gBAAAC,SAAA,IAAa,WAAW,KAAK,EAAI;AAAA,IAC3C;AAEA,UAAM;AAAA,MACJ,MAAM;AAAA,MACN,OAAO;AAAA,MACP,WAAW;AAAA,MACX,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAG;AAAA,IACL,IAAI;AACJ,UAAM,UAAU,MAAM,GAAG,eAAe,UAAU,KAAK;AACvD,UAAM,UAAU,gBAAgB,OAAO;AACvC,UAAM,eAAe,SAAS;AAE9B,QAAI,eAAe,YAAY,SAAS,GAAG;AACzC,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,IAAI;AAAA,UACJ,OAAO;AAAA,UACP,WAAW;AAAA,UACX,SAAS;AAAA,UACT,iBAAiB,UAAU;AAAA,UAC3B,OAAO;AAAA,UACP;AAAA;AAAA,QAPK;AAAA,MAQP;AAAA,IAEJ;AAEA,UAAM,kBAAkB,CAAC,UAA2C;AAClE,gBAAU,KAAK;AACf,gBAAU,UAAU,OAAO,aAAa,OAAO;AAAA,IACjD;AAEA,WACE,6CAAC,iBAAAC,SAAA,EAA0B,SAAS,iBAAkB,GAAG,eACvD,wDAAC,mBACE;AAAA,2BAAqB,6CAAC,sBAAmB,IAAK;AAAA,MAC/C,6CAAC,+BAAW,IAAI,EAAE,MAAM,EAAE,GAAI,wBAAa;AAAA,MAC1C,mBAAmB,6CAAC,oBAAiB,IAAK;AAAA,OAC7C,KALgB,OAMlB;AAAA,EAEJ,CAAC;AAED,SACE;AAAA,IAAC,YAAAC;AAAA,IAAA;AAAA,MACC,eAAY;AAAA,MACX,GAAG;AAAA,MACJ,oBAAoB,UAAU,sBAAsB,iBAAiB;AAAA,MACrE,OAAO,EAAE,YAAY,iBAAiB,MAAM,GAAG,UAAU,MAAM;AAAA,MAE9D;AAAA;AAAA,EACH;AAEJ;;;AD7EA,IAAO,gBAAQ;","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
|
|
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
|
|
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 ===
|
|
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__ */
|
|
130
|
+
return /* @__PURE__ */ jsx2(Divider, {}, `divider-${index}`);
|
|
103
131
|
}
|
|
104
132
|
const {
|
|
105
133
|
type: __,
|
|
106
134
|
items: entryItems,
|
|
107
|
-
startIcon:
|
|
108
|
-
endIcon:
|
|
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__ */
|
|
146
|
+
return /* @__PURE__ */ jsx2(
|
|
118
147
|
NestedMenuItem,
|
|
119
148
|
{
|
|
120
149
|
id: entryId,
|
|
121
150
|
label: entryLabelValue,
|
|
122
|
-
startIcon:
|
|
123
|
-
endIcon:
|
|
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
|
-
|
|
162
|
+
entryOnClick?.(event);
|
|
133
163
|
handleClose();
|
|
134
164
|
parentMenuClose?.(event, "itemClick", entryId);
|
|
135
165
|
};
|
|
136
|
-
return /* @__PURE__ */
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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__ */
|
|
145
|
-
/* @__PURE__ */
|
|
146
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
/* @__PURE__ */ jsx(ArrowRight_default, {})
|
|
173
|
-
] })
|
|
204
|
+
label,
|
|
205
|
+
startIcon: StartIconComponent,
|
|
206
|
+
endIcon: EndIconComponent ?? ArrowRight_default,
|
|
207
|
+
...menuItemProps
|
|
174
208
|
}
|
|
175
209
|
),
|
|
176
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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
|
|
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__ */
|
|
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
|
-
|
|
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 (
|
|
258
|
-
return /* @__PURE__ */
|
|
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:
|
|
264
|
-
endIcon:
|
|
297
|
+
startIcon: StartIcon,
|
|
298
|
+
endIcon: EndIcon,
|
|
265
299
|
parentMenuClose: menuProps.onClose,
|
|
266
|
-
|
|
267
|
-
|
|
300
|
+
menuProps,
|
|
301
|
+
items: childItems,
|
|
302
|
+
...muiMenuItemProps
|
|
268
303
|
},
|
|
269
304
|
itemKey
|
|
270
305
|
);
|
|
271
306
|
}
|
|
272
307
|
const handleItemClick = (event) => {
|
|
273
|
-
|
|
308
|
+
itemOnClick?.(event);
|
|
274
309
|
menuProps.onClose?.(event, "itemClick", entryId);
|
|
275
310
|
};
|
|
276
|
-
return /* @__PURE__ */
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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__ */
|
|
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.
|
|
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",
|