UNPKG

stylescape

Version:

Stylescape is a visual identity framework developed by Scape Agency.

224 lines 9.19 kB
export class NotificationManager { constructor(options = {}) { this.container = null; this.notifications = new Map(); this.options = { position: options.position ?? "top-right", maxNotifications: options.maxNotifications ?? 5, cssClass: options.cssClass ?? "ss-notification", animationDuration: options.animationDuration ?? 300, defaultDuration: options.defaultDuration ?? 4000, newestOnTop: options.newestOnTop ?? true, pauseOnHover: options.pauseOnHover ?? true, }; this.createContainer(); if (!NotificationManager.instance) { NotificationManager.instance = this; } } success(message, options) { return this.show({ ...options, message, type: "success" }); } error(message, options) { return this.show({ ...options, message, type: "error", duration: options?.duration ?? 0, }); } warning(message, options) { return this.show({ ...options, message, type: "warning" }); } info(message, options) { return this.show({ ...options, message, type: "info" }); } show(options) { if (this.notifications.size >= this.options.maxNotifications) { const oldest = this.options.newestOnTop ? Array.from(this.notifications.keys()).pop() : Array.from(this.notifications.keys()).shift(); if (oldest) this.dismiss(oldest); } const id = `notification-${Date.now()}-${Math.random().toString(36).slice(2)}`; const element = this.createNotification(id, options); const duration = options.duration ?? this.options.defaultDuration; const instance = { id, element, options, timeout: null, startTime: Date.now(), remainingTime: duration, }; this.notifications.set(id, instance); if (this.options.newestOnTop) { this.container?.prepend(element); } else { this.container?.appendChild(element); } requestAnimationFrame(() => { element.classList.add(`${this.options.cssClass}--visible`); }); if (duration > 0) { instance.timeout = setTimeout(() => this.dismiss(id), duration); } return id; } dismiss(id) { const instance = this.notifications.get(id); if (!instance) return; if (instance.timeout) { clearTimeout(instance.timeout); } instance.element.classList.remove(`${this.options.cssClass}--visible`); instance.element.classList.add(`${this.options.cssClass}--removing`); setTimeout(() => { instance.element.remove(); this.notifications.delete(id); instance.options.onClose?.(); }, this.options.animationDuration); } dismissAll() { this.notifications.forEach((_, id) => this.dismiss(id)); } get count() { return this.notifications.size; } destroy() { this.dismissAll(); this.container?.remove(); this.container = null; if (NotificationManager.instance === this) { NotificationManager.instance = null; } } static getInstance() { if (!NotificationManager.instance) { NotificationManager.instance = new NotificationManager(); } return NotificationManager.instance; } static init() { const container = document.querySelector('[data-ss="notification-container"]'); return new NotificationManager({ position: container?.dataset .ssNotificationPosition, maxNotifications: container?.dataset.ssNotificationMax ? parseInt(container.dataset.ssNotificationMax, 10) : undefined, }); } createContainer() { this.container = document.querySelector(`[data-ss="notification-container"], .${this.options.cssClass}-container`); if (!this.container) { this.container = document.createElement("div"); this.container.className = `${this.options.cssClass}-container`; document.body.appendChild(this.container); } this.container.classList.add(`${this.options.cssClass}-container--${this.options.position}`); this.container.setAttribute("role", "region"); this.container.setAttribute("aria-label", "Notifications"); this.container.setAttribute("aria-live", "polite"); } createNotification(id, options) { const el = document.createElement("div"); el.className = `${this.options.cssClass} ${this.options.cssClass}--${options.type || "info"}`; if (options.className) { el.classList.add(options.className); } el.setAttribute("role", "alert"); el.setAttribute("aria-live", "assertive"); el.setAttribute("data-notification-id", id); const iconHtml = options.icon ? `<span class="${this.options.cssClass}__icon">${options.icon}</span>` : this.getDefaultIcon(options.type || "info"); const titleHtml = options.title ? `<div class="${this.options.cssClass}__title">${options.title}</div>` : ""; const actionHtml = options.actionText ? `<button type="button" class="${this.options.cssClass}__action">${options.actionText}</button>` : ""; const closeHtml = options.closable !== false ? `<button type="button" class="${this.options.cssClass}__close" aria-label="Close">&times;</button>` : ""; const progressHtml = options.showProgress && (options.duration ?? this.options.defaultDuration) > 0 ? `<div class="${this.options.cssClass}__progress"><div class="${this.options.cssClass}__progress-bar"></div></div>` : ""; el.innerHTML = ` ${iconHtml} <div class="${this.options.cssClass}__content"> ${titleHtml} <div class="${this.options.cssClass}__message">${options.message}</div> ${actionHtml} </div> ${closeHtml} ${progressHtml} `; const closeBtn = el.querySelector(`.${this.options.cssClass}__close`); closeBtn?.addEventListener("click", () => this.dismiss(id)); const actionBtn = el.querySelector(`.${this.options.cssClass}__action`); actionBtn?.addEventListener("click", () => { options.onAction?.(); this.dismiss(id); }); if (this.options.pauseOnHover) { el.addEventListener("mouseenter", () => this.pauseTimeout(id)); el.addEventListener("mouseleave", () => this.resumeTimeout(id)); } if (options.showProgress) { const duration = options.duration ?? this.options.defaultDuration; const progressBar = el.querySelector(`.${this.options.cssClass}__progress-bar`); if (progressBar && duration > 0) { progressBar.style.transition = `width ${duration}ms linear`; requestAnimationFrame(() => { progressBar.style.width = "0%"; }); } } return el; } getDefaultIcon(type) { const icons = { success: `<span class="${this.options.cssClass}__icon">✓</span>`, error: `<span class="${this.options.cssClass}__icon">✕</span>`, warning: `<span class="${this.options.cssClass}__icon">⚠</span>`, info: `<span class="${this.options.cssClass}__icon">ℹ</span>`, }; return icons[type]; } pauseTimeout(id) { const instance = this.notifications.get(id); if (!instance || !instance.timeout) return; clearTimeout(instance.timeout); instance.remainingTime -= Date.now() - instance.startTime; const progressBar = instance.element.querySelector(`.${this.options.cssClass}__progress-bar`); if (progressBar) { const computed = getComputedStyle(progressBar); progressBar.style.width = computed.width; progressBar.style.transition = "none"; } } resumeTimeout(id) { const instance = this.notifications.get(id); if (!instance || instance.remainingTime <= 0) return; instance.startTime = Date.now(); instance.timeout = setTimeout(() => this.dismiss(id), instance.remainingTime); const progressBar = instance.element.querySelector(`.${this.options.cssClass}__progress-bar`); if (progressBar) { progressBar.style.transition = `width ${instance.remainingTime}ms linear`; requestAnimationFrame(() => { progressBar.style.width = "0%"; }); } } } NotificationManager.instance = null; export default NotificationManager; //# sourceMappingURL=NotificationManager.js.map