import { h, qsa } from "@/util.ts"; import { type CSSProperties, defineComponent, onBeforeUnmount, onMounted, ref } from "vue"; const carrierStyle = { opacity: "0", display: "block", pointerEvents: "none", backgroundColor: "black", border: "white solid 1px", color: "white", padding: "10px", position: "absolute", zIndex: "1", overflow: "hidden", height: "0", width: "0", transition: "opacity 200ms, height 200ms, width 200ms", } satisfies CSSProperties; const textCarrierStyle = { width: "350px", display: "block", overflow: "hidden", } satisfies CSSProperties; const defaultWidth = 350; export default defineComponent({ name: "dj-sexy-tooltip", setup() { const active = ref(false); const carrier = ref(null); const textCarrier = ref(null); onMounted(() => { document.body.appendChild(h("style", { textContent: `.tooltip { display: none; }` })); document.body.style.position = "relative"; document.addEventListener("mousemove", (event) => this.rerenderTooltip({ x: event.pageX, y: event.pageY })); }); function rerenderTooltip(mousePos: { x: number; y: number }) { const newText = this.getTooltipTextForHoveredElement(mousePos); if (newText !== this.textCarrier) { this.transitionTooltipSize(newText); const tooltipHasText = newText !== ""; if (tooltipHasText !== this.active) { this.toggleActive(); } } if (this.isStillVisible()) { this.updatePosition(mousePos); } } function isStillVisible() { return getComputedStyle(carrier.value).opacity !== "0"; } function updatePosition(pos: { x: number; y: number }) { if (pos.x + 15 + this.carrier.clientWidth <= document.body.scrollWidth) { this.carrier.style.left = (pos.x + 15) + "px"; } else { this.carrier.style.left = (document.body.scrollWidth - this.carrier.clientWidth - 5) + "px"; } if (pos.y + this.carrier.clientHeight <= document.body.scrollHeight) { this.carrier.style.top = pos.y + "px"; } else { this.carrier.style.top = (document.body.scrollHeight - this.carrier.clientHeight - 5) + "px"; } } function toggleActive() { if (this.active) { this.active = false; this.carrier.style.opacity = "0"; this.carrier.style.padding = "0"; } else { this.active = true; this.carrier.style.opacity = "1"; this.carrier.style.padding = SexyTooltip.carrierStyle.padding; } } function transitionTooltipSize(text: string) { const testDiv = h("div"); testDiv.style.height = "auto"; testDiv.style.width = SexyTooltip.defaultWidth + "px"; testDiv.innerHTML = text; document.body.appendChild(testDiv); const calculatedHeight = testDiv.scrollHeight; testDiv.style.display = "inline"; testDiv.style.width = "auto"; document.body.removeChild(testDiv); const size = { height: calculatedHeight + "px", width: "350px" }; this.carrier.style.height = size.height; this.carrier.style.width = text === "" ? "0" : size.width; this.textCarrier.innerHTML = text; } function getTooltipTextForHoveredElement(mousePos: { x: number; y: number }) { for (const elem of this.elementsWithTooltips) { const boundingRect = elem.getBoundingClientRect(); const inYRange = mousePos.y >= boundingRect.top + window.pageYOffset - 1 && mousePos.y <= boundingRect.bottom + window.pageYOffset + 1; const inXRange = mousePos.x >= boundingRect.left + window.pageXOffset - 1 && mousePos.x <= boundingRect.right + window.pageXOffset + 1; if (inYRange && inXRange) { return elem.nextElementSibling?.innerText ?? ""; } } return ""; } return () => (
); }, });