|
- interface Point {
- x: number;
- y: number;
- }
-
- interface Size {
- height: string;
- width: string;
- }
-
- class SexyTooltip {
- private static readonly carrierStyle: Partial<CSSStyleDeclaration> = {
- 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",
- };
- private static readonly textCarrierStyle: Partial<CSSStyleDeclaration> = {
- width: "350px",
- display: "block",
- overflow: "hidden",
- };
- private static readonly defaultWidth = 350;
- private readonly carrier: HTMLDivElement;
- private readonly textCarrier: HTMLSpanElement;
- private readonly elementsWithTooltips: Element[] = [];
- private active: boolean = false;
-
- constructor(carrier: HTMLDivElement) {
- if (carrier.childNodes.length > 0) {
- throw new Error("Incorrect setup for tooltip! Remove the child nodes from the tooltip div.");
- }
- this.carrier = carrier;
- this.textCarrier = document.createElement("span");
- this.carrier.appendChild(this.textCarrier);
- Object.assign(this.carrier.style, SexyTooltip.carrierStyle);
- Object.assign(this.textCarrier.style, SexyTooltip.textCarrierStyle);
- document.querySelectorAll(".tooltip").forEach((element) => {
- if (element.nodeName === "SPAN") {
- console.log(element.previousElementSibling);
- this.elementsWithTooltips.push(element.previousElementSibling);
- }
- });
- document.addEventListener("mousemove", (event) => this.rerenderTooltip({ x: event.pageX, y: event.pageY }));
- }
-
- rerenderTooltip(mousePos: Point) {
- const newText = this.getTooltipTextForHoveredElement(mousePos);
- if (newText !== this.textCarrier.innerHTML) {
- this.transitionTooltipSize(newText);
- const tooltipHasText = newText !== "";
- if (tooltipHasText !== this.active) {
- this.toggleActive();
- }
- }
- if (this.isStillVisible()) {
- this.updatePosition(mousePos);
- }
- }
-
- isStillVisible() {
- return getComputedStyle(this.carrier).opacity !== "0";
- }
-
- updatePosition(pos: Point) {
- 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";
- }
- }
-
- 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;
- }
- }
-
- transitionTooltipSize(text: string) {
- const testDiv = document.createElement("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;
- }
-
- getTooltipTextForHoveredElement(mousePos: Point): string {
- 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 as HTMLSpanElement)?.innerText ?? "";
- }
- }
- return "";
- }
- }
-
- export default SexyTooltip;
|