Files
dickloads/src/lib/actions/tooltip.ts
Falkan 6a9ecf9d46 fix: custom tooltip (no wiggle required), add ⓘ indicator for described units
- Replace native title-based tooltip with custom positioned div
  Native title only renders on mousemove; custom div fires on mouseenter
  after 500ms delay as intended
- Tooltip styled dark with fade-in; Dan Mode gets yellow-on-black with magenta glow
- Units with descriptions show a small superscript ⓘ on the result label
2026-03-18 19:33:14 -04:00

84 lines
2.4 KiB
TypeScript

/**
* 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';
// Shared tooltip element — one per page, repositioned as needed.
let tooltipEl: HTMLDivElement | null = null;
function getTooltipEl(): HTMLDivElement {
if (!tooltipEl) {
tooltipEl = document.createElement('div');
tooltipEl.className = TOOLTIP_CLASS;
document.body.appendChild(tooltipEl);
}
return tooltipEl;
}
export function tooltip(node: HTMLElement, text: string) {
let timer: ReturnType<typeof setTimeout> | null = null;
let currentText = text;
function show(e: MouseEvent) {
if (!currentText) return;
const el = getTooltipEl();
el.textContent = currentText;
// Position near cursor, clamped to viewport
const x = Math.min(e.clientX + 12, window.innerWidth - el.offsetWidth - 8);
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) {
if (!currentText) return;
timer = setTimeout(() => show(e), 500);
}
function move(e: MouseEvent) {
// Keep tooltip near cursor while hovering
if (!tooltipEl?.classList.contains('hu-tooltip--visible')) return;
const el = getTooltipEl();
const x = Math.min(e.clientX + 12, window.innerWidth - el.offsetWidth - 8);
const y = e.clientY + 20;
el.style.left = `${x}px`;
el.style.top = `${y}px`;
}
function leave() {
if (timer !== null) {
clearTimeout(timer);
timer = null;
}
tooltipEl?.classList.remove('hu-tooltip--visible');
}
node.addEventListener('mouseenter', enter);
node.addEventListener('mousemove', move);
node.addEventListener('mouseleave', leave);
return {
update(newText: string) {
currentText = newText;
if (tooltipEl?.classList.contains('hu-tooltip--visible')) {
tooltipEl.textContent = newText;
}
},
destroy() {
if (timer !== null) clearTimeout(timer);
node.removeEventListener('mouseenter', enter);
node.removeEventListener('mousemove', move);
node.removeEventListener('mouseleave', leave);
tooltipEl?.classList.remove('hu-tooltip--visible');
}
};
}