@studiocms/ui
Version:
The UI library for StudioCMS. Includes the layouts & components we use to build StudioCMS.
161 lines (160 loc) • 5.49 kB
JavaScript
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();
}
}
});