import { debounce } from "./dom-utils.js"; import { emitEvent, listenTo } from "./events.js"; import { registerLoadCallback } from "./main.js"; import { makeAtlasImage } from "./atlas.js"; const tooltips = {}; let tooltipsCounter = 0; let lastCoordinates = null; let suppressTooltips = false; let activeTooltipTarget = null; registerLoadCallback(() => { document.querySelectorAll("[data-retain-tooltip-hover]").forEach(elem => elem.addEventListener("mousemove", e => { if (lastCoordinates !== null) { e.stopPropagation(); lastCoordinates = { x: e.pageX, y: e.pageY }; emitEvent("tooltips:show"); } })); window.addEventListener("mousemove", () => { if (lastCoordinates !== null) { emitEvent("tooltips:hide"); } lastCoordinates = null; }); window.addEventListener("mousemove", debounce(e => { lastCoordinates = { x: e.pageX, y: e.pageY }; emitEvent("tooltips:show"); }, 500)); window.addEventListener("wheel", () => { if (lastCoordinates !== null) { setTimeout(() => emitEvent("tooltips:show"), 200); } }); listenTo("tooltips:show", showTooltip); listenTo("tooltips:hide", hideTooltip); listenTo("dev:suppress-tooltips", () => { suppressTooltips = !suppressTooltips; console.log("suppressing tooltips", suppressTooltips); }); }); export function createTooltip(data) { const id = tooltipsCounter; tooltips[id] = data; tooltipsCounter++; return id; } export function getTooltip(id) { if (typeof id === "object") { // assume DOM element if (id.dataset && id.dataset.tooltip) { return getTooltip(id.dataset.tooltip); } return null; } return tooltips[id]; } export function deleteTooltip(id) { delete tooltips[id]; } export function setTooltip(element, data) { if (data.length === 0) return; if (element.dataset.tooltip) { tooltips[Number(element.dataset.tooltip)] = data; return; } element.dataset.tooltip = createTooltip(data).toString(10); } export function positionAsTooltip(mainElement, tooltipElement, { horizontal = "left", vertical = "bottom" } = {}) { const targetRect = mainElement.getBoundingClientRect(); const domRect = tooltipElement.getBoundingClientRect(); const windowWidth = window.innerWidth, windowHeight = window.innerHeight; tooltipElement.style.left = `${Math.min(targetRect[horizontal], windowWidth - domRect.width)}px`; tooltipElement.style.top = `${Math.min(targetRect[vertical], windowHeight - domRect.height)}px`; } function showTooltip() { if (suppressTooltips) return; if (!lastCoordinates) return; if (activeTooltipTarget) hideTooltip(); let target = document.elementFromPoint(lastCoordinates.x, lastCoordinates.y); while (target && !target.dataset.tooltip) { target = target.parentElement; } if (!target) return; const dom = createTooltipDom(target.dataset.tooltip); if (!dom) return; const targetRect = target.getBoundingClientRect(); dom.style.left = `${targetRect.left}px`; dom.style.top = `${targetRect.bottom}px`; target.appendChild(dom); positionAsTooltip(target, dom); activeTooltipTarget = target; } function hideTooltip() { if (suppressTooltips) return; if (!activeTooltipTarget) return; const tooltip = activeTooltipTarget.querySelector(".tooltip"); if (!tooltip) return; activeTooltipTarget.removeChild(tooltip); activeTooltipTarget = null; } function createTooltipDom(id) { const data = tooltips[id]; if (!Array.isArray(data)) { console.warn("tooltip %s is not an array: %o", id, data); return null; } const div = document.createElement("div"); div.classList.add("tooltip"); for (const row of data) { const rowElem = document.createElement("div"); if (typeof row === "string") { rowElem.innerText = row; } else { if (!row.type) { throw row; } // TODO: support richer tooltips switch (row.type) { case "italics": rowElem.innerText = row.text; rowElem.classList.add("italics"); break; case "item": if (row.prefix) { rowElem.appendChild(document.createTextNode(row.prefix)); } rowElem.appendChild(makeAtlasImage(row.atlasIndex)); rowElem.appendChild(document.createTextNode(row.name)); rowElem.classList.add("tooltip--item"); break; case "meta": break; default: console.warn("unknown tooltip type", row); break; } } div.appendChild(rowElem); } return div; } export function tooltipDataToPlain(data) { if (!Array.isArray(data)) { console.warn("tooltip data is not an array: %o", data); return []; } return data.flatMap(row => { if (typeof row === "string") return row; if (!row.type) return []; if (row.text) return row.text; if (row.type === "item") return row.name; if (row.type === "meta") return []; return []; }) }