react-aria
Version:
Spectrum UI components in React
1 lines • 13.3 kB
Source Map (JSON)
{"mappings":"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","sources":["packages/react-aria/src/menu/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} from '../overlays/usePopover';\nimport {\n FocusableElement,\n FocusStrategy,\n KeyboardEvent,\n Node,\n PressEvent,\n RefObject\n} from '@react-types/shared';\nimport {focusWithoutScrolling} from '../utils/focusWithoutScrolling';\nimport {\n getActiveElement,\n getEventTarget,\n isFocusWithin,\n nodeContains\n} from '../utils/shadowdom/DOMFunctions';\nimport type {OverlayProps} from '../overlays/Overlay';\nimport type {SubmenuTriggerState} from 'react-stately/useMenuTriggerState';\nimport {useCallback, useRef} from 'react';\nimport {useEvent} from '../utils/useEvent';\nimport {useId} from '../utils/useId';\nimport {useLayoutEffect} from '../utils/useLayoutEffect';\nimport {useLocale} from '../i18n/I18nProvider';\nimport {useSafelyMouseToSubmenu} from './useSafelyMouseToSubmenu';\n\nexport interface AriaSubmenuTriggerProps {\n /**\n * An object representing the submenu trigger menu item. Contains all the relevant information\n * that makes up the menu item.\n *\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 *\n * @default 200\n */\n delay?: number;\n /** Whether the submenu trigger uses virtual focus. */\n shouldUseVirtualFocus?: boolean;\n}\n\ninterface SubmenuTriggerProps extends Omit<AriaMenuItemProps, 'key' | 'onAction'> {\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'> &\n Pick<OverlayProps, 'disableFocusManagement'>;\n}\n\n/**\n * Provides the behavior and accessibility implementation for a submenu trigger and its associated\n * submenu.\n *\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>(\n props: AriaSubmenuTriggerProps,\n state: SubmenuTriggerState,\n ref: RefObject<FocusableElement | null>\n): SubmenuTriggerAria<T> {\n let {\n parentMenuRef,\n submenuRef,\n type = 'menu',\n isDisabled,\n delay = 200,\n shouldUseVirtualFocus\n } = 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 = useCallback(\n (focusStrategy?: FocusStrategy) => {\n cancelOpenTimeout();\n state.open(focusStrategy);\n },\n [state, cancelOpenTimeout]\n );\n\n let onSubmenuClose = useCallback(() => {\n cancelOpenTimeout();\n state.close();\n }, [state, cancelOpenTimeout]);\n\n useLayoutEffect(() => {\n return () => {\n cancelOpenTimeout();\n };\n }, [cancelOpenTimeout]);\n\n let submenuKeyDown = (e: KeyboardEvent) => {\n // If focus is not within the menu, assume virtual focus is being used.\n // This means some other input element is also within the popover, so we shouldn't close the menu.\n if (!isFocusWithin(e.currentTarget)) {\n return;\n }\n\n switch (e.key) {\n case 'ArrowLeft':\n if (direction === 'ltr' && nodeContains(e.currentTarget, getEventTarget(e) as Element)) {\n e.preventDefault();\n e.stopPropagation();\n onSubmenuClose();\n if (!shouldUseVirtualFocus && ref.current) {\n focusWithoutScrolling(ref.current);\n }\n }\n break;\n case 'ArrowRight':\n if (direction === 'rtl' && nodeContains(e.currentTarget, getEventTarget(e) as Element)) {\n e.preventDefault();\n e.stopPropagation();\n onSubmenuClose();\n if (!shouldUseVirtualFocus && ref.current) {\n focusWithoutScrolling(ref.current);\n }\n }\n break;\n case 'Escape':\n // TODO: can remove this when we fix collection event leaks\n if (nodeContains(submenuRef.current, getEventTarget(e) as Element)) {\n e.stopPropagation();\n onSubmenuClose();\n if (!shouldUseVirtualFocus && ref.current) {\n focusWithoutScrolling(ref.current);\n }\n }\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 e.preventDefault();\n if (!state.isOpen) {\n onSubmenuOpen('first');\n }\n\n if (type === 'menu' && !!submenuRef?.current && getActiveElement() === ref?.current) {\n focusWithoutScrolling(submenuRef.current);\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 e.preventDefault();\n if (!state.isOpen) {\n onSubmenuOpen('first');\n }\n\n if (type === 'menu' && !!submenuRef?.current && getActiveElement() === ref?.current) {\n focusWithoutScrolling(submenuRef.current);\n }\n } else if (state.isOpen) {\n onSubmenuClose();\n } else {\n e.continuePropagation();\n }\n }\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 useEvent(parentMenuRef, 'focusin', e => {\n // If we detect focus moved to a different item in the same menu that the currently open submenu trigger is in\n // then close the submenu. This is for a case where the user hovers a root menu item when multiple submenus are open\n if (\n state.isOpen &&\n nodeContains(parentMenuRef.current, getEventTarget(e) as HTMLElement) &&\n getEventTarget(e) !== ref.current\n ) {\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({\n menuRef: parentMenuRef,\n submenuRef,\n isOpen: state.isOpen,\n isDisabled: isDisabled\n });\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 isOpen: state.isOpen\n },\n submenuProps,\n popoverProps: {\n isNonModal: true,\n shouldCloseOnInteractOutside\n }\n };\n}\n"],"names":[],"version":3,"file":"useSubmenuTrigger.mjs.map"}