dragonflight/services/premiere-plugin-uxp/src/tooltip.js
zgaetano 39ef551489 feat(uxp): ship the icon-rail panel redesign as v2.2.2 (recover from redesign branch)
The redesigned UXP panel (left icon rail, compact list-view toggle, hover
tooltips, single Export menu) was committed only to redesign/panel-icon-rail
and never merged, so main + the website kept serving the old blocky-button
build under the same version number (2.2.2). That branch had diverged off an
old main and is missing recent worker/HLS/NVENC/import work, so it can't be
merged wholesale — cherry-pick just the plugin instead.

- services/premiere-plugin-uxp: replace source with the redesigned panel
  (adds src/tooltip.js; reworks index.html + styles.css + src/*). Verified
  byte-identical to the build installed on BMG-PC-Edit.
- web-ui/public/downloads/dragonflight-mam-2.2.2.ccx: swap the served
  artifact to the redesigned 34708-byte build (download link unchanged).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-29 20:45:29 -04:00

78 lines
2.6 KiB
JavaScript

// 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();
})();