@react-aria/menu 3.15.5 → 3.17.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/dist/types.d.ts +9 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/useMenu.main.js +7 -5
- package/dist/useMenu.main.js.map +1 -1
- package/dist/useMenu.mjs +8 -5
- package/dist/useMenu.module.js +8 -5
- package/dist/useMenu.module.js.map +1 -1
- package/dist/useMenuItem.main.js +39 -22
- package/dist/useMenuItem.main.js.map +1 -1
- package/dist/useMenuItem.mjs +39 -22
- package/dist/useMenuItem.module.js +39 -22
- package/dist/useMenuItem.module.js.map +1 -1
- package/dist/useMenuTrigger.main.js +1 -0
- package/dist/useMenuTrigger.main.js.map +1 -1
- package/dist/useMenuTrigger.mjs +1 -0
- package/dist/useMenuTrigger.module.js +1 -0
- package/dist/useMenuTrigger.module.js.map +1 -1
- package/dist/useSafelyMouseToSubmenu.main.js +1 -1
- package/dist/useSafelyMouseToSubmenu.main.js.map +1 -1
- package/dist/useSafelyMouseToSubmenu.mjs +1 -1
- package/dist/useSafelyMouseToSubmenu.module.js +1 -1
- package/dist/useSafelyMouseToSubmenu.module.js.map +1 -1
- package/dist/useSubmenuTrigger.main.js +8 -4
- package/dist/useSubmenuTrigger.main.js.map +1 -1
- package/dist/useSubmenuTrigger.mjs +8 -4
- package/dist/useSubmenuTrigger.module.js +8 -4
- package/dist/useSubmenuTrigger.module.js.map +1 -1
- package/dist/utils.main.js +20 -0
- package/dist/utils.main.js.map +1 -0
- package/dist/utils.mjs +15 -0
- package/dist/utils.module.js +15 -0
- package/dist/utils.module.js.map +1 -0
- package/package.json +17 -16
- package/src/useMenu.ts +11 -13
- package/src/useMenuItem.ts +52 -28
- package/src/useMenuTrigger.ts +2 -1
- package/src/useSafelyMouseToSubmenu.ts +1 -1
- package/src/useSubmenuTrigger.ts +5 -5
- package/src/utils.ts +22 -0
|
@@ -50,16 +50,18 @@ function $0065b146e7192841$export$7138b0d059a6e743(props, state, ref) {
|
|
|
50
50
|
switch(e.key){
|
|
51
51
|
case 'ArrowLeft':
|
|
52
52
|
if (direction === 'ltr' && e.currentTarget.contains(e.target)) {
|
|
53
|
+
var _ref_current;
|
|
53
54
|
e.stopPropagation();
|
|
54
55
|
onSubmenuClose();
|
|
55
|
-
ref.current.focus();
|
|
56
|
+
(_ref_current = ref.current) === null || _ref_current === void 0 ? void 0 : _ref_current.focus();
|
|
56
57
|
}
|
|
57
58
|
break;
|
|
58
59
|
case 'ArrowRight':
|
|
59
60
|
if (direction === 'rtl' && e.currentTarget.contains(e.target)) {
|
|
61
|
+
var _ref_current1;
|
|
60
62
|
e.stopPropagation();
|
|
61
63
|
onSubmenuClose();
|
|
62
|
-
ref.current.focus();
|
|
64
|
+
(_ref_current1 = ref.current) === null || _ref_current1 === void 0 ? void 0 : _ref_current1.focus();
|
|
63
65
|
}
|
|
64
66
|
break;
|
|
65
67
|
case 'Escape':
|
|
@@ -68,13 +70,14 @@ function $0065b146e7192841$export$7138b0d059a6e743(props, state, ref) {
|
|
|
68
70
|
break;
|
|
69
71
|
}
|
|
70
72
|
};
|
|
73
|
+
var _state_focusStrategy;
|
|
71
74
|
let submenuProps = {
|
|
72
75
|
id: overlayId,
|
|
73
76
|
'aria-labelledby': submenuTriggerId,
|
|
74
77
|
submenuLevel: state.submenuLevel,
|
|
75
78
|
...type === 'menu' && {
|
|
76
79
|
onClose: state.closeAll,
|
|
77
|
-
autoFocus: state.focusStrategy,
|
|
80
|
+
autoFocus: (_state_focusStrategy = state.focusStrategy) !== null && _state_focusStrategy !== void 0 ? _state_focusStrategy : undefined,
|
|
78
81
|
onKeyDown: submenuKeyDown
|
|
79
82
|
}
|
|
80
83
|
};
|
|
@@ -125,7 +128,8 @@ function $0065b146e7192841$export$7138b0d059a6e743(props, state, ref) {
|
|
|
125
128
|
}
|
|
126
129
|
};
|
|
127
130
|
let onBlur = (e)=>{
|
|
128
|
-
|
|
131
|
+
var _parentMenuRef_current;
|
|
132
|
+
if (state.isOpen && ((_parentMenuRef_current = parentMenuRef.current) === null || _parentMenuRef_current === void 0 ? void 0 : _parentMenuRef_current.contains(e.relatedTarget))) onSubmenuClose();
|
|
129
133
|
};
|
|
130
134
|
let shouldCloseOnInteractOutside = (target)=>{
|
|
131
135
|
if (target !== ref.current) return true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":"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","sources":["packages/@react-aria/menu/src/useSubmenuTrigger.ts"],"sourcesContent":["/*\n * Copyright 2023 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {AriaMenuItemProps} from './useMenuItem';\nimport {AriaMenuOptions} from './useMenu';\nimport type {AriaPopoverProps, OverlayProps} from '@react-aria/overlays';\nimport {FocusableElement, FocusStrategy, KeyboardEvent, Node, PressEvent, RefObject} from '@react-types/shared';\nimport type {SubmenuTriggerState} from '@react-stately/menu';\nimport {useCallback, useRef} from 'react';\nimport {useEffectEvent, useId, useLayoutEffect} from '@react-aria/utils';\nimport {useLocale} from '@react-aria/i18n';\nimport {useSafelyMouseToSubmenu} from './useSafelyMouseToSubmenu';\n\nexport interface AriaSubmenuTriggerProps {\n /**\n * An object representing the submenu trigger menu item. Contains all the relevant information that makes up the menu item.\n * @deprecated\n */\n node?: Node<unknown>,\n /** Whether the submenu trigger is disabled. */\n isDisabled?: boolean,\n /** The type of the contents that the submenu trigger opens. */\n type?: 'dialog' | 'menu',\n /** Ref of the menu that contains the submenu trigger. */\n parentMenuRef: RefObject<HTMLElement | null>,\n /** Ref of the submenu opened by the submenu trigger. */\n submenuRef: RefObject<HTMLElement | null>,\n /**\n * The delay time in milliseconds for the submenu to appear after hovering over the trigger.\n * @default 200\n */\n delay?: number\n}\n\ninterface SubmenuTriggerProps extends AriaMenuItemProps {\n /** Whether the submenu trigger is in an expanded state. */\n isOpen: boolean\n}\n\ninterface SubmenuProps<T> extends AriaMenuOptions<T> {\n /** The level of the submenu. */\n submenuLevel: number\n}\n\nexport interface SubmenuTriggerAria<T> {\n /** Props for the submenu trigger menu item. */\n submenuTriggerProps: SubmenuTriggerProps,\n /** Props for the submenu controlled by the submenu trigger menu item. */\n submenuProps: SubmenuProps<T>,\n /** Props for the submenu's popover container. */\n popoverProps: Pick<AriaPopoverProps, 'isNonModal' | 'shouldCloseOnInteractOutside'> & Pick<OverlayProps, 'disableFocusManagement'>\n}\n\n/**\n * Provides the behavior and accessibility implementation for a submenu trigger and its associated submenu.\n * @param props - Props for the submenu trigger and refs attach to its submenu and parent menu.\n * @param state - State for the submenu trigger.\n * @param ref - Ref to the submenu trigger element.\n */\nexport function useSubmenuTrigger<T>(props: AriaSubmenuTriggerProps, state: SubmenuTriggerState, ref: RefObject<FocusableElement | null>): SubmenuTriggerAria<T> {\n let {parentMenuRef, submenuRef, type = 'menu', isDisabled, delay = 200} = props;\n let submenuTriggerId = useId();\n let overlayId = useId();\n let {direction} = useLocale();\n let openTimeout = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n let cancelOpenTimeout = useCallback(() => {\n if (openTimeout.current) {\n clearTimeout(openTimeout.current);\n openTimeout.current = undefined;\n }\n }, [openTimeout]);\n\n let onSubmenuOpen = useEffectEvent((focusStrategy?: FocusStrategy) => {\n cancelOpenTimeout();\n state.open(focusStrategy);\n });\n\n let onSubmenuClose = useEffectEvent(() => {\n cancelOpenTimeout();\n state.close();\n });\n\n useLayoutEffect(() => {\n return () => {\n cancelOpenTimeout();\n };\n }, [cancelOpenTimeout]);\n\n let submenuKeyDown = (e: KeyboardEvent) => {\n switch (e.key) {\n case 'ArrowLeft':\n if (direction === 'ltr' && e.currentTarget.contains(e.target as Element)) {\n e.stopPropagation();\n onSubmenuClose();\n ref.current.focus();\n }\n break;\n case 'ArrowRight':\n if (direction === 'rtl' && e.currentTarget.contains(e.target as Element)) {\n e.stopPropagation();\n onSubmenuClose();\n ref.current.focus();\n }\n break;\n case 'Escape':\n e.stopPropagation();\n state.closeAll();\n break;\n }\n };\n\n let submenuProps = {\n id: overlayId,\n 'aria-labelledby': submenuTriggerId,\n submenuLevel: state.submenuLevel,\n ...(type === 'menu' && {\n onClose: state.closeAll,\n autoFocus: state.focusStrategy,\n onKeyDown: submenuKeyDown\n })\n };\n\n let submenuTriggerKeyDown = (e: KeyboardEvent) => {\n switch (e.key) {\n case 'ArrowRight':\n if (!isDisabled) {\n if (direction === 'ltr') {\n if (!state.isOpen) {\n onSubmenuOpen('first');\n }\n\n if (type === 'menu' && !!submenuRef?.current && document.activeElement === ref?.current) {\n submenuRef.current.focus();\n }\n } else if (state.isOpen) {\n onSubmenuClose();\n } else {\n e.continuePropagation();\n }\n }\n\n break;\n case 'ArrowLeft':\n if (!isDisabled) {\n if (direction === 'rtl') {\n if (!state.isOpen) {\n onSubmenuOpen('first');\n }\n\n if (type === 'menu' && !!submenuRef?.current && document.activeElement === ref?.current) {\n submenuRef.current.focus();\n }\n } else if (state.isOpen) {\n onSubmenuClose();\n } else {\n e.continuePropagation();\n }\n }\n break;\n case 'Escape':\n state.closeAll();\n break;\n default:\n e.continuePropagation();\n break;\n }\n };\n\n let onPressStart = (e: PressEvent) => {\n if (!isDisabled && (e.pointerType === 'virtual' || e.pointerType === 'keyboard')) {\n // If opened with a screen reader or keyboard, auto focus the first submenu item.\n onSubmenuOpen('first');\n }\n };\n\n let onPress = (e: PressEvent) => {\n if (!isDisabled && (e.pointerType === 'touch' || e.pointerType === 'mouse')) {\n // For touch or on a desktop device with a small screen open on press up to possible problems with\n // press up happening on the newly opened tray items\n onSubmenuOpen();\n }\n };\n\n let onHoverChange = (isHovered) => {\n if (!isDisabled) {\n if (isHovered && !state.isOpen) {\n if (!openTimeout.current) {\n openTimeout.current = setTimeout(() => {\n onSubmenuOpen();\n }, delay);\n }\n } else if (!isHovered) {\n cancelOpenTimeout();\n }\n }\n };\n\n let onBlur = (e) => {\n if (state.isOpen && parentMenuRef.current.contains(e.relatedTarget)) {\n onSubmenuClose();\n }\n };\n\n let shouldCloseOnInteractOutside = (target) => {\n if (target !== ref.current) {\n return true;\n }\n\n return false;\n };\n\n useSafelyMouseToSubmenu({menuRef: parentMenuRef, submenuRef, isOpen: state.isOpen, isDisabled: isDisabled});\n\n return {\n submenuTriggerProps: {\n id: submenuTriggerId,\n 'aria-controls': state.isOpen ? overlayId : undefined,\n 'aria-haspopup': !isDisabled ? type : undefined,\n 'aria-expanded': state.isOpen ? 'true' : 'false',\n onPressStart,\n onPress,\n onHoverChange,\n onKeyDown: submenuTriggerKeyDown,\n onBlur,\n isOpen: state.isOpen\n },\n submenuProps,\n popoverProps: {\n isNonModal: true,\n disableFocusManagement: true,\n shouldCloseOnInteractOutside\n }\n };\n}\n"],"names":[],"version":3,"file":"useSubmenuTrigger.module.js.map"}
|
|
1
|
+
{"mappings":"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","sources":["packages/@react-aria/menu/src/useSubmenuTrigger.ts"],"sourcesContent":["/*\n * Copyright 2023 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {AriaMenuItemProps} from './useMenuItem';\nimport {AriaMenuOptions} from './useMenu';\nimport type {AriaPopoverProps, OverlayProps} from '@react-aria/overlays';\nimport {FocusableElement, FocusStrategy, KeyboardEvent, Node, PressEvent, RefObject} from '@react-types/shared';\nimport type {SubmenuTriggerState} from '@react-stately/menu';\nimport {useCallback, useRef} from 'react';\nimport {useEffectEvent, useId, useLayoutEffect} from '@react-aria/utils';\nimport {useLocale} from '@react-aria/i18n';\nimport {useSafelyMouseToSubmenu} from './useSafelyMouseToSubmenu';\n\nexport interface AriaSubmenuTriggerProps {\n /**\n * An object representing the submenu trigger menu item. Contains all the relevant information that makes up the menu item.\n * @deprecated\n */\n node?: Node<unknown>,\n /** Whether the submenu trigger is disabled. */\n isDisabled?: boolean,\n /** The type of the contents that the submenu trigger opens. */\n type?: 'dialog' | 'menu',\n /** Ref of the menu that contains the submenu trigger. */\n parentMenuRef: RefObject<HTMLElement | null>,\n /** Ref of the submenu opened by the submenu trigger. */\n submenuRef: RefObject<HTMLElement | null>,\n /**\n * The delay time in milliseconds for the submenu to appear after hovering over the trigger.\n * @default 200\n */\n delay?: number\n}\n\ninterface SubmenuTriggerProps extends Omit<AriaMenuItemProps, 'key'> {\n /** Whether the submenu trigger is in an expanded state. */\n isOpen: boolean\n}\n\ninterface SubmenuProps<T> extends AriaMenuOptions<T> {\n /** The level of the submenu. */\n submenuLevel: number\n}\n\nexport interface SubmenuTriggerAria<T> {\n /** Props for the submenu trigger menu item. */\n submenuTriggerProps: SubmenuTriggerProps,\n /** Props for the submenu controlled by the submenu trigger menu item. */\n submenuProps: SubmenuProps<T>,\n /** Props for the submenu's popover container. */\n popoverProps: Pick<AriaPopoverProps, 'isNonModal' | 'shouldCloseOnInteractOutside'> & Pick<OverlayProps, 'disableFocusManagement'>\n}\n\n/**\n * Provides the behavior and accessibility implementation for a submenu trigger and its associated submenu.\n * @param props - Props for the submenu trigger and refs attach to its submenu and parent menu.\n * @param state - State for the submenu trigger.\n * @param ref - Ref to the submenu trigger element.\n */\nexport function useSubmenuTrigger<T>(props: AriaSubmenuTriggerProps, state: SubmenuTriggerState, ref: RefObject<FocusableElement | null>): SubmenuTriggerAria<T> {\n let {parentMenuRef, submenuRef, type = 'menu', isDisabled, delay = 200} = props;\n let submenuTriggerId = useId();\n let overlayId = useId();\n let {direction} = useLocale();\n let openTimeout = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n let cancelOpenTimeout = useCallback(() => {\n if (openTimeout.current) {\n clearTimeout(openTimeout.current);\n openTimeout.current = undefined;\n }\n }, [openTimeout]);\n\n let onSubmenuOpen = useEffectEvent((focusStrategy?: FocusStrategy) => {\n cancelOpenTimeout();\n state.open(focusStrategy);\n });\n\n let onSubmenuClose = useEffectEvent(() => {\n cancelOpenTimeout();\n state.close();\n });\n\n useLayoutEffect(() => {\n return () => {\n cancelOpenTimeout();\n };\n }, [cancelOpenTimeout]);\n\n let submenuKeyDown = (e: KeyboardEvent) => {\n switch (e.key) {\n case 'ArrowLeft':\n if (direction === 'ltr' && e.currentTarget.contains(e.target as Element)) {\n e.stopPropagation();\n onSubmenuClose();\n ref.current?.focus();\n }\n break;\n case 'ArrowRight':\n if (direction === 'rtl' && e.currentTarget.contains(e.target as Element)) {\n e.stopPropagation();\n onSubmenuClose();\n ref.current?.focus();\n }\n break;\n case 'Escape':\n e.stopPropagation();\n state.closeAll();\n break;\n }\n };\n\n let submenuProps = {\n id: overlayId,\n 'aria-labelledby': submenuTriggerId,\n submenuLevel: state.submenuLevel,\n ...(type === 'menu' && {\n onClose: state.closeAll,\n autoFocus: state.focusStrategy ?? undefined,\n onKeyDown: submenuKeyDown\n })\n };\n\n let submenuTriggerKeyDown = (e: KeyboardEvent) => {\n switch (e.key) {\n case 'ArrowRight':\n if (!isDisabled) {\n if (direction === 'ltr') {\n if (!state.isOpen) {\n onSubmenuOpen('first');\n }\n\n if (type === 'menu' && !!submenuRef?.current && document.activeElement === ref?.current) {\n submenuRef.current.focus();\n }\n } else if (state.isOpen) {\n onSubmenuClose();\n } else {\n e.continuePropagation();\n }\n }\n\n break;\n case 'ArrowLeft':\n if (!isDisabled) {\n if (direction === 'rtl') {\n if (!state.isOpen) {\n onSubmenuOpen('first');\n }\n\n if (type === 'menu' && !!submenuRef?.current && document.activeElement === ref?.current) {\n submenuRef.current.focus();\n }\n } else if (state.isOpen) {\n onSubmenuClose();\n } else {\n e.continuePropagation();\n }\n }\n break;\n case 'Escape':\n state.closeAll();\n break;\n default:\n e.continuePropagation();\n break;\n }\n };\n\n let onPressStart = (e: PressEvent) => {\n if (!isDisabled && (e.pointerType === 'virtual' || e.pointerType === 'keyboard')) {\n // If opened with a screen reader or keyboard, auto focus the first submenu item.\n onSubmenuOpen('first');\n }\n };\n\n let onPress = (e: PressEvent) => {\n if (!isDisabled && (e.pointerType === 'touch' || e.pointerType === 'mouse')) {\n // For touch or on a desktop device with a small screen open on press up to possible problems with\n // press up happening on the newly opened tray items\n onSubmenuOpen();\n }\n };\n\n let onHoverChange = (isHovered) => {\n if (!isDisabled) {\n if (isHovered && !state.isOpen) {\n if (!openTimeout.current) {\n openTimeout.current = setTimeout(() => {\n onSubmenuOpen();\n }, delay);\n }\n } else if (!isHovered) {\n cancelOpenTimeout();\n }\n }\n };\n\n let onBlur = (e) => {\n if (state.isOpen && parentMenuRef.current?.contains(e.relatedTarget)) {\n onSubmenuClose();\n }\n };\n\n let shouldCloseOnInteractOutside = (target) => {\n if (target !== ref.current) {\n return true;\n }\n\n return false;\n };\n\n useSafelyMouseToSubmenu({menuRef: parentMenuRef, submenuRef, isOpen: state.isOpen, isDisabled: isDisabled});\n\n return {\n submenuTriggerProps: {\n id: submenuTriggerId,\n 'aria-controls': state.isOpen ? overlayId : undefined,\n 'aria-haspopup': !isDisabled ? type : undefined,\n 'aria-expanded': state.isOpen ? 'true' : 'false',\n onPressStart,\n onPress,\n onHoverChange,\n onKeyDown: submenuTriggerKeyDown,\n onBlur,\n isOpen: state.isOpen\n },\n submenuProps,\n popoverProps: {\n isNonModal: true,\n disableFocusManagement: true,\n shouldCloseOnInteractOutside\n }\n };\n}\n"],"names":[],"version":3,"file":"useSubmenuTrigger.module.js.map"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
function $parcel$export(e, n, v, s) {
|
|
3
|
+
Object.defineProperty(e, n, {get: v, set: s, enumerable: true, configurable: true});
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
$parcel$export(module.exports, "menuData", () => $815e346b11b84016$export$6f49b4016bfc8d56);
|
|
7
|
+
/*
|
|
8
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
9
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
10
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
11
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
14
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
15
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
16
|
+
* governing permissions and limitations under the License.
|
|
17
|
+
*/ const $815e346b11b84016$export$6f49b4016bfc8d56 = new WeakMap();
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=utils.main.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"mappings":";;;;;;AAAA;;;;;;;;;;CAUC,GAWM,MAAM,4CAAW,IAAI","sources":["packages/@react-aria/menu/src/utils.ts"],"sourcesContent":["/*\n * Copyright 2024 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {Key} from '@react-types/shared';\nimport {TreeState} from '@react-stately/tree';\n\ninterface MenuData {\n onClose?: () => void,\n onAction?: (key: Key) => void,\n shouldUseVirtualFocus?: boolean\n}\n\nexport const menuData = new WeakMap<TreeState<unknown>, MenuData>();\n"],"names":[],"version":3,"file":"utils.main.js.map"}
|
package/dist/utils.mjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/ const $fc79756100351201$export$6f49b4016bfc8d56 = new WeakMap();
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
export {$fc79756100351201$export$6f49b4016bfc8d56 as menuData};
|
|
15
|
+
//# sourceMappingURL=utils.module.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/ const $fc79756100351201$export$6f49b4016bfc8d56 = new WeakMap();
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
export {$fc79756100351201$export$6f49b4016bfc8d56 as menuData};
|
|
15
|
+
//# sourceMappingURL=utils.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"mappings":"AAAA;;;;;;;;;;CAUC,GAWM,MAAM,4CAAW,IAAI","sources":["packages/@react-aria/menu/src/utils.ts"],"sourcesContent":["/*\n * Copyright 2024 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {Key} from '@react-types/shared';\nimport {TreeState} from '@react-stately/tree';\n\ninterface MenuData {\n onClose?: () => void,\n onAction?: (key: Key) => void,\n shouldUseVirtualFocus?: boolean\n}\n\nexport const menuData = new WeakMap<TreeState<unknown>, MenuData>();\n"],"names":[],"version":3,"file":"utils.module.js.map"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-aria/menu",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.17.0",
|
|
4
4
|
"description": "Spectrum UI components in React",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -22,26 +22,27 @@
|
|
|
22
22
|
"url": "https://github.com/adobe/react-spectrum"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@react-aria/focus": "^3.
|
|
26
|
-
"@react-aria/i18n": "^3.12.
|
|
27
|
-
"@react-aria/interactions": "^3.
|
|
28
|
-
"@react-aria/overlays": "^3.
|
|
29
|
-
"@react-aria/selection": "^3.
|
|
30
|
-
"@react-aria/utils": "^3.
|
|
31
|
-
"@react-stately/collections": "^3.
|
|
32
|
-
"@react-stately/menu": "^3.
|
|
33
|
-
"@react-stately/
|
|
34
|
-
"@react-
|
|
35
|
-
"@react-types/
|
|
36
|
-
"@react-types/
|
|
25
|
+
"@react-aria/focus": "^3.19.1",
|
|
26
|
+
"@react-aria/i18n": "^3.12.5",
|
|
27
|
+
"@react-aria/interactions": "^3.23.0",
|
|
28
|
+
"@react-aria/overlays": "^3.25.0",
|
|
29
|
+
"@react-aria/selection": "^3.22.0",
|
|
30
|
+
"@react-aria/utils": "^3.27.0",
|
|
31
|
+
"@react-stately/collections": "^3.12.1",
|
|
32
|
+
"@react-stately/menu": "^3.9.1",
|
|
33
|
+
"@react-stately/selection": "^3.19.0",
|
|
34
|
+
"@react-stately/tree": "^3.8.7",
|
|
35
|
+
"@react-types/button": "^3.10.2",
|
|
36
|
+
"@react-types/menu": "^3.9.14",
|
|
37
|
+
"@react-types/shared": "^3.27.0",
|
|
37
38
|
"@swc/helpers": "^0.5.0"
|
|
38
39
|
},
|
|
39
40
|
"peerDependencies": {
|
|
40
|
-
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0",
|
|
41
|
-
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0"
|
|
41
|
+
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
|
|
42
|
+
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
|
42
43
|
},
|
|
43
44
|
"publishConfig": {
|
|
44
45
|
"access": "public"
|
|
45
46
|
},
|
|
46
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "09e7f44bebdc9d89122926b2b439a0a38a2814ea"
|
|
47
48
|
}
|
package/src/useMenu.ts
CHANGED
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import {AriaMenuProps} from '@react-types/menu';
|
|
14
|
-
import {DOMAttributes,
|
|
14
|
+
import {DOMAttributes, KeyboardDelegate, KeyboardEvents, RefObject} from '@react-types/shared';
|
|
15
15
|
import {filterDOMProps, mergeProps} from '@react-aria/utils';
|
|
16
|
+
import {menuData} from './utils';
|
|
16
17
|
import {TreeState} from '@react-stately/tree';
|
|
17
18
|
import {useSelectableList} from '@react-aria/selection';
|
|
18
19
|
|
|
@@ -24,21 +25,17 @@ export interface MenuAria {
|
|
|
24
25
|
export interface AriaMenuOptions<T> extends Omit<AriaMenuProps<T>, 'children'>, KeyboardEvents {
|
|
25
26
|
/** Whether the menu uses virtual scrolling. */
|
|
26
27
|
isVirtualized?: boolean,
|
|
27
|
-
|
|
28
28
|
/**
|
|
29
29
|
* An optional keyboard delegate implementation for type to select,
|
|
30
30
|
* to override the default.
|
|
31
31
|
*/
|
|
32
|
-
keyboardDelegate?: KeyboardDelegate
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
onAction?: (key: Key) => void
|
|
32
|
+
keyboardDelegate?: KeyboardDelegate,
|
|
33
|
+
/**
|
|
34
|
+
* Whether the menu items should use virtual focus instead of being focused directly.
|
|
35
|
+
*/
|
|
36
|
+
shouldUseVirtualFocus?: boolean
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
export const menuData = new WeakMap<TreeState<unknown>, MenuData>();
|
|
41
|
-
|
|
42
39
|
/**
|
|
43
40
|
* Provides the behavior and accessibility implementation for a menu component.
|
|
44
41
|
* A menu displays a list of actions or options that a user can choose.
|
|
@@ -70,7 +67,8 @@ export function useMenu<T>(props: AriaMenuOptions<T>, state: TreeState<T>, ref:
|
|
|
70
67
|
|
|
71
68
|
menuData.set(state, {
|
|
72
69
|
onClose: props.onClose,
|
|
73
|
-
onAction: props.onAction
|
|
70
|
+
onAction: props.onAction,
|
|
71
|
+
shouldUseVirtualFocus: props.shouldUseVirtualFocus
|
|
74
72
|
});
|
|
75
73
|
|
|
76
74
|
return {
|
|
@@ -79,8 +77,8 @@ export function useMenu<T>(props: AriaMenuOptions<T>, state: TreeState<T>, ref:
|
|
|
79
77
|
...listProps,
|
|
80
78
|
onKeyDown: (e) => {
|
|
81
79
|
// don't clear the menu selected keys if the user is presses escape since escape closes the menu
|
|
82
|
-
if (e.key !== 'Escape') {
|
|
83
|
-
listProps.onKeyDown(e);
|
|
80
|
+
if (e.key !== 'Escape' || props.shouldUseVirtualFocus) {
|
|
81
|
+
listProps.onKeyDown?.(e);
|
|
84
82
|
}
|
|
85
83
|
}
|
|
86
84
|
})
|
package/src/useMenuItem.ts
CHANGED
|
@@ -14,7 +14,8 @@ import {DOMAttributes, DOMProps, FocusableElement, FocusEvents, HoverEvents, Key
|
|
|
14
14
|
import {filterDOMProps, mergeProps, useLinkProps, useRouter, useSlotId} from '@react-aria/utils';
|
|
15
15
|
import {getItemCount} from '@react-stately/collections';
|
|
16
16
|
import {isFocusVisible, useFocus, useHover, useKeyboard, usePress} from '@react-aria/interactions';
|
|
17
|
-
import {menuData} from './
|
|
17
|
+
import {menuData} from './utils';
|
|
18
|
+
import {SelectionManager} from '@react-stately/selection';
|
|
18
19
|
import {TreeState} from '@react-stately/tree';
|
|
19
20
|
import {useSelectableItem} from '@react-aria/selection';
|
|
20
21
|
|
|
@@ -58,7 +59,7 @@ export interface AriaMenuItemProps extends DOMProps, PressEvents, HoverEvents, K
|
|
|
58
59
|
'aria-label'?: string,
|
|
59
60
|
|
|
60
61
|
/** The unique key for the menu item. */
|
|
61
|
-
key
|
|
62
|
+
key: Key,
|
|
62
63
|
|
|
63
64
|
/**
|
|
64
65
|
* Handler that is called when the menu should close after selecting an item.
|
|
@@ -88,7 +89,10 @@ export interface AriaMenuItemProps extends DOMProps, PressEvents, HoverEvents, K
|
|
|
88
89
|
'aria-expanded'?: boolean | 'true' | 'false',
|
|
89
90
|
|
|
90
91
|
/** Identifies the menu item's popup element whose contents or presence is controlled by the menu item. */
|
|
91
|
-
'aria-controls'?: string
|
|
92
|
+
'aria-controls'?: string,
|
|
93
|
+
|
|
94
|
+
/** Override of the selection manager. By default, `state.selectionManager` is used. */
|
|
95
|
+
selectionManager?: SelectionManager
|
|
92
96
|
}
|
|
93
97
|
|
|
94
98
|
/**
|
|
@@ -106,7 +110,7 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
|
|
|
106
110
|
'aria-haspopup': hasPopup,
|
|
107
111
|
onPressStart: pressStartProp,
|
|
108
112
|
onPressUp: pressUpProp,
|
|
109
|
-
onPress,
|
|
113
|
+
onPress: pressProp,
|
|
110
114
|
onPressChange,
|
|
111
115
|
onPressEnd,
|
|
112
116
|
onHoverStart: hoverStartProp,
|
|
@@ -116,13 +120,15 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
|
|
|
116
120
|
onKeyUp,
|
|
117
121
|
onFocus,
|
|
118
122
|
onFocusChange,
|
|
119
|
-
onBlur
|
|
123
|
+
onBlur,
|
|
124
|
+
selectionManager = state.selectionManager
|
|
120
125
|
} = props;
|
|
121
126
|
|
|
122
127
|
let isTrigger = !!hasPopup;
|
|
123
|
-
let
|
|
124
|
-
let
|
|
125
|
-
let
|
|
128
|
+
let isTriggerExpanded = isTrigger && props['aria-expanded'] === 'true';
|
|
129
|
+
let isDisabled = props.isDisabled ?? selectionManager.isDisabled(key);
|
|
130
|
+
let isSelected = props.isSelected ?? selectionManager.isSelected(key);
|
|
131
|
+
let data = menuData.get(state)!;
|
|
126
132
|
let item = state.collection.getItem(key);
|
|
127
133
|
let onClose = props.onClose || data.onClose;
|
|
128
134
|
let router = useRouter();
|
|
@@ -143,16 +149,16 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
|
|
|
143
149
|
onAction(key);
|
|
144
150
|
}
|
|
145
151
|
|
|
146
|
-
if (e.target instanceof HTMLAnchorElement) {
|
|
152
|
+
if (e.target instanceof HTMLAnchorElement && item) {
|
|
147
153
|
router.open(e.target, e, item.props.href, item.props.routerOptions as RouterOptions);
|
|
148
154
|
}
|
|
149
155
|
};
|
|
150
156
|
|
|
151
157
|
let role = 'menuitem';
|
|
152
158
|
if (!isTrigger) {
|
|
153
|
-
if (
|
|
159
|
+
if (selectionManager.selectionMode === 'single') {
|
|
154
160
|
role = 'menuitemradio';
|
|
155
|
-
} else if (
|
|
161
|
+
} else if (selectionManager.selectionMode === 'multiple') {
|
|
156
162
|
role = 'menuitemcheckbox';
|
|
157
163
|
}
|
|
158
164
|
}
|
|
@@ -173,7 +179,7 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
|
|
|
173
179
|
'aria-expanded': props['aria-expanded']
|
|
174
180
|
};
|
|
175
181
|
|
|
176
|
-
if (
|
|
182
|
+
if (selectionManager.selectionMode !== 'none' && !isTrigger) {
|
|
177
183
|
ariaProps['aria-checked'] = isSelected;
|
|
178
184
|
}
|
|
179
185
|
|
|
@@ -190,22 +196,37 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
|
|
|
190
196
|
pressStartProp?.(e);
|
|
191
197
|
};
|
|
192
198
|
|
|
199
|
+
let maybeClose = () => {
|
|
200
|
+
// Pressing a menu item should close by default in single selection mode but not multiple
|
|
201
|
+
// selection mode, except if overridden by the closeOnSelect prop.
|
|
202
|
+
if (!isTrigger && onClose && (closeOnSelect ?? (selectionManager.selectionMode !== 'multiple' || selectionManager.isLink(key)))) {
|
|
203
|
+
onClose();
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
193
207
|
let onPressUp = (e: PressEvent) => {
|
|
194
|
-
|
|
208
|
+
// If interacting with mouse, allow the user to mouse down on the trigger button,
|
|
209
|
+
// drag, and release over an item (matching native behavior).
|
|
210
|
+
if (e.pointerType === 'mouse') {
|
|
195
211
|
performAction(e);
|
|
196
|
-
|
|
197
|
-
// Pressing a menu item should close by default in single selection mode but not multiple
|
|
198
|
-
// selection mode, except if overridden by the closeOnSelect prop.
|
|
199
|
-
if (!isTrigger && onClose && (closeOnSelect ?? (state.selectionManager.selectionMode !== 'multiple' || state.selectionManager.isLink(key)))) {
|
|
200
|
-
onClose();
|
|
201
|
-
}
|
|
212
|
+
maybeClose();
|
|
202
213
|
}
|
|
203
214
|
|
|
204
215
|
pressUpProp?.(e);
|
|
205
216
|
};
|
|
206
217
|
|
|
218
|
+
let onPress = (e: PressEvent) => {
|
|
219
|
+
if (e.pointerType !== 'keyboard' && e.pointerType !== 'mouse') {
|
|
220
|
+
performAction(e);
|
|
221
|
+
maybeClose();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
pressProp?.(e);
|
|
225
|
+
};
|
|
226
|
+
|
|
207
227
|
let {itemProps, isFocused} = useSelectableItem({
|
|
208
|
-
|
|
228
|
+
id,
|
|
229
|
+
selectionManager: selectionManager,
|
|
209
230
|
key,
|
|
210
231
|
ref,
|
|
211
232
|
shouldSelectOnPressUp: true,
|
|
@@ -214,7 +235,8 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
|
|
|
214
235
|
// because we handle it ourselves. The behavior of menus
|
|
215
236
|
// is slightly different from other collections because
|
|
216
237
|
// actions are performed on key down rather than key up.
|
|
217
|
-
linkBehavior: 'none'
|
|
238
|
+
linkBehavior: 'none',
|
|
239
|
+
shouldUseVirtualFocus: data.shouldUseVirtualFocus
|
|
218
240
|
});
|
|
219
241
|
|
|
220
242
|
let {pressProps, isPressed} = usePress({
|
|
@@ -228,9 +250,10 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
|
|
|
228
250
|
let {hoverProps} = useHover({
|
|
229
251
|
isDisabled,
|
|
230
252
|
onHoverStart(e) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
253
|
+
// Hovering over an already expanded sub dialog trigger should keep focus in the dialog.
|
|
254
|
+
if (!isFocusVisible() && !(isTriggerExpanded && hasPopup === 'dialog')) {
|
|
255
|
+
selectionManager.setFocused(true);
|
|
256
|
+
selectionManager.setFocusedKey(key);
|
|
234
257
|
}
|
|
235
258
|
hoverStartProp?.(e);
|
|
236
259
|
},
|
|
@@ -249,7 +272,7 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
|
|
|
249
272
|
|
|
250
273
|
switch (e.key) {
|
|
251
274
|
case ' ':
|
|
252
|
-
if (!isDisabled &&
|
|
275
|
+
if (!isDisabled && selectionManager.selectionMode === 'none' && !isTrigger && closeOnSelect !== false && onClose) {
|
|
253
276
|
onClose();
|
|
254
277
|
}
|
|
255
278
|
break;
|
|
@@ -272,15 +295,16 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
|
|
|
272
295
|
});
|
|
273
296
|
|
|
274
297
|
let {focusProps} = useFocus({onBlur, onFocus, onFocusChange});
|
|
275
|
-
let domProps = filterDOMProps(item
|
|
298
|
+
let domProps = filterDOMProps(item?.props);
|
|
276
299
|
delete domProps.id;
|
|
277
|
-
let linkProps = useLinkProps(item
|
|
300
|
+
let linkProps = useLinkProps(item?.props);
|
|
278
301
|
|
|
279
302
|
return {
|
|
280
303
|
menuItemProps: {
|
|
281
304
|
...ariaProps,
|
|
282
305
|
...mergeProps(domProps, linkProps, isTrigger ? {onFocus: itemProps.onFocus, 'data-key': itemProps['data-key']} : itemProps, pressProps, hoverProps, keyboardProps, focusProps),
|
|
283
|
-
|
|
306
|
+
// If a submenu is expanded, set the tabIndex to -1 so that shift tabbing goes out of the menu instead of the parent menu item.
|
|
307
|
+
tabIndex: itemProps.tabIndex != null && isTriggerExpanded ? -1 : itemProps.tabIndex
|
|
284
308
|
},
|
|
285
309
|
labelProps: {
|
|
286
310
|
id: labelId
|
package/src/useMenuTrigger.ts
CHANGED
|
@@ -47,7 +47,7 @@ export interface MenuTriggerAria<T> {
|
|
|
47
47
|
*/
|
|
48
48
|
export function useMenuTrigger<T>(props: AriaMenuTriggerProps, state: MenuTriggerState, ref: RefObject<Element | null>): MenuTriggerAria<T> {
|
|
49
49
|
let {
|
|
50
|
-
type = 'menu'
|
|
50
|
+
type = 'menu',
|
|
51
51
|
isDisabled,
|
|
52
52
|
trigger = 'press'
|
|
53
53
|
} = props;
|
|
@@ -128,6 +128,7 @@ export function useMenuTrigger<T>(props: AriaMenuTriggerProps, state: MenuTrigge
|
|
|
128
128
|
delete triggerProps.onPress;
|
|
129
129
|
|
|
130
130
|
return {
|
|
131
|
+
// @ts-ignore - TODO we pass out both DOMAttributes AND AriaButtonProps, but useButton will discard the longPress event handlers, it's only through PressResponder magic that this works for RSP and RAC. it does not work in aria examples
|
|
131
132
|
menuTriggerProps: {
|
|
132
133
|
...triggerProps,
|
|
133
134
|
...(trigger === 'press' ? pressProps : longPressProps),
|
|
@@ -63,7 +63,7 @@ export function useSafelyMouseToSubmenu(options: SafelyMouseToSubmenuOptions) {
|
|
|
63
63
|
let submenu = submenuRef.current;
|
|
64
64
|
let menu = menuRef.current;
|
|
65
65
|
|
|
66
|
-
if (isDisabled || !submenu || !isOpen || modality !== 'pointer') {
|
|
66
|
+
if (isDisabled || !submenu || !isOpen || modality !== 'pointer' || !menu) {
|
|
67
67
|
reset();
|
|
68
68
|
return;
|
|
69
69
|
}
|
package/src/useSubmenuTrigger.ts
CHANGED
|
@@ -41,7 +41,7 @@ export interface AriaSubmenuTriggerProps {
|
|
|
41
41
|
delay?: number
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
interface SubmenuTriggerProps extends AriaMenuItemProps {
|
|
44
|
+
interface SubmenuTriggerProps extends Omit<AriaMenuItemProps, 'key'> {
|
|
45
45
|
/** Whether the submenu trigger is in an expanded state. */
|
|
46
46
|
isOpen: boolean
|
|
47
47
|
}
|
|
@@ -101,14 +101,14 @@ export function useSubmenuTrigger<T>(props: AriaSubmenuTriggerProps, state: Subm
|
|
|
101
101
|
if (direction === 'ltr' && e.currentTarget.contains(e.target as Element)) {
|
|
102
102
|
e.stopPropagation();
|
|
103
103
|
onSubmenuClose();
|
|
104
|
-
ref.current
|
|
104
|
+
ref.current?.focus();
|
|
105
105
|
}
|
|
106
106
|
break;
|
|
107
107
|
case 'ArrowRight':
|
|
108
108
|
if (direction === 'rtl' && e.currentTarget.contains(e.target as Element)) {
|
|
109
109
|
e.stopPropagation();
|
|
110
110
|
onSubmenuClose();
|
|
111
|
-
ref.current
|
|
111
|
+
ref.current?.focus();
|
|
112
112
|
}
|
|
113
113
|
break;
|
|
114
114
|
case 'Escape':
|
|
@@ -124,7 +124,7 @@ export function useSubmenuTrigger<T>(props: AriaSubmenuTriggerProps, state: Subm
|
|
|
124
124
|
submenuLevel: state.submenuLevel,
|
|
125
125
|
...(type === 'menu' && {
|
|
126
126
|
onClose: state.closeAll,
|
|
127
|
-
autoFocus: state.focusStrategy,
|
|
127
|
+
autoFocus: state.focusStrategy ?? undefined,
|
|
128
128
|
onKeyDown: submenuKeyDown
|
|
129
129
|
})
|
|
130
130
|
};
|
|
@@ -205,7 +205,7 @@ export function useSubmenuTrigger<T>(props: AriaSubmenuTriggerProps, state: Subm
|
|
|
205
205
|
};
|
|
206
206
|
|
|
207
207
|
let onBlur = (e) => {
|
|
208
|
-
if (state.isOpen && parentMenuRef.current
|
|
208
|
+
if (state.isOpen && parentMenuRef.current?.contains(e.relatedTarget)) {
|
|
209
209
|
onSubmenuClose();
|
|
210
210
|
}
|
|
211
211
|
};
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {Key} from '@react-types/shared';
|
|
14
|
+
import {TreeState} from '@react-stately/tree';
|
|
15
|
+
|
|
16
|
+
interface MenuData {
|
|
17
|
+
onClose?: () => void,
|
|
18
|
+
onAction?: (key: Key) => void,
|
|
19
|
+
shouldUseVirtualFocus?: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const menuData = new WeakMap<TreeState<unknown>, MenuData>();
|