diff --git a/packages/@react-aria/menu/src/useSubmenuTrigger.ts b/packages/@react-aria/menu/src/useSubmenuTrigger.ts index a4c6d26d15b..01048b335bb 100644 --- a/packages/@react-aria/menu/src/useSubmenuTrigger.ts +++ b/packages/@react-aria/menu/src/useSubmenuTrigger.ts @@ -38,7 +38,9 @@ export interface AriaSubmenuTriggerProps { * The delay time in milliseconds for the submenu to appear after hovering over the trigger. * @default 200 */ - delay?: number + delay?: number, + /** Whether the submenu trigger uses virtual focus. */ + isVirtualFocus?: boolean } interface SubmenuTriggerProps extends Omit { @@ -67,7 +69,7 @@ export interface SubmenuTriggerAria { * @param ref - Ref to the submenu trigger element. */ export function useSubmenuTrigger(props: AriaSubmenuTriggerProps, state: SubmenuTriggerState, ref: RefObject): SubmenuTriggerAria { - let {parentMenuRef, submenuRef, type = 'menu', isDisabled, delay = 200} = props; + let {parentMenuRef, submenuRef, type = 'menu', isDisabled, delay = 200, isVirtualFocus} = props; let submenuTriggerId = useId(); let overlayId = useId(); let {direction} = useLocale(); @@ -101,14 +103,18 @@ export function useSubmenuTrigger(props: AriaSubmenuTriggerProps, state: Subm if (direction === 'ltr' && e.currentTarget.contains(e.target as Element)) { e.stopPropagation(); onSubmenuClose(); - ref.current?.focus(); + if (!isVirtualFocus) { + ref.current?.focus(); + } } break; case 'ArrowRight': if (direction === 'rtl' && e.currentTarget.contains(e.target as Element)) { e.stopPropagation(); onSubmenuClose(); - ref.current?.focus(); + if (!isVirtualFocus) { + ref.current?.focus(); + } } break; case 'Escape': @@ -121,9 +127,10 @@ export function useSubmenuTrigger(props: AriaSubmenuTriggerProps, state: Subm let subDialogKeyDown = (e: KeyboardEvent) => { switch (e.key) { case 'Escape': - e.stopPropagation(); onSubmenuClose(); - ref.current?.focus(); + if (!isVirtualFocus) { + ref.current?.focus(); + } break; } }; @@ -247,8 +254,7 @@ export function useSubmenuTrigger(props: AriaSubmenuTriggerProps, state: Subm submenuProps, popoverProps: { isNonModal: true, - // TODO: does this break anything in RSP implementation? - disableFocusManagement: type === 'menu', + disableFocusManagement: type === 'menu' && !isVirtualFocus, shouldCloseOnInteractOutside } }; diff --git a/packages/react-aria-components/src/Menu.tsx b/packages/react-aria-components/src/Menu.tsx index b9078aa665d..437f99d15ee 100644 --- a/packages/react-aria-components/src/Menu.tsx +++ b/packages/react-aria-components/src/Menu.tsx @@ -118,7 +118,7 @@ export interface SubmenuTriggerProps { delay?: number } -const SubmenuTriggerContext = createContext<{parentMenuRef: RefObject} | null>(null); +const SubmenuTriggerContext = createContext<{parentMenuRef: RefObject, isVirtualFocus?: boolean} | null>(null); /** * A submenu trigger is used to wrap a submenu's trigger item and the submenu itself. @@ -132,11 +132,12 @@ export const SubmenuTrigger = /*#__PURE__*/ createBranchComponent('submenutrigg let submenuTriggerState = useSubmenuTriggerState({triggerKey: item.key}, rootMenuTriggerState); let submenuRef = useRef(null); let itemRef = useObjectRef(ref); - let {parentMenuRef} = useContext(SubmenuTriggerContext)!; + let {parentMenuRef, isVirtualFocus} = useContext(SubmenuTriggerContext)!; let {submenuTriggerProps, submenuProps, popoverProps} = useSubmenuTrigger({ parentMenuRef, submenuRef, - delay: props.delay + delay: props.delay, + isVirtualFocus }, submenuTriggerState, itemRef); return ( @@ -187,7 +188,6 @@ export const SubdialogTrigger = /*#__PURE__*/ createBranchComponent('subdialogt let submenuTriggerState = useSubmenuTriggerState({triggerKey: item.key}, rootMenuTriggerState); let subdialogRef = useRef(null); let itemRef = useObjectRef(ref); - // TODO: We will probably support nested subdialogs so test that use case let {parentMenuRef} = useContext(SubmenuTriggerContext)!; let {submenuTriggerProps, submenuProps, popoverProps} = useSubmenuTrigger({ parentMenuRef, @@ -302,8 +302,9 @@ function MenuInner({props, collection, menuRef: ref}: MenuInne [MenuStateContext, state], [SeparatorContext, {elementType: 'div'}], [SectionContext, {name: 'MenuSection', render: MenuSectionInner}], - [SubmenuTriggerContext, {parentMenuRef: ref}], + [SubmenuTriggerContext, {parentMenuRef: ref, isVirtualFocus: autocompleteMenuProps?.shouldUseVirtualFocus}], [MenuItemContext, null], + [UNSTABLE_InternalAutocompleteContext, null], [SelectionManagerContext, state.selectionManager] ]}> { let {onAction, onSelectionChange, selectionMode} = args;