Improved tooltip display behavior; disabled for touch, less eager to activate on mouse, closer to cursor.

This commit is contained in:
Leo Vasanko
2026-01-22 18:08:50 +00:00
parent 667f44f1fa
commit fa82fee53e
+62 -16
View File
@@ -10,6 +10,8 @@
// Global activation state - shared across all instances
let globalActive = false
let globalDeactivateTimer: ReturnType<typeof setTimeout> | null = null
// Track if we've seen real mouse movement (not touch-simulated)
let hasRealMouse = false
</script>
<script setup lang="ts">
@@ -23,44 +25,88 @@ const props = defineProps<{
const visible = ref(false)
const mouseX = ref(0)
const mouseY = ref(0)
let hoverTimer: ReturnType<typeof setTimeout> | null = null
let settleTimer: ReturnType<typeof setTimeout> | null = null
let lastMoveX = 0
let lastMoveY = 0
// Movement threshold (pixels) - cursor must settle within this radius
const SETTLE_THRESHOLD = 8
const tooltipStyle = computed(() => ({
left: `${mouseX.value + 12}px`,
top: `${mouseY.value + 12}px`,
left: `${mouseX.value}px`,
top: `${mouseY.value}px`,
}))
const startHover = (e: MouseEvent) => {
mouseX.value = e.clientX
mouseY.value = e.clientY
// Clear any pending deactivation
// Check if the device likely has a real mouse (fine pointer)
const hasFinePointer = () => window.matchMedia('(pointer: fine)').matches
const showTooltip = () => {
visible.value = true
globalActive = true
}
const scheduleTooltip = () => {
if (settleTimer) clearTimeout(settleTimer)
if (globalDeactivateTimer) {
clearTimeout(globalDeactivateTimer)
globalDeactivateTimer = null
}
const delay = globalActive ? 0 : (props.delay ?? 800)
hoverTimer = setTimeout(() => {
visible.value = true
globalActive = true
}, delay)
const delay = globalActive ? 0 : (props.delay ?? 900)
settleTimer = setTimeout(showTooltip, delay)
}
const startHover = (e: MouseEvent) => {
// Ignore touch events (no fine pointer and no confirmed real mouse)
if (!hasFinePointer() && !hasRealMouse) return
mouseX.value = e.clientX
mouseY.value = e.clientY
lastMoveX = e.clientX
lastMoveY = e.clientY
}
const updatePosition = (e: MouseEvent) => {
// Detect real mouse via movement (touch events don't generate continuous mousemove)
if (e.movementX !== 0 || e.movementY !== 0) hasRealMouse = true
if (!hasFinePointer() && !hasRealMouse) return
mouseX.value = e.clientX
mouseY.value = e.clientY
// If tooltip is already visible, just update position
if (visible.value) return
const dx = e.clientX - lastMoveX
const dy = e.clientY - lastMoveY
const distance = Math.sqrt(dx * dx + dy * dy)
// If cursor moved beyond threshold, reset settle timer
if (distance > SETTLE_THRESHOLD) {
lastMoveX = e.clientX
lastMoveY = e.clientY
if (settleTimer) {
clearTimeout(settleTimer)
settleTimer = null
}
}
// Schedule tooltip when cursor settles
if (!settleTimer) {
scheduleTooltip()
}
}
const endHover = () => {
if (hoverTimer) {
clearTimeout(hoverTimer)
hoverTimer = null
if (settleTimer) {
clearTimeout(settleTimer)
settleTimer = null
}
visible.value = false
// Deactivate global state after a short delay if no new tooltip started
if (globalDeactivateTimer) clearTimeout(globalDeactivateTimer)
globalDeactivateTimer = setTimeout(() => {
globalActive = false
}, 500)
}, 400)
}
defineExpose({