UNPKG

@studiocms/ui

Version:

The UI library for StudioCMS. Includes the layouts & components we use to build StudioCMS.

161 lines (160 loc) 5.49 kB
import { generateID } from "../../utils/generateID.js"; import { getIconString } from "../../utils/iconStrings.js"; let activeToasts = []; let lastActiveElement = null; const revertFocusBackToLastActiveElement = () => { if (lastActiveElement) { lastActiveElement.focus(); lastActiveElement = null; } }; class Timer { id; started; remaining; running; callback; constructor(callback, delay) { this.id = null; this.started = null; this.remaining = delay; this.running = false; this.callback = callback; this.start(); } start = () => { this.running = true; this.started = /* @__PURE__ */ new Date(); this.id = setTimeout(this.callback, this.remaining); }; pause = () => { if (!this.id || !this.started || !this.running) return; this.running = false; clearTimeout(this.id); this.remaining -= (/* @__PURE__ */ new Date()).getTime() - this.started.getTime(); }; getTimeLeft = () => { if (this.running) { this.pause(); this.start(); } return this.remaining; }; getStateRunning = () => { return this.running; }; } function removeToast(toastID) { const toastEl = document.getElementById(toastID); if (!toastEl) return; activeToasts = activeToasts.filter((x) => x !== toastID); toastEl.classList.add("closing"); setTimeout(() => toastEl.remove(), 400); } function createToast(props) { const toastParent = document.getElementById("sui-toast-drawer"); const toastContainer = document.createElement("div"); const toastID = generateID("toast"); toastContainer.tabIndex = 0; toastContainer.ariaLive = "polite"; toastContainer.role = "alert"; toastContainer.id = toastID; toastContainer.ariaLabel = `${props.title} (F8)`; toastContainer.classList.add( "sui-toast-container", `${props.closeButton || props.persistent && "closeable"}`, `${props.persistent && "persistent"}` ); const toastHeader = document.createElement("div"); toastHeader.classList.add("sui-toast-header"); const toastHeaderLeftSide = document.createElement("div"); toastHeaderLeftSide.classList.add("sui-toast-header-left-side"); const toastTitle = document.createElement("span"); toastTitle.textContent = props.title; toastTitle.classList.add("sui-toast-title"); let iconString; if (props.type === "success") { iconString = "check-circle"; } else if (props.type === "danger") { iconString = "exclamation-circle"; } else if (props.type === "warning") { iconString = "exclamation-triangle"; } else { iconString = "information-circle"; } const toastIcon = getIconString(iconString, "toast-icon", 24, 24); toastHeaderLeftSide.innerHTML = toastIcon; toastHeaderLeftSide.appendChild(toastTitle); toastHeader.appendChild(toastHeaderLeftSide); if (props.closeButton || props.persistent) { const closeIconContainer = document.createElement("button"); closeIconContainer.classList.add("close-icon-container"); closeIconContainer.addEventListener("click", () => removeToast(toastID)); closeIconContainer.innerHTML = getIconString("x-mark", "close-icon", 24, 24); closeIconContainer.tabIndex = 0; closeIconContainer.ariaLabel = "Close toast"; toastHeader.appendChild(closeIconContainer); } toastContainer.appendChild(toastHeader); if (props.description) { const toastDesc = document.createElement("span"); toastDesc.innerHTML = props.description; toastDesc.classList.add("sui-toast-desc"); toastContainer.appendChild(toastDesc); } if (!props.persistent) { const toastProgressBar = document.createElement("div"); toastProgressBar.classList.add("sui-toast-progress-bar"); toastProgressBar.style.animationDuration = props.duration ? `${props.duration}ms` : `${toastParent.dataset.duration || 4e3}ms`; toastContainer.appendChild(toastProgressBar); } toastParent.appendChild(toastContainer); activeToasts.push(toastID); if (!props.persistent) { const timer = new Timer( () => removeToast(toastID), props.duration || (toastParent.dataset.duration ? Number.parseInt(toastParent.dataset.duration) : 4e3) ); const timerPauseWrapper = () => { toastContainer.classList.add("paused"); timer.pause(); }; const timerStartWrapper = () => { toastContainer.classList.remove("paused"); timer.start(); }; toastContainer.addEventListener("mouseenter", timerPauseWrapper); toastContainer.addEventListener("focusin", timerPauseWrapper); toastContainer.addEventListener("mouseleave", timerStartWrapper); toastContainer.addEventListener("focusout", () => { const focusedOrHasFocused = toastContainer.matches(":focus-within"); if (!focusedOrHasFocused) { revertFocusBackToLastActiveElement(); } timerStartWrapper(); }); } toastContainer.addEventListener("keydown", (e) => { if (e.key === "Escape") { e.preventDefault(); removeToast(toastID); revertFocusBackToLastActiveElement(); } }); } document.addEventListener("createtoast", (e) => { e.stopImmediatePropagation(); const event = e; createToast(event.detail); }); window.addEventListener("keydown", (e) => { if (e.key === "F8") { e.preventDefault(); const oldestToast = activeToasts[0]; if (oldestToast) { lastActiveElement = document.activeElement; const toastEl = document.getElementById(oldestToast); if (toastEl) toastEl?.focus(); } } });