stylescape
Version:
Stylescape is a visual identity framework developed by Scape Agency.
297 lines • 10.9 kB
JavaScript
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