diff --git a/packages/vuetify/src/components/VMenu/VMenu.tsx b/packages/vuetify/src/components/VMenu/VMenu.tsx index 7150113d79d..1399e141a85 100644 --- a/packages/vuetify/src/components/VMenu/VMenu.tsx +++ b/packages/vuetify/src/components/VMenu/VMenu.tsx @@ -109,6 +109,16 @@ export const VMenu = genericComponent()({ }) onDeactivated(() => isActive.value = false) + let focusTrapSuppressed = false + let focusTrapSuppressionTimeout = -1 + + async function onPointerdown () { + focusTrapSuppressed = true + focusTrapSuppressionTimeout = window.setTimeout(() => { + focusTrapSuppressed = false + }, 100) + } + async function onFocusIn (e: FocusEvent) { const before = e.relatedTarget as HTMLElement | null const after = e.target as HTMLElement | null @@ -119,15 +129,21 @@ export const VMenu = genericComponent()({ isActive.value && before !== after && overlay.value?.contentEl && - // We're the topmost menu - overlay.value?.globalTop && // It isn't the document or the menu body ![document, overlay.value.contentEl].includes(after!) && // It isn't inside the menu body !overlay.value.contentEl.contains(after) ) { - const focusable = focusableChildren(overlay.value.contentEl) - focusable[0]?.focus() + if (focusTrapSuppressed) { + if (!props.openOnHover) { + isActive.value = false + } + } else { + const focusable = focusableChildren(overlay.value.contentEl) + focusable[0]?.focus() + + document.removeEventListener('pointerdown', onPointerdown) + } } } @@ -135,11 +151,14 @@ export const VMenu = genericComponent()({ if (val) { parent?.register() if (IN_BROWSER && !props.disableInitialFocus) { + document.addEventListener('pointerdown', onPointerdown) document.addEventListener('focusin', onFocusIn, { once: true }) } } else { parent?.unregister() if (IN_BROWSER) { + clearTimeout(focusTrapSuppressionTimeout) + document.removeEventListener('pointerdown', onPointerdown) document.removeEventListener('focusin', onFocusIn) } }