Scope the submenu reopen guard to the browser's own light dismiss

A submenu's close() marked every close it caused as recent, so the
250ms guard meant to stop a trigger's own click reopening a popover
the same press just light-dismissed also blocked a deliberate
keyboard reopen (Enter/Space right after ArrowLeft) that happened to
land within that window. Only a close the browser made on its own
now counts toward the guard; close() marks its own hidePopover() call
explicit so it never seeds it. Fixes the "opens a submenu with Right,
Enter or Space" browser test (plan step 36, actions group).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
Andreas Reinhold / reini
2026-09-14 18:26:51 +02:00
co-authored by Claude Sonnet 5
parent 884f585e4d
commit 2b3d3d507c
+9 -1
View File
@@ -45,7 +45,9 @@ const ITEMS = '[role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradi
// A popover="auto" closes on the press that lands on its trigger, and the click that follows // A popover="auto" closes on the press that lands on its trigger, and the click that follows
// would open it again. A close this recent is taken as that press. It is timed from // would open it again. A close this recent is taken as that press. It is timed from
// `beforetoggle`, which fires as the popover closes: `toggle` is queued, and arrives after that // `beforetoggle`, which fires as the popover closes: `toggle` is queued, and arrives after that
// click. // click. Only a close the browser made on its own (light dismiss) sets it — `close()` marks its
// own as explicit, so a keyboard command that shuts a menu never guards against reopening it a
// moment later, only the browser's own light dismiss racing the click it precedes does.
const REOPEN_GUARD_MS = 250 const REOPEN_GUARD_MS = 250
// A submenu opens after the pointer has rested on its item for a moment, and closes a moment after // A submenu opens after the pointer has rested on its item for a moment, and closes a moment after
@@ -56,6 +58,7 @@ const HOVER_CLOSE_MS = 320
const menu = () => ({ const menu = () => ({
closedAt: -Infinity, closedAt: -Infinity,
closingExplicitly: false,
anchored: null, anchored: null,
returnFocus: true, returnFocus: true,
focusWasInside: false, focusWasInside: false,
@@ -104,8 +107,12 @@ const menu = () => ({
this.focusWasInside = event.newState === 'closed' && menu.contains(document.activeElement) this.focusWasInside = event.newState === 'closed' && menu.contains(document.activeElement)
if (event.newState === 'closed') { if (event.newState === 'closed') {
if (!this.closingExplicitly) {
this.closedAt = performance.now() this.closedAt = performance.now()
} }
this.closingExplicitly = false
}
}) })
this.listen(menu, 'toggle', (event) => { this.listen(menu, 'toggle', (event) => {
@@ -336,6 +343,7 @@ const menu = () => ({
} }
if (this.$refs.menu.matches(':popover-open')) { if (this.$refs.menu.matches(':popover-open')) {
this.closingExplicitly = true
this.$refs.menu.hidePopover() this.$refs.menu.hidePopover()
} }