diff --git a/packages/radix-vue/src/NavigationMenu/NavigationMenuContent.vue b/packages/radix-vue/src/NavigationMenu/NavigationMenuContent.vue index 6cd527b76..c609e7d73 100644 --- a/packages/radix-vue/src/NavigationMenu/NavigationMenuContent.vue +++ b/packages/radix-vue/src/NavigationMenu/NavigationMenuContent.vue @@ -28,7 +28,7 @@ const commonProps = computed(() => ({ triggerRef: itemContext!.triggerRef, focusProxyRef: itemContext!.focusProxyRef, wasEscapeCloseRef: itemContext!.wasEscapeCloseRef, - // onContentFocusOutside: itemContext!.onContentFocusOutside, + onContentFocusOutside: itemContext!.onContentFocusOutside, // onRootContentClose: itemContext!.onRootContentClose, })); diff --git a/packages/radix-vue/src/NavigationMenu/NavigationMenuContentImpl.vue b/packages/radix-vue/src/NavigationMenu/NavigationMenuContentImpl.vue index e50b8d099..9b7fe2238 100644 --- a/packages/radix-vue/src/NavigationMenu/NavigationMenuContentImpl.vue +++ b/packages/radix-vue/src/NavigationMenu/NavigationMenuContentImpl.vue @@ -6,7 +6,7 @@ interface NavigationMenuContentImplProps { triggerRef: Ref; focusProxyRef: Ref; wasEscapeCloseRef: Ref; - // onContentFocusOutside(): void; + onContentFocusOutside(): void; // onRootContentClose(): void; } @@ -20,7 +20,7 @@ import { makeContentId, makeTriggerId, } from "./utils"; -import { useArrowNavigation, useCollection } from "@/shared"; +import { useArrowNavigation, useCollection, onFocusOutside } from "@/shared"; const props = defineProps(); const emits = defineEmits<{ @@ -29,6 +29,7 @@ const emits = defineEmits<{ const { getItems } = useCollection(); +const elementRef = ref(); const context = inject(NAVIGATION_MENU_INJECTION_KEY); const triggerId = makeTriggerId(context!.baseId, props.value); const contentId = makeContentId(context!.baseId, props.value); @@ -67,44 +68,37 @@ const motionAttribute = computed(() => { return attribute; }); +onFocusOutside(elementRef, (ev) => { + props.onContentFocusOutside(); + const target = ev.target as HTMLElement; + // Only dismiss content when focus moves outside of the menu + if (context!.rootNavigationMenu?.value?.contains(target)) ev.preventDefault(); +}); + const handleKeydown = (ev: KeyboardEvent) => { const isMetaKey = ev.altKey || ev.ctrlKey || ev.metaKey; const isTabKey = ev.key === "Tab" && !isMetaKey; const candidates = getTabbableCandidates(ev.currentTarget as HTMLElement); - const triggerItems = getItems(context?.rootNavigationMenu.value); - const triggerButtonIndex = triggerItems.findIndex( - (i) => i === props.triggerRef.value - ); - const nextTriggerButton = triggerItems[triggerButtonIndex + 1]; if (isTabKey) { const focusedElement = document.activeElement; const index = candidates.findIndex( (candidate) => candidate === focusedElement ); - if ( - index === candidates.length - 1 && - triggerButtonIndex !== triggerItems.length - 1 - ) { - return nextTriggerButton.focus(); - } const isMovingBackwards = ev.shiftKey; - if (isMovingBackwards && index === 0) { - props.triggerRef.value?.focus(); + const nextCandidates = isMovingBackwards + ? candidates.slice(0, index).reverse() + : candidates.slice(index + 1, candidates.length); + + if (focusFirst(nextCandidates)) { + // prevent browser tab keydown because we've handled focus + ev.preventDefault(); } else { - const nextCandidates = isMovingBackwards - ? candidates.slice(0, index).reverse() - : candidates.slice(index + 1, candidates.length); - - if (focusFirst(nextCandidates)) { - // prevent browser tab keydown because we've handled focus - ev.preventDefault(); - } else { - // If we can't focus that means we're at the edges - // so focus the proxy and let browser handle - // tab/shift+tab keypress on the proxy instead - props.focusProxyRef.value?.focus(); - } + // If we can't focus that means we're at the edges + // so focus the proxy and let browser handle + // tab/shift+tab keypress on the proxy instead + props.focusProxyRef.value?.focus(); + return; } } @@ -114,8 +108,8 @@ const handleKeydown = (ev: KeyboardEvent) => { undefined, { itemsArray: candidates, loop: false } ); - newSelectedElement?.focus(); + ev.preventDefault(); }; const handleEscape = (ev: KeyboardEvent) => { @@ -129,10 +123,11 @@ defineExpose({