tarinai/js/ui_tooltips.js

127 lines
5.4 KiB
JavaScript

"use strict";
(function (global) {
let activeTooltipElement = null;
let hideTimer = 0;
let longPressTimer = 0;
function ensureTooltipPopover() {
let pop = document.getElementById("toolTipPopover");
if (!pop) {
pop = document.createElement("div");
pop.id = "toolTipPopover";
pop.className = "tool-tip-popover hidden";
document.body.appendChild(pop);
}
return pop;
}
function resolveTip(el) {
if (!el) return "";
if (typeof el.__tarinaiTipProvider === "function") return String(el.__tarinaiTipProvider() || "");
return String(el.dataset?.tip || "");
}
function showTooltip(el, options = {}) {
if (!el || !document.documentElement.contains(el)) return;
const tip = resolveTip(el);
if (!tip) return;
if (hideTimer) { clearTimeout(hideTimer); hideTimer = 0; }
const pop = ensureTooltipPopover();
activeTooltipElement = el;
pop.textContent = tip;
pop.classList.remove("hidden");
const rect = el.getBoundingClientRect();
const popW = pop.offsetWidth || 220;
const popH = pop.offsetHeight || 36;
const preferLeft = rect.left - popW - 12;
const rightSide = rect.right + 12;
const left = preferLeft >= 8 ? preferLeft : rightSide;
pop.style.left = `${clamp(left, 8, Math.max(8, window.innerWidth - popW - 8))}px`;
pop.style.top = `${clamp(rect.top + rect.height / 2 - popH / 2, 8, Math.max(8, window.innerHeight - popH - 8))}px`;
if (options.autoHide || document.body.classList.contains("touch-mobile-ui")) {
hideTimer = setTimeout(hideTooltip, Number(options.delay || 1800));
}
}
function hideTooltip() {
activeTooltipElement = null;
if (hideTimer) { clearTimeout(hideTimer); hideTimer = 0; }
if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = 0; }
ensureTooltipPopover().classList.add("hidden");
}
function refreshTooltip(el = null) {
const target = el || activeTooltipElement;
if (!target) return;
// Do not let unrelated UI refreshes steal an already-visible tooltip.
// Ground UI updates run during stats refresh; without this guard, hovering a
// tool can suddenly turn into the current ground description.
if (el && activeTooltipElement && el !== activeTooltipElement) return;
if (!document.documentElement.contains(target)) { hideTooltip(); return; }
const pop = ensureTooltipPopover();
if (pop.classList.contains("hidden")) return;
showTooltip(target);
}
function bindTooltip(el, tip = "") {
if (!el) return;
if (typeof tip === "function") el.__tarinaiTipProvider = tip;
else if (tip) el.dataset.tip = String(tip);
// Native browser title tooltips can linger and overlap the custom popover.
el.removeAttribute?.("title");
if (el.dataset.tipBound === "1") return;
el.dataset.tipBound = "1";
el.addEventListener("mouseenter", () => { if (!document.body.classList.contains("touch-mobile-ui")) showTooltip(el); });
el.addEventListener("focus", () => { if (!document.body.classList.contains("touch-mobile-ui")) showTooltip(el); });
el.addEventListener("mouseleave", hideTooltip);
el.addEventListener("blur", hideTooltip);
el.addEventListener("pointerdown", () => {
if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = 0; }
if (activeTooltipElement === el || document.body.classList.contains("touch-mobile-ui")) hideTooltip();
});
el.addEventListener("touchstart", () => {
if (!document.body.classList.contains("touch-mobile-ui")) return;
if (longPressTimer) clearTimeout(longPressTimer);
longPressTimer = setTimeout(() => {
longPressTimer = 0;
el.dataset.suppressNextClick = "1";
setTimeout(() => { if (el.dataset.suppressNextClick === "1") el.dataset.suppressNextClick = ""; }, 900);
showTooltip(el, { autoHide: true, delay: 2600 });
}, 520);
}, { passive: true });
el.addEventListener("touchmove", () => { if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = 0; } }, { passive: true });
el.addEventListener("touchend", () => { if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = 0; } }, { passive: true });
el.addEventListener("touchcancel", () => { if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = 0; } }, { passive: true });
}
function applyToolPaletteTooltips() {
const tips = global.TEXT_CATALOG?.toolTips || {};
const root = global.ui?.toolPalette || document;
for (const btn of root.querySelectorAll?.(".tool[data-tool]") || []) {
const tip = tips[btn.dataset.tool];
if (tip) bindTooltip(btn, tip);
}
}
document.addEventListener("pointerdown", (e) => {
if (!activeTooltipElement) return;
if (activeTooltipElement.contains?.(e.target)) return;
hideTooltip();
}, { capture: true, passive: true });
document.addEventListener("touchstart", (e) => {
if (!activeTooltipElement) return;
if (activeTooltipElement.contains?.(e.target)) return;
hideTooltip();
}, { capture: true, passive: true });
window.addEventListener("scroll", hideTooltip, { passive: true });
window.addEventListener("resize", hideTooltip, { passive: true });
document.addEventListener("visibilitychange", () => { if (document.hidden) hideTooltip(); });
global.TarinaiTooltips = Object.freeze({
bind: bindTooltip,
hide: hideTooltip,
refresh: refreshTooltip,
applyToolPalette: applyToolPaletteTooltips,
});
})(window);