fix: tooltip tracks current mouse position, opens at cursor
This commit is contained in:
@@ -1,15 +1,6 @@
|
|||||||
/**
|
|
||||||
* Svelte action: show a custom tooltip after a 500ms hover delay.
|
|
||||||
* Uses a positioned div instead of the native `title` attribute —
|
|
||||||
* native tooltips only appear on mousemove, not on mouseenter, which
|
|
||||||
* causes a "hover then wiggle" requirement that feels broken.
|
|
||||||
*
|
|
||||||
* If text is empty, does nothing.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const TOOLTIP_CLASS = 'hu-tooltip';
|
const TOOLTIP_CLASS = 'hu-tooltip';
|
||||||
|
const TOOLTIP_VISIBLE_CLASS = 'hu-tooltip--visible';
|
||||||
|
|
||||||
// Shared tooltip element — one per page, repositioned as needed.
|
|
||||||
let tooltipEl: HTMLDivElement | null = null;
|
let tooltipEl: HTMLDivElement | null = null;
|
||||||
|
|
||||||
function getTooltipEl(): HTMLDivElement {
|
function getTooltipEl(): HTMLDivElement {
|
||||||
@@ -24,41 +15,36 @@ function getTooltipEl(): HTMLDivElement {
|
|||||||
export function tooltip(node: HTMLElement, text: string) {
|
export function tooltip(node: HTMLElement, text: string) {
|
||||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||||
let currentText = text;
|
let currentText = text;
|
||||||
|
let mouseX = 0;
|
||||||
|
let mouseY = 0;
|
||||||
|
|
||||||
function show(e: MouseEvent) {
|
function show() {
|
||||||
if (!currentText) return;
|
if (!currentText) return;
|
||||||
const el = getTooltipEl();
|
const el = getTooltipEl();
|
||||||
el.textContent = currentText;
|
el.textContent = currentText;
|
||||||
|
el.style.left = `${mouseX + 12}px`;
|
||||||
// Position near cursor, clamped to viewport
|
el.style.top = `${mouseY + 16}px`;
|
||||||
const x = Math.min(e.clientX + 12, window.innerWidth - el.offsetWidth - 8);
|
el.classList.add(TOOLTIP_VISIBLE_CLASS);
|
||||||
const y = e.clientY + 20;
|
|
||||||
el.style.left = `${x}px`;
|
|
||||||
el.style.top = `${y}px`;
|
|
||||||
el.classList.add('hu-tooltip--visible');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function enter(e: MouseEvent) {
|
function enter(e: MouseEvent) {
|
||||||
if (!currentText) return;
|
if (!currentText) return;
|
||||||
timer = setTimeout(() => show(e), 500);
|
mouseX = e.clientX;
|
||||||
|
mouseY = e.clientY;
|
||||||
|
timer = setTimeout(show, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
function move(e: MouseEvent) {
|
function move(e: MouseEvent) {
|
||||||
// Keep tooltip near cursor while hovering
|
mouseX = e.clientX;
|
||||||
if (!tooltipEl?.classList.contains('hu-tooltip--visible')) return;
|
mouseY = e.clientY;
|
||||||
const el = getTooltipEl();
|
if (!tooltipEl?.classList.contains(TOOLTIP_VISIBLE_CLASS)) return;
|
||||||
const x = Math.min(e.clientX + 12, window.innerWidth - el.offsetWidth - 8);
|
tooltipEl.style.left = `${mouseX + 12}px`;
|
||||||
const y = e.clientY + 20;
|
tooltipEl.style.top = `${mouseY + 16}px`;
|
||||||
el.style.left = `${x}px`;
|
|
||||||
el.style.top = `${y}px`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function leave() {
|
function leave() {
|
||||||
if (timer !== null) {
|
if (timer !== null) { clearTimeout(timer); timer = null; }
|
||||||
clearTimeout(timer);
|
tooltipEl?.classList.remove(TOOLTIP_VISIBLE_CLASS);
|
||||||
timer = null;
|
|
||||||
}
|
|
||||||
tooltipEl?.classList.remove('hu-tooltip--visible');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
node.addEventListener('mouseenter', enter);
|
node.addEventListener('mouseenter', enter);
|
||||||
@@ -68,7 +54,7 @@ export function tooltip(node: HTMLElement, text: string) {
|
|||||||
return {
|
return {
|
||||||
update(newText: string) {
|
update(newText: string) {
|
||||||
currentText = newText;
|
currentText = newText;
|
||||||
if (tooltipEl?.classList.contains('hu-tooltip--visible')) {
|
if (tooltipEl?.classList.contains(TOOLTIP_VISIBLE_CLASS)) {
|
||||||
tooltipEl.textContent = newText;
|
tooltipEl.textContent = newText;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -77,7 +63,7 @@ export function tooltip(node: HTMLElement, text: string) {
|
|||||||
node.removeEventListener('mouseenter', enter);
|
node.removeEventListener('mouseenter', enter);
|
||||||
node.removeEventListener('mousemove', move);
|
node.removeEventListener('mousemove', move);
|
||||||
node.removeEventListener('mouseleave', leave);
|
node.removeEventListener('mouseleave', leave);
|
||||||
tooltipEl?.classList.remove('hu-tooltip--visible');
|
tooltipEl?.classList.remove(TOOLTIP_VISIBLE_CLASS);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user