SOURCE / PINNED RELEASE
Made of little things.
City Breaker
- Release
- 94d2b8664b10…
- Author-recorded commit
- df23103cbcc1…
- License
- LICENSE
- Author’s source reference
- nostr://npub1sj2vketepqw9v6fmge5fdzuj7atj9sv6qv2jss05n2z6rl64lcfqumdsnt/wss%3A%2F%2Fgit.napplet.soy%2F/n-cf8b858653e
Archive hash verified: eeeed725668c1ece…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
// Hover help for every control: any element with data-tip shows a description.
// Touch: press and hold for half a second. Canvases can show their own tips.
let tip: HTMLDivElement | null = null;
let current: HTMLElement | null = null;
let pressTimer = 0;
function el(): HTMLDivElement {
if (!tip) {
tip = document.createElement('div');
tip.className = 'tip';
tip.setAttribute('role', 'tooltip');
tip.hidden = true;
document.body.append(tip);
}
return tip;
}
/** Show a tooltip near a point (clientX/Y). `html` may contain simple markup. */
export function showTipAt(x: number, y: number, html: string): void {
const t = el();
t.innerHTML = html;
t.hidden = false;
const r = t.getBoundingClientRect();
const W = window.innerWidth;
const H = window.innerHeight;
let left = x + 14;
let top = y + 16;
if (left + r.width > W - 6) left = Math.max(6, x - r.width - 10);
if (top + r.height > H - 6) top = Math.max(6, y - r.height - 12);
t.style.left = `${left}px`;
t.style.top = `${top}px`;
}
export function hideTip(): void {
if (tip) tip.hidden = true;
current = null;
}
function tipFor(target: EventTarget | null): HTMLElement | null {
return target instanceof Element ? target.closest<HTMLElement>('[data-tip]') : null;
}
function show(elm: HTMLElement): void {
current = elm;
const r = elm.getBoundingClientRect();
const title = elm.dataset.tipTitle;
const body = elm.dataset.tip ?? '';
showTipAt(r.left + Math.min(r.width, 40), r.bottom - 6, `${title ? `<b>${title}</b>` : ''}${body}`);
}
export function installTooltips(): void {
document.addEventListener('pointerover', (e) => {
if (e.pointerType === 'touch') return;
const t = tipFor(e.target);
if (t && t !== current) show(t);
if (!t && current) hideTip();
});
document.addEventListener('pointerout', (e) => {
if (e.pointerType === 'touch') return;
const to = tipFor(e.relatedTarget);
if (!to) hideTip();
});
document.addEventListener('focusin', (e) => {
const t = tipFor(e.target);
if (t && (e.target as HTMLElement).matches(':focus-visible')) show(t);
});
document.addEventListener('focusout', hideTip);
// Long-press on touch.
document.addEventListener('pointerdown', (e) => {
hideTip();
if (e.pointerType !== 'touch') return;
const t = tipFor(e.target);
if (!t) return;
window.clearTimeout(pressTimer);
pressTimer = window.setTimeout(() => show(t), 500);
});
const cancel = () => window.clearTimeout(pressTimer);
document.addEventListener('pointerup', cancel);
document.addEventListener('pointercancel', cancel);
document.addEventListener('scroll', hideTip, true);
window.addEventListener('blur', hideTip);
}
