mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-02-19 15:27:16 -05:00
Fix tooltip (3)
This commit is contained in:
@@ -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' }),
|
||||
|
||||
Reference in New Issue
Block a user