// Hover tooltips — v1 // Icon-first UI: every actionable control carries a [data-tip] label that // surfaces on hover. UXP's CSS engine can't be trusted with // `content: attr(data-tip)` on ::after, so we position a single floating // bubble with plain DOM + getBoundingClientRect (both well supported). (function () { let bubble = null; let timer = null; function ensure() { if (bubble) return bubble; bubble = document.createElement('div'); bubble.className = 'tip-bubble'; document.body.appendChild(bubble); return bubble; } function show(el) { const text = el.getAttribute('data-tip'); if (!text) return; const tip = ensure(); tip.textContent = text; tip.style.display = 'block'; tip.style.opacity = '0'; const r = el.getBoundingClientRect(); const t = tip.getBoundingClientRect(); // position:absolute on a body-level node is offset from the document // origin; add scroll offset (0 in practice, body doesn't scroll) for safety. const sx = window.pageXOffset || document.documentElement.scrollLeft || 0; const sy = window.pageYOffset || document.documentElement.scrollTop || 0; const gap = 7; const pos = el.getAttribute('data-tip-pos') || 'down'; let x, y; if (pos === 'right') { x = r.right + gap; y = r.top + (r.height - t.height) / 2; } else if (pos === 'up') { x = r.left + (r.width - t.width) / 2; y = r.top - t.height - gap; } else if (pos === 'up-left') { x = r.right - t.width; y = r.top - t.height - gap; } else if (pos === 'down-left') { x = r.right - t.width; y = r.bottom + gap; } else { x = r.left + (r.width - t.width) / 2; y = r.bottom + gap; } const vw = window.innerWidth || document.documentElement.clientWidth || 99999; const vh = window.innerHeight || document.documentElement.clientHeight || 99999; x = Math.max(4, Math.min(x, vw - t.width - 4)); y = Math.max(4, Math.min(y, vh - t.height - 4)); tip.style.left = (x + sx) + 'px'; tip.style.top = (y + sy) + 'px'; tip.style.opacity = '1'; } function hide() { clearTimeout(timer); if (bubble) { bubble.style.opacity = '0'; bubble.style.display = 'none'; } } function bind(el) { el.addEventListener('mouseenter', () => { clearTimeout(timer); timer = setTimeout(() => show(el), 240); }); el.addEventListener('mouseleave', hide); el.addEventListener('click', hide); } function init() { document.querySelectorAll('[data-tip]').forEach(bind); } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); else init(); })();