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 // Global activation state - shared across all instances
let globalActive = false let globalActive = false
let globalDeactivateTimer: ReturnType<typeof setTimeout> | null = null let globalDeactivateTimer: ReturnType<typeof setTimeout> | null = null
// Track if we've seen real mouse movement (not touch-simulated)
let hasRealMouse = false
</script> </script>
<script setup lang="ts"> <script setup lang="ts">
@@ -23,44 +25,88 @@ const props = defineProps<{
const visible = ref(false) const visible = ref(false)
const mouseX = ref(0) const mouseX = ref(0)
const mouseY = 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(() => ({ const tooltipStyle = computed(() => ({
left: `${mouseX.value + 12}px`, left: `${mouseX.value}px`,
top: `${mouseY.value + 12}px`, top: `${mouseY.value}px`,
})) }))
const startHover = (e: MouseEvent) => { // Check if the device likely has a real mouse (fine pointer)
mouseX.value = e.clientX const hasFinePointer = () => window.matchMedia('(pointer: fine)').matches
mouseY.value = e.clientY
// Clear any pending deactivation const showTooltip = () => {
visible.value = true
globalActive = true
}
const scheduleTooltip = () => {
if (settleTimer) clearTimeout(settleTimer)
if (globalDeactivateTimer) { if (globalDeactivateTimer) {
clearTimeout(globalDeactivateTimer) clearTimeout(globalDeactivateTimer)
globalDeactivateTimer = null globalDeactivateTimer = null
} }
const delay = globalActive ? 0 : (props.delay ?? 800) const delay = globalActive ? 0 : (props.delay ?? 900)
hoverTimer = setTimeout(() => { settleTimer = setTimeout(showTooltip, delay)
visible.value = true }
globalActive = true
}, 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) => { 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 mouseX.value = e.clientX
mouseY.value = e.clientY 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 = () => { const endHover = () => {
if (hoverTimer) { if (settleTimer) {
clearTimeout(hoverTimer) clearTimeout(settleTimer)
hoverTimer = null settleTimer = null
} }
visible.value = false visible.value = false
// Deactivate global state after a short delay if no new tooltip started // Deactivate global state after a short delay if no new tooltip started
if (globalDeactivateTimer) clearTimeout(globalDeactivateTimer) if (globalDeactivateTimer) clearTimeout(globalDeactivateTimer)
globalDeactivateTimer = setTimeout(() => { globalDeactivateTimer = setTimeout(() => {
globalActive = false globalActive = false
}, 500) }, 400)
} }
defineExpose({ defineExpose({