Fix tooltip (3)

This commit is contained in:
MartinBraquet
2026-02-11 16:52:37 +01:00
parent 9c00fffb89
commit f42a1ad64f

View File

@@ -40,18 +40,36 @@ export function Tooltip(props: {
const [open, setOpen] = useState(false)
// --- Mobile tap suppression to prevent accidental neighbor tooltips ---
// After a tap/click on touch devices, browsers may emit hover/focus events on elements
// that appear under the finger due to layout shifts (e.g., when a card is removed).
// --- Mobile/tap guards to prevent accidental neighbor tooltips ---
// After a tap/click on touch devices, browsers may emit hover/focus-like events
// on elements that appear under the finger due to layout shifts (e.g., when a card is removed).
// We keep a short global cooldown window during which tooltip "open" requests are ignored.
// This helps avoid ghost tooltips appearing on adjacent cards after actions like hide/remove.
// Additionally, we default hover behavior to mouse-only on touch-capable devices.
const nowFn = () => Date.now()
const SUPPRESS_MS = 900
// Module-level shared state
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const g: any = (globalThis as any)
if (g.__tooltipLastTapTs === undefined) g.__tooltipLastTapTs = 0 as number
if (g.__tooltipListenersSetup === undefined) g.__tooltipListenersSetup = false as boolean
const isTouchCapable = () => {
if (typeof window === 'undefined') return false
// Prefer pointer hints
// noinspection JSUnresolvedReference
const nav: any = navigator as any
const hasMP = !!nav && typeof nav.maxTouchPoints === 'number' && nav.maxTouchPoints > 0
const hasTP = !!nav && typeof nav.msMaxTouchPoints === 'number' && nav.msMaxTouchPoints > 0
const mm = typeof window.matchMedia === 'function'
? window.matchMedia('(hover: none) and (pointer: coarse)')
: null
const mm2 = typeof window.matchMedia === 'function'
? window.matchMedia('(any-hover: none)')
: null
const hasOntouch = 'ontouchstart' in window
return hasMP || hasTP || !!mm?.matches || !!mm2?.matches || hasOntouch
}
useEffect(() => {
if (g.__tooltipListenersSetup) return
if (typeof window === 'undefined') return
@@ -60,9 +78,11 @@ export function Tooltip(props: {
}
// Mark taps/pointerdowns (especially touch) globally
window.addEventListener('touchstart', markTap, {passive: true})
window.addEventListener('touchend', markTap, {passive: true})
window.addEventListener('pointerdown', markTap, {passive: true})
// Fallback for some browsers
// Fallbacks for some browsers
window.addEventListener('mousedown', markTap, {passive: true})
window.addEventListener('click', markTap, {passive: true})
g.__tooltipListenersSetup = true
return () => {
// We intentionally do not remove listeners to avoid duplicating across many instances.
@@ -86,7 +106,7 @@ export function Tooltip(props: {
if (next) {
const dt = nowFn() - g.__tooltipLastTapTs
// Ignore open requests shortly after a tap/click (mobile gesture)
if (dt >= 0 && dt < 400) return
if (dt >= 0 && dt < SUPPRESS_MS) return
}
setOpen(next)
},
@@ -104,7 +124,8 @@ export function Tooltip(props: {
const { getReferenceProps, getFloatingProps } = useInteractions([
useHover(context, {
mouseOnly: noTap,
// On touch-capable devices, default to mouse-only hover unless explicitly overridden via noTap=false
mouseOnly: noTap ?? isTouchCapable(),
handleClose: hasSafePolygon ? safePolygon({ buffer: -0.5 }) : null,
}),
useRole(context, { role: 'tooltip' }),