127 lines
5.4 KiB
JavaScript
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);
|