global tooltip system, migrate all title attrs to data-tip/data-help
New tooltip.js module with styled tooltips (data-tip) and multi-line inline help (data-help). Migrates all ~70 native title attributes across index.php, JS modules, and all plugins. Adds inline help on 9 strategic UI elements (split, superview, zen, extract, assets, etc). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
527305355c
commit
f465306c1d
21 files changed
+229
-109
No files matched your search
@@ -0,0 +1,91 @@
|
||||
// ── Global tooltip system ──
|
||||
// Replaces native title="" with styled tooltips. Supports:
|
||||
// data-tip="short text" — single-line tooltip (replaces title)
|
||||
// data-help="line1\nline2" — multi-line inline help (shown on hover)
|
||||
|
||||
const SHOW_DELAY = 400;
|
||||
const HIDE_DELAY = 100;
|
||||
const OFFSET = 8;
|
||||
|
||||
let tipEl = null;
|
||||
let showTimer = null;
|
||||
let hideTimer = null;
|
||||
let currentTarget = null;
|
||||
|
||||
function create() {
|
||||
if (tipEl) return;
|
||||
tipEl = document.createElement('div');
|
||||
tipEl.id = 'tooltip';
|
||||
tipEl.setAttribute('role', 'tooltip');
|
||||
document.body.appendChild(tipEl);
|
||||
}
|
||||
|
||||
function show(target) {
|
||||
clearTimeout(hideTimer);
|
||||
if (currentTarget === target) return;
|
||||
currentTarget = target;
|
||||
create();
|
||||
|
||||
const tip = target.dataset.tip || '';
|
||||
const help = target.dataset.help || '';
|
||||
if (!tip && !help) { hide(); return; }
|
||||
|
||||
if (help) {
|
||||
tipEl.classList.add('tooltip-help');
|
||||
tipEl.innerHTML = help.replace(/\\n/g, '<br>');
|
||||
} else {
|
||||
tipEl.classList.remove('tooltip-help');
|
||||
tipEl.textContent = tip;
|
||||
}
|
||||
|
||||
tipEl.style.visibility = 'hidden';
|
||||
tipEl.style.display = 'block';
|
||||
|
||||
const rect = target.getBoundingClientRect();
|
||||
const tipRect = tipEl.getBoundingClientRect();
|
||||
|
||||
let top, left;
|
||||
const spaceBelow = window.innerHeight - rect.bottom;
|
||||
const spaceAbove = rect.top;
|
||||
|
||||
if (spaceBelow >= tipRect.height + OFFSET || spaceBelow >= spaceAbove) {
|
||||
top = rect.bottom + OFFSET;
|
||||
tipEl.classList.remove('tooltip-above');
|
||||
} else {
|
||||
top = rect.top - tipRect.height - OFFSET;
|
||||
tipEl.classList.add('tooltip-above');
|
||||
}
|
||||
|
||||
left = rect.left + rect.width / 2 - tipRect.width / 2;
|
||||
left = Math.max(8, Math.min(left, window.innerWidth - tipRect.width - 8));
|
||||
top = Math.max(4, top);
|
||||
|
||||
tipEl.style.top = top + 'px';
|
||||
tipEl.style.left = left + 'px';
|
||||
tipEl.style.visibility = '';
|
||||
}
|
||||
|
||||
function hide() {
|
||||
currentTarget = null;
|
||||
if (tipEl) tipEl.style.display = 'none';
|
||||
}
|
||||
|
||||
function onEnter(e) {
|
||||
const target = e.target.closest('[data-tip],[data-help]');
|
||||
if (!target) return;
|
||||
clearTimeout(hideTimer);
|
||||
showTimer = setTimeout(() => show(target), SHOW_DELAY);
|
||||
}
|
||||
|
||||
function onLeave(e) {
|
||||
const target = e.target.closest('[data-tip],[data-help]');
|
||||
if (!target) return;
|
||||
clearTimeout(showTimer);
|
||||
hideTimer = setTimeout(hide, HIDE_DELAY);
|
||||
}
|
||||
|
||||
document.addEventListener('pointerenter', onEnter, true);
|
||||
document.addEventListener('pointerleave', onLeave, true);
|
||||
document.addEventListener('pointerdown', () => { clearTimeout(showTimer); hide(); }, true);
|
||||
document.addEventListener('keydown', () => { clearTimeout(showTimer); hide(); }, true);
|
||||
document.addEventListener('scroll', () => { clearTimeout(showTimer); hide(); }, true);
|
||||
Reference in new issue
Block a user