Files
djledda-web/app/tooltip.tsx
Daniel Ledda bcb820f35e nice
2024-10-31 23:46:23 +01:00

123 lines
4.4 KiB
TypeScript

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<HTMLElement | null>(null);
const textCarrier = ref<HTMLElement | null>(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 () => (
<div ref={carrier}>
<span ref={textCarrier} />
</div>
);
},
});