<script src="https://unpkg.com/@floating-ui/core@1.7.3/dist/floating-ui.core.umd.min.js"></script>
<script src="https://unpkg.com/@floating-ui/dom@1.7.4/dist/floating-ui.dom.umd.min.js"></script>
<script>
(() => {
if (window.FloatingUIDOM == null) {
return;
}
const { autoUpdate, computePosition, flip, offset, shift, size } =
window.FloatingUIDOM;
const tooltipPlacements = new Set(["top", "right", "bottom", "left"]);
const tooltipId = `codex-visualization-tooltip-${
window.crypto?.randomUUID?.() ?? Date.now().toString(36)
}`;
const tooltipGap = 5;
const hoverDelay = 700;
const skipDelay = 300;
let activeTrigger = null;
let pendingTrigger = null;
let tooltip = null;
let openTimeout = null;
let autoUpdateCleanup = null;
let positionRequest = 0;
let skipDelayUntil = 0;
const getTrigger = (target) =>
target?.nodeType === Node.ELEMENT_NODE
? target.closest("[data-tooltip]")
: null;
const containsTarget = (trigger, target) =>
target?.nodeType != null && trigger.contains(target);
const getContent = (trigger) =>
trigger.getAttribute("data-tooltip")?.trim() ?? "";
const getPlacement = (trigger) => {
const placement = trigger.getAttribute("data-tooltip-placement");
return tooltipPlacements.has(placement) ? placement : "top";
};
const getTooltip = () => {
if (tooltip == null) {
tooltip = document.createElement("div");
tooltip.id = tooltipId;
tooltip.className = "tooltip";
tooltip.setAttribute("role", "tooltip");
}
return tooltip;
};
const setDescribedBy = (trigger, described) => {
const ids = new Set(
(trigger.getAttribute("aria-describedby") ?? "")
.split(/\s+/)
.filter(Boolean),
);
if (described) {
ids.add(tooltipId);
} else {
ids.delete(tooltipId);
}
if (ids.size === 0) {
trigger.removeAttribute("aria-describedby");
} else {
trigger.setAttribute("aria-describedby", [...ids].join(" "));
}
};
const clearOpenTimeout = () => {
if (openTimeout != null) {
window.clearTimeout(openTimeout);
openTimeout = null;
}
pendingTrigger = null;
};
const closeTooltip = (activateSkipDelay = true) => {
clearOpenTimeout();
positionRequest += 1;
autoUpdateCleanup?.();
autoUpdateCleanup = null;
if (activeTrigger == null) {
return;
}
setDescribedBy(activeTrigger, false);
activeTrigger = null;
tooltip?.remove();
if (activateSkipDelay) {
skipDelayUntil = Date.now() + skipDelay;
}
};
const updatePosition = (trigger, floating) => {
const request = ++positionRequest;
void computePosition(trigger, floating, {
middleware: [
offset(tooltipGap),
flip({ padding: tooltipGap }),
shift({ padding: tooltipGap }),
size({
padding: tooltipGap,
apply({ availableHeight, availableWidth, elements }) {
elements.floating.style.setProperty(
"--tooltip-available-width",
`${Math.max(0, availableWidth)}px`,
);
elements.floating.style.setProperty(
"--tooltip-available-height",
`${Math.max(0, availableHeight)}px`,
);
},
}),
],
placement: getPlacement(trigger),
strategy: "fixed",
})
.then(({ x, y }) => {
if (activeTrigger !== trigger || request !== positionRequest) {
return;
}
const scale = window.devicePixelRatio || 1;
floating.style.transform = `translate(${Math.round(x * scale) / scale}px, ${Math.round(y * scale) / scale}px)`;
floating.style.visibility = "visible";
})
.catch(() => {
if (activeTrigger === trigger && request === positionRequest) {
closeTooltip(false);
}
});
};
const openTooltip = (trigger) => {
const content = getContent(trigger);
if (!trigger.isConnected || content.length === 0) {
return;
}
clearOpenTimeout();
if (activeTrigger === trigger) {
return;
}
closeTooltip();
const floating = getTooltip();
floating.textContent = content;
floating.style.visibility = "hidden";
floating.style.transform = "translate(0, 0)";
document.body.appendChild(floating);
activeTrigger = trigger;
setDescribedBy(trigger, true);
autoUpdateCleanup = autoUpdate(trigger, floating, () => {
updatePosition(trigger, floating);
});
};
const requestOpen = (trigger, immediate = false) => {
if (
getContent(trigger).length === 0 ||
activeTrigger === trigger ||
pendingTrigger === trigger
) {
return;
}
clearOpenTimeout();
pendingTrigger = trigger;
const delay =
immediate || activeTrigger != null || Date.now() < skipDelayUntil
? 0
: hoverDelay;
if (delay === 0) {
openTooltip(trigger);
return;
}
openTimeout = window.setTimeout(() => {
openTimeout = null;
pendingTrigger = null;
openTooltip(trigger);
}, delay);
};
const handlePointerOpen = (event) => {
if (event.pointerType === "touch") {
return;
}
const trigger = getTrigger(event.target);
if (trigger == null || containsTarget(trigger, event.relatedTarget)) {
return;
}
requestOpen(trigger);
};
const handleLeave = (event) => {
const trigger = getTrigger(event.target);
if (trigger == null || containsTarget(trigger, event.relatedTarget)) {
return;
}
if (activeTrigger === trigger || pendingTrigger === trigger) {
closeTooltip();
}
};
const handleFocusIn = (event) => {
const trigger = getTrigger(event.target);
if (trigger?.matches(":focus-visible")) {
requestOpen(trigger, true);
}
};
const handleKeyDown = (event) => {
if (event.key === "Escape") {
closeTooltip();
}
};
const handleContextMenu = (event) => {
const trigger = getTrigger(event.target);
if (activeTrigger === trigger || pendingTrigger === trigger) {
closeTooltip();
}
};
const destroy = () => {
closeTooltip(false);
document.removeEventListener("pointerover", handlePointerOpen);
document.removeEventListener("pointerout", handleLeave);
document.removeEventListener("focusin", handleFocusIn);
document.removeEventListener("focusout", handleLeave);
document.removeEventListener("keydown", handleKeyDown);
document.removeEventListener("contextmenu", handleContextMenu);
tooltip = null;
};
document.addEventListener("pointerover", handlePointerOpen);
document.addEventListener("pointerout", handleLeave);
document.addEventListener("focusin", handleFocusIn);
document.addEventListener("focusout", handleLeave);
document.addEventListener("keydown", handleKeyDown);
document.addEventListener("contextmenu", handleContextMenu);
window.addEventListener("pagehide", destroy, { once: true });
})();
</script>
<script id="codex-visualization-lucide" async src="https://unpkg.com/lucide@1.17.0/dist/umd/lucide.js"></script>
<script>
(() => {
const initialize = () => {
globalThis.lucide?.createIcons({ attrs: { width: 16, height: 16 } });
};
if (globalThis.lucide != null) {
initialize();
return;
}
document
.getElementById("codex-visualization-lucide")
?.addEventListener("load", initialize, { once: true });
})();
</script>