better-mui-menu 1.0.7 → 1.0.9
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 +1 -1
- package/dist/index.cjs +3 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
## Live demo
|
|
8
8
|
|
|
9
|
-
Try it: [StackBlitz playground](https://stackblitz.com/edit/vitejs-vite-autejxh8?file=src
|
|
9
|
+
Try it: [StackBlitz playground](https://stackblitz.com/edit/vitejs-vite-autejxh8?embed=1&file=src%2FMenuDemo.tsx&view=preview).
|
|
10
10
|
|
|
11
11
|

|
|
12
12
|
|
package/dist/index.cjs
CHANGED
|
@@ -67,16 +67,12 @@ var transitionConfig = {
|
|
|
67
67
|
type: import_Fade.default,
|
|
68
68
|
timeout: { enter: CLOSE_DELAY + 50, exit: CLOSE_DELAY + 50 }
|
|
69
69
|
};
|
|
70
|
-
var MenuItemContent = (0, import_material.styled)(import_material.Stack)(
|
|
70
|
+
var MenuItemContent = (0, import_material.styled)(import_material.Stack)({
|
|
71
71
|
flexDirection: "row",
|
|
72
72
|
alignItems: "center",
|
|
73
|
-
gap:
|
|
74
|
-
fontSize: theme.typography.body1.fontSize,
|
|
75
|
-
"& .MuiSvgIcon-root": {
|
|
76
|
-
fontSize: theme.typography.body2.fontSize
|
|
77
|
-
},
|
|
73
|
+
gap: "8px",
|
|
78
74
|
padding: 0
|
|
79
|
-
})
|
|
75
|
+
});
|
|
80
76
|
|
|
81
77
|
// src/Menu/NestedMenuItem.tsx
|
|
82
78
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
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 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 { MenuItemContent, transitionConfig } from './common';\n\ntype Props = {\n anchorEl: null | HTMLElement;\n open: boolean;\n onClose: () => void;\n items: MenuItem[];\n};\n\nexport function Menu({ anchorEl, open, onClose, items }: Props) {\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={onClose}\n items={nestedItems}\n />\n );\n }\n\n const handleItemClick = (event: React.MouseEvent<HTMLLIElement>) => {\n onClick?.(event);\n onClose();\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 anchorEl={anchorEl}\n open={open}\n onClose={onClose}\n onKeyDown={e => {\n if (e.key === 'Escape' && open) onClose();\n }}\n transitionDuration={transitionConfig.timeout}\n slots={{ transition: transitionConfig.type }}\n slotProps={{\n list: {\n 'aria-labelledby': 'icon-menu-button',\n },\n }}\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 { MenuList, Paper, Popper, Typography } from '@mui/material';\nimport type { MenuItem } from './types';\nimport { CLOSE_DELAY, MenuItemContent, transitionConfig } from './common';\n\ntype NestedMenuItemProps = MenuItemProps & {\n label: ReactNode;\n startIcon?: SvgIconComponent;\n endIcon?: SvgIconComponent;\n parentMenuClose: () => void;\n children?: ReactNode;\n items?: MenuItem[];\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 ...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();\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 />\n );\n }\n\n const handleItemClick = (event: MouseEvent<HTMLLIElement>) => {\n onClick?.(event);\n handleClose();\n parentMenuClose();\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 // TODO(ege): There can be a timeout here before we execute closing to improve UX - in case user is not very precise with mouse.\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 <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 // TODO(ege): There can be a timeout here before we execute closing to improve UX - in case user is not very precise with mouse.\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\n elevation={2}\n sx={{\n borderRadius: 1,\n bgcolor: 'background.default',\n }}\n >\n <MenuList\n autoFocusItem\n id={subMenuId}\n aria-labelledby={menuItemId}\n role='menu'\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};\n\nexport const MenuItemContent = styled(Stack)(({ theme }) => ({\n flexDirection: 'row',\n alignItems: 'center',\n gap: theme.spacing(1),\n fontSize: theme.typography.body1.fontSize,\n '& .MuiSvgIcon-root': {\n fontSize: theme.typography.body2.fontSize,\n },\n padding: 0,\n}));\n"],"mappings":"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","names":["import_Divider","import_MenuItem","import_react","import_material","import_Fade","_jsx","import_material","Fade","import_jsx_runtime","MuiMenuItem","Divider","Fade","import_jsx_runtime","Divider","MuiMenuItem","MuiMenu"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/Menu/index.tsx","../src/Menu/NestedMenuItem.tsx","../../../node_modules/@mui/icons-material/esm/utils/createSvgIcon.js","../../../node_modules/@mui/icons-material/esm/ArrowRight.js","../src/Menu/common.ts"],"sourcesContent":["import { Menu } from './Menu'\n\nexport type { MenuItem } from './Menu/types'\nexport default Menu\n","import 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 { MenuItemContent, transitionConfig } from './common';\n\ntype Props = {\n anchorEl: null | HTMLElement;\n open: boolean;\n onClose: () => void;\n items: MenuItem[];\n};\n\nexport function Menu({ anchorEl, open, onClose, items }: Props) {\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={onClose}\n items={nestedItems}\n />\n );\n }\n\n const handleItemClick = (event: React.MouseEvent<HTMLLIElement>) => {\n onClick?.(event);\n onClose();\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 anchorEl={anchorEl}\n open={open}\n onClose={onClose}\n onKeyDown={e => {\n if (e.key === 'Escape' && open) onClose();\n }}\n transitionDuration={transitionConfig.timeout}\n slots={{ transition: transitionConfig.type }}\n slotProps={{\n list: {\n 'aria-labelledby': 'icon-menu-button',\n },\n }}\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 { MenuList, Paper, Popper, Typography } from '@mui/material';\nimport type { MenuItem } from './types';\nimport { CLOSE_DELAY, MenuItemContent, transitionConfig } from './common';\n\ntype NestedMenuItemProps = MenuItemProps & {\n label: ReactNode;\n startIcon?: SvgIconComponent;\n endIcon?: SvgIconComponent;\n parentMenuClose: () => void;\n children?: ReactNode;\n items?: MenuItem[];\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 ...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();\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 />\n );\n }\n\n const handleItemClick = (event: MouseEvent<HTMLLIElement>) => {\n onClick?.(event);\n handleClose();\n parentMenuClose();\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 // TODO(ege): There can be a timeout here before we execute closing to improve UX - in case user is not very precise with mouse.\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 <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 // TODO(ege): There can be a timeout here before we execute closing to improve UX - in case user is not very precise with mouse.\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\n elevation={2}\n sx={{\n borderRadius: 1,\n bgcolor: 'background.default',\n }}\n >\n <MenuList\n autoFocusItem\n id={subMenuId}\n aria-labelledby={menuItemId}\n role='menu'\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};\n\nexport const MenuItemContent = styled(Stack)({\n flexDirection: 'row',\n alignItems: 'center',\n gap: '8px',\n padding: 0,\n});\n"],"mappings":"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","names":["import_Divider","import_MenuItem","import_react","import_material","import_Fade","_jsx","import_material","Fade","import_jsx_runtime","MuiMenuItem","Divider","Fade","import_jsx_runtime","Divider","MuiMenuItem","MuiMenu"]}
|
package/dist/index.js
CHANGED
|
@@ -31,16 +31,12 @@ var transitionConfig = {
|
|
|
31
31
|
type: Fade,
|
|
32
32
|
timeout: { enter: CLOSE_DELAY + 50, exit: CLOSE_DELAY + 50 }
|
|
33
33
|
};
|
|
34
|
-
var MenuItemContent = styled(Stack)(
|
|
34
|
+
var MenuItemContent = styled(Stack)({
|
|
35
35
|
flexDirection: "row",
|
|
36
36
|
alignItems: "center",
|
|
37
|
-
gap:
|
|
38
|
-
fontSize: theme.typography.body1.fontSize,
|
|
39
|
-
"& .MuiSvgIcon-root": {
|
|
40
|
-
fontSize: theme.typography.body2.fontSize
|
|
41
|
-
},
|
|
37
|
+
gap: "8px",
|
|
42
38
|
padding: 0
|
|
43
|
-
})
|
|
39
|
+
});
|
|
44
40
|
|
|
45
41
|
// src/Menu/NestedMenuItem.tsx
|
|
46
42
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
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 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 { MenuItemContent, transitionConfig } from './common';\n\ntype Props = {\n anchorEl: null | HTMLElement;\n open: boolean;\n onClose: () => void;\n items: MenuItem[];\n};\n\nexport function Menu({ anchorEl, open, onClose, items }: Props) {\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={onClose}\n items={nestedItems}\n />\n );\n }\n\n const handleItemClick = (event: React.MouseEvent<HTMLLIElement>) => {\n onClick?.(event);\n onClose();\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 anchorEl={anchorEl}\n open={open}\n onClose={onClose}\n onKeyDown={e => {\n if (e.key === 'Escape' && open) onClose();\n }}\n transitionDuration={transitionConfig.timeout}\n slots={{ transition: transitionConfig.type }}\n slotProps={{\n list: {\n 'aria-labelledby': 'icon-menu-button',\n },\n }}\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 { MenuList, Paper, Popper, Typography } from '@mui/material';\nimport type { MenuItem } from './types';\nimport { CLOSE_DELAY, MenuItemContent, transitionConfig } from './common';\n\ntype NestedMenuItemProps = MenuItemProps & {\n label: ReactNode;\n startIcon?: SvgIconComponent;\n endIcon?: SvgIconComponent;\n parentMenuClose: () => void;\n children?: ReactNode;\n items?: MenuItem[];\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 ...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();\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 />\n );\n }\n\n const handleItemClick = (event: MouseEvent<HTMLLIElement>) => {\n onClick?.(event);\n handleClose();\n parentMenuClose();\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 // TODO(ege): There can be a timeout here before we execute closing to improve UX - in case user is not very precise with mouse.\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 <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 // TODO(ege): There can be a timeout here before we execute closing to improve UX - in case user is not very precise with mouse.\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\n elevation={2}\n sx={{\n borderRadius: 1,\n bgcolor: 'background.default',\n }}\n >\n <MenuList\n autoFocusItem\n id={subMenuId}\n aria-labelledby={menuItemId}\n role='menu'\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};\n\nexport const MenuItemContent = styled(Stack)(({ theme }) => ({\n flexDirection: 'row',\n alignItems: 'center',\n gap: theme.spacing(1),\n fontSize: theme.typography.body1.fontSize,\n '& .MuiSvgIcon-root': {\n fontSize: theme.typography.body2.fontSize,\n },\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/common.ts","../src/index.ts"],"sourcesContent":["import 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 { MenuItemContent, transitionConfig } from './common';\n\ntype Props = {\n anchorEl: null | HTMLElement;\n open: boolean;\n onClose: () => void;\n items: MenuItem[];\n};\n\nexport function Menu({ anchorEl, open, onClose, items }: Props) {\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={onClose}\n items={nestedItems}\n />\n );\n }\n\n const handleItemClick = (event: React.MouseEvent<HTMLLIElement>) => {\n onClick?.(event);\n onClose();\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 anchorEl={anchorEl}\n open={open}\n onClose={onClose}\n onKeyDown={e => {\n if (e.key === 'Escape' && open) onClose();\n }}\n transitionDuration={transitionConfig.timeout}\n slots={{ transition: transitionConfig.type }}\n slotProps={{\n list: {\n 'aria-labelledby': 'icon-menu-button',\n },\n }}\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 { MenuList, Paper, Popper, Typography } from '@mui/material';\nimport type { MenuItem } from './types';\nimport { CLOSE_DELAY, MenuItemContent, transitionConfig } from './common';\n\ntype NestedMenuItemProps = MenuItemProps & {\n label: ReactNode;\n startIcon?: SvgIconComponent;\n endIcon?: SvgIconComponent;\n parentMenuClose: () => void;\n children?: ReactNode;\n items?: MenuItem[];\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 ...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();\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 />\n );\n }\n\n const handleItemClick = (event: MouseEvent<HTMLLIElement>) => {\n onClick?.(event);\n handleClose();\n parentMenuClose();\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 // TODO(ege): There can be a timeout here before we execute closing to improve UX - in case user is not very precise with mouse.\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 <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 // TODO(ege): There can be a timeout here before we execute closing to improve UX - in case user is not very precise with mouse.\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\n elevation={2}\n sx={{\n borderRadius: 1,\n bgcolor: 'background.default',\n }}\n >\n <MenuList\n autoFocusItem\n id={subMenuId}\n aria-labelledby={menuItemId}\n role='menu'\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};\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"]}
|