UNPKG

stylescape

Version:

Stylescape is a visual identity framework developed by Scape Agency.

297 lines 10.9 kB
export class Tooltip { constructor(selectorOrElement, options = {}) { this.tooltipElement = null; this.arrowElement = null; this.showTimeout = null; this.hideTimeout = null; this.isVisible = false; this.clearTimeouts = () => { if (this.showTimeout) { clearTimeout(this.showTimeout); this.showTimeout = null; } if (this.hideTimeout) { clearTimeout(this.hideTimeout); this.hideTimeout = null; } }; this.handleMouseEnter = () => { this.show(); }; this.handleMouseLeave = () => { this.hide(); }; this.handleFocus = () => { this.show(); }; this.handleBlur = () => { this.hide(); }; this.handleClick = (event) => { event.preventDefault(); this.toggle(); }; this.triggerElement = typeof selectorOrElement === "string" ? document.querySelector(selectorOrElement) : selectorOrElement; const triggers = options.trigger ? Array.isArray(options.trigger) ? options.trigger : [options.trigger] : ["hover", "focus"]; this.options = { content: options.content ?? this.triggerElement?.getAttribute("title") ?? "", position: options.position ?? "top", trigger: triggers, showDelay: options.showDelay ?? 200, hideDelay: options.hideDelay ?? 100, animationDuration: options.animationDuration ?? 150, offset: options.offset ?? 8, tooltipClass: options.tooltipClass ?? "tooltip__popup", maxWidth: options.maxWidth ?? 250, allowHTML: options.allowHTML ?? false, interactive: options.interactive ?? false, arrow: options.arrow ?? true, zIndex: options.zIndex ?? 9999, }; if (!this.triggerElement) { console.warn("[Stylescape] Tooltip trigger element not found"); return; } if (this.triggerElement.hasAttribute("title")) { this.triggerElement.removeAttribute("title"); } this.init(); } show() { if (!this.triggerElement) return; this.clearTimeouts(); this.showTimeout = setTimeout(() => { this.createTooltip(); this.position(); this.tooltipElement?.classList.add(`${this.options.tooltipClass}--visible`); this.isVisible = true; }, this.options.showDelay); } hide() { this.clearTimeouts(); this.hideTimeout = setTimeout(() => { this.tooltipElement?.classList.remove(`${this.options.tooltipClass}--visible`); setTimeout(() => { this.destroyTooltip(); this.isVisible = false; }, this.options.animationDuration); }, this.options.hideDelay); } toggle() { if (this.isVisible) { this.hide(); } else { this.show(); } } setContent(content) { this.options.content = content; if (this.tooltipElement) { const contentEl = this.tooltipElement.querySelector(`.${this.options.tooltipClass}__content`); if (contentEl) { if (this.options.allowHTML) { contentEl.innerHTML = content; } else { contentEl.textContent = content; } } } } setPosition(position) { this.options.position = position; if (this.tooltipElement) { this.position(); } } get visible() { return this.isVisible; } destroy() { this.clearTimeouts(); this.destroyTooltip(); this.removeEventListeners(); this.triggerElement = null; } static initTooltips() { const tooltips = []; document .querySelectorAll('[data-ss="tooltip"]') .forEach((el) => { const content = el.dataset.ssTooltipContent || el.getAttribute("title") || ""; const position = el.dataset.ssTooltipPosition || "top"; const trigger = el.dataset.ssTooltipTrigger?.split(",") || ["hover", "focus"]; tooltips.push(new Tooltip(el, { content, position, trigger, })); }); return tooltips; } init() { if (!this.triggerElement) return; const id = `tooltip-${Date.now()}-${Math.random().toString(36).slice(2)}`; this.triggerElement.setAttribute("aria-describedby", id); const triggers = this.options.trigger; if (triggers.includes("hover")) { this.triggerElement.addEventListener("mouseenter", this.handleMouseEnter); this.triggerElement.addEventListener("mouseleave", this.handleMouseLeave); } if (triggers.includes("focus")) { this.triggerElement.addEventListener("focus", this.handleFocus); this.triggerElement.addEventListener("blur", this.handleBlur); } if (triggers.includes("click")) { this.triggerElement.addEventListener("click", this.handleClick); } } removeEventListeners() { if (!this.triggerElement) return; this.triggerElement.removeEventListener("mouseenter", this.handleMouseEnter); this.triggerElement.removeEventListener("mouseleave", this.handleMouseLeave); this.triggerElement.removeEventListener("focus", this.handleFocus); this.triggerElement.removeEventListener("blur", this.handleBlur); this.triggerElement.removeEventListener("click", this.handleClick); } createTooltip() { if (this.tooltipElement) return; const tooltip = document.createElement("div"); tooltip.className = this.options.tooltipClass; tooltip.setAttribute("role", "tooltip"); tooltip.setAttribute("id", this.triggerElement?.getAttribute("aria-describedby") || ""); tooltip.style.zIndex = String(this.options.zIndex); tooltip.style.maxWidth = `${this.options.maxWidth}px`; const content = document.createElement("div"); content.className = `${this.options.tooltipClass}__content`; if (this.options.allowHTML) { content.innerHTML = this.options.content; } else { content.textContent = this.options.content; } tooltip.appendChild(content); if (this.options.arrow) { this.arrowElement = document.createElement("div"); this.arrowElement.className = `${this.options.tooltipClass}__arrow`; tooltip.appendChild(this.arrowElement); } if (this.options.interactive) { tooltip.addEventListener("mouseenter", this.clearTimeouts); tooltip.addEventListener("mouseleave", () => this.hide()); } document.body.appendChild(tooltip); this.tooltipElement = tooltip; } destroyTooltip() { this.tooltipElement?.remove(); this.tooltipElement = null; this.arrowElement = null; } position() { if (!this.triggerElement || !this.tooltipElement) return; const triggerRect = this.triggerElement.getBoundingClientRect(); const tooltipRect = this.tooltipElement.getBoundingClientRect(); const scrollX = window.scrollX; const scrollY = window.scrollY; let position = this.options.position; if (position === "auto") { position = this.calculateBestPosition(triggerRect, tooltipRect); } let top; let left; switch (position) { case "top": top = triggerRect.top + scrollY - tooltipRect.height - this.options.offset; left = triggerRect.left + scrollX + (triggerRect.width - tooltipRect.width) / 2; break; case "bottom": top = triggerRect.bottom + scrollY + this.options.offset; left = triggerRect.left + scrollX + (triggerRect.width - tooltipRect.width) / 2; break; case "left": top = triggerRect.top + scrollY + (triggerRect.height - tooltipRect.height) / 2; left = triggerRect.left + scrollX - tooltipRect.width - this.options.offset; break; case "right": top = triggerRect.top + scrollY + (triggerRect.height - tooltipRect.height) / 2; left = triggerRect.right + scrollX + this.options.offset; break; default: top = triggerRect.top + scrollY - tooltipRect.height - this.options.offset; left = triggerRect.left + scrollX + (triggerRect.width - tooltipRect.width) / 2; } left = Math.max(8, Math.min(left, window.innerWidth - tooltipRect.width - 8)); this.tooltipElement.style.top = `${top}px`; this.tooltipElement.style.left = `${left}px`; this.tooltipElement.dataset.position = position; if (this.arrowElement) { this.arrowElement.dataset.position = position; } } calculateBestPosition(triggerRect, tooltipRect) { const space = { top: triggerRect.top, bottom: window.innerHeight - triggerRect.bottom, left: triggerRect.left, right: window.innerWidth - triggerRect.right, }; const height = tooltipRect.height + this.options.offset; const width = tooltipRect.width + this.options.offset; if (space.top >= height) return "top"; if (space.bottom >= height) return "bottom"; if (space.right >= width) return "right"; if (space.left >= width) return "left"; return "top"; } } export default Tooltip; //# sourceMappingURL=Tooltip.js.map