Skip to content

Commit d1dafff

Browse files
authored
fix(VMenu): avoid scrolling to the off-screen menu (#22044)
fixes #21775 fixes #20569 fixes #21015 fixes #16819
1 parent bf337fb commit d1dafff

File tree

1 file changed

+25
-4
lines changed
  • packages/vuetify/src/components/VMenu

1 file changed

+25
-4
lines changed

packages/vuetify/src/components/VMenu/VMenu.tsx

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,16 @@ export const VMenu = genericComponent<OverlaySlots>()({
109109
})
110110
onDeactivated(() => isActive.value = false)
111111

112+
let focusTrapSuppressed = false
113+
let focusTrapSuppressionTimeout = -1
114+
115+
async function onPointerdown () {
116+
focusTrapSuppressed = true
117+
focusTrapSuppressionTimeout = window.setTimeout(() => {
118+
focusTrapSuppressed = false
119+
}, 100)
120+
}
121+
112122
async function onFocusIn (e: FocusEvent) {
113123
const before = e.relatedTarget as HTMLElement | null
114124
const after = e.target as HTMLElement | null
@@ -119,27 +129,38 @@ export const VMenu = genericComponent<OverlaySlots>()({
119129
isActive.value &&
120130
before !== after &&
121131
overlay.value?.contentEl &&
122-
// We're the topmost menu
123-
overlay.value?.globalTop &&
132+
// We're the menu without open submenus or overlays
133+
overlay.value?.localTop &&
124134
// It isn't the document or the menu body
125135
![document, overlay.value.contentEl].includes(after!) &&
126136
// It isn't inside the menu body
127137
!overlay.value.contentEl.contains(after)
128138
) {
129-
const focusable = focusableChildren(overlay.value.contentEl)
130-
focusable[0]?.focus()
139+
if (focusTrapSuppressed) {
140+
if (!props.openOnHover || !overlay.value.activatorEl?.contains(after)) {
141+
isActive.value = false
142+
}
143+
} else {
144+
const focusable = focusableChildren(overlay.value.contentEl)
145+
focusable[0]?.focus()
146+
147+
document.removeEventListener('pointerdown', onPointerdown)
148+
}
131149
}
132150
}
133151

134152
watch(isActive, val => {
135153
if (val) {
136154
parent?.register()
137155
if (IN_BROWSER && !props.disableInitialFocus) {
156+
document.addEventListener('pointerdown', onPointerdown)
138157
document.addEventListener('focusin', onFocusIn, { once: true })
139158
}
140159
} else {
141160
parent?.unregister()
142161
if (IN_BROWSER) {
162+
clearTimeout(focusTrapSuppressionTimeout)
163+
document.removeEventListener('pointerdown', onPointerdown)
143164
document.removeEventListener('focusin', onFocusIn)
144165
}
145166
}

0 commit comments

Comments
 (0)