Close a persistent rich tooltip on a second press of its trigger

The bubble is a popover="auto" and the trigger is outside it, so the press
on the trigger light-dismissed the open bubble and the click that followed
opened it again: a second press never closed it, in any engine. Found by
the new exit test. A close the browser made within 250ms is now taken for
that press, as menu.js's REOPEN_GUARD_MS does, timed from beforetoggle
because toggle is queued past the click; the morph test waits past the
guard before pressing again, as the menu tests do.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andreas Reinhold / reini
2026-09-16 20:24:13 +02:00
co-authored by Claude Opus 5
parent 6a133c2c67
commit 684d60efc8
2 changed files with 27 additions and 2 deletions
+23 -1
View File
@@ -18,6 +18,12 @@
const HOVER_DELAY_MS = 500
const LEAVE_GRACE_MS = 1500
// A persistent bubble is a `popover="auto"`, and its trigger is outside it: the press on the
// trigger light-dismisses the open bubble, and the click that follows would open it again, so a
// second press never closed it. A close the browser made this recently is taken as that press, as
// menu.js's REOPEN_GUARD_MS does; `beforetoggle` times it, because `toggle` is queued past the click.
const REOPEN_GUARD_MS = 250
document.addEventListener('alpine:init', () => {
window.Alpine.data('materialRichTooltip', (persistent = false) => ({
timer: null,
@@ -50,12 +56,28 @@ document.addEventListener('alpine:init', () => {
wrapper.addEventListener('pointerenter', describe)
if (persistent) {
let dismissedAt = -Infinity
let closingExplicitly = false
bubble.addEventListener('beforetoggle', (event) => {
if (event.newState === 'closed' && !closingExplicitly) {
dismissedAt = performance.now()
}
closingExplicitly = false
})
wrapper.addEventListener('click', (event) => {
if (bubble.contains(event.target)) {
return
}
open() ? bubble.hidePopover() : bubble.showPopover()
if (open()) {
closingExplicitly = true
bubble.hidePopover()
} else if (performance.now() - dismissedAt > REOPEN_GUARD_MS) {
bubble.showPopover()
}
})
return