feat: tooltip viewport clamping — shifts minimally to keep bounding box in view

This commit is contained in:
Falkan
2026-03-19 00:27:01 -04:00
parent d1500c6ac3
commit cd0482bade

View File

@@ -12,6 +12,26 @@ function getTooltipEl(): HTMLDivElement {
return tooltipEl; return tooltipEl;
} }
function clamp(val: number, min: number, max: number) {
return Math.min(Math.max(val, min), max);
}
function positionTooltip(el: HTMLDivElement, cx: number, cy: number) {
const margin = 6;
// Start with ideal position: just right of and below cursor
let x = cx + 12;
let y = cy + 16;
// Measure after content is set (element is in DOM, opacity-hidden is fine)
const w = el.offsetWidth;
const h = el.offsetHeight;
// Shift left/right only as much as needed
x = clamp(x, margin, window.innerWidth - w - margin);
// Shift up/down only as much as needed
y = clamp(y, margin, window.innerHeight - h - margin);
el.style.left = `${x}px`;
el.style.top = `${y}px`;
}
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;
@@ -22,8 +42,8 @@ export function tooltip(node: HTMLElement, text: string) {
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 before making visible — offsetWidth/Height valid since el is in DOM
el.style.top = `${mouseY + 16}px`; positionTooltip(el, mouseX, mouseY);
el.classList.add(TOOLTIP_VISIBLE_CLASS); el.classList.add(TOOLTIP_VISIBLE_CLASS);
} }
@@ -38,8 +58,7 @@ export function tooltip(node: HTMLElement, text: string) {
mouseX = e.clientX; mouseX = e.clientX;
mouseY = e.clientY; mouseY = e.clientY;
if (!tooltipEl?.classList.contains(TOOLTIP_VISIBLE_CLASS)) return; if (!tooltipEl?.classList.contains(TOOLTIP_VISIBLE_CLASS)) return;
tooltipEl.style.left = `${mouseX + 12}px`; positionTooltip(tooltipEl, mouseX, mouseY);
tooltipEl.style.top = `${mouseY + 16}px`;
} }
function leave() { function leave() {
@@ -56,6 +75,7 @@ export function tooltip(node: HTMLElement, text: string) {
currentText = newText; currentText = newText;
if (tooltipEl?.classList.contains(TOOLTIP_VISIBLE_CLASS)) { if (tooltipEl?.classList.contains(TOOLTIP_VISIBLE_CLASS)) {
tooltipEl.textContent = newText; tooltipEl.textContent = newText;
positionTooltip(tooltipEl, mouseX, mouseY);
} }
}, },
destroy() { destroy() {