Files
warpbox/static/js/warpbox-ui.js

99 lines
3.6 KiB
JavaScript
Raw Permalink Normal View History

window.WarpBoxUI = (() => {
let toastTimer = null;
function toast(message, type = "info", options = {}) {
const target = options.target || document.querySelector("#toast");
if (!target) return;
target.textContent = message;
target.classList.remove("toast-info", "toast-warning", "toast-error", "is-visible");
target.classList.add(`toast-${type}`, "is-visible");
clearTimeout(toastTimer);
toastTimer = setTimeout(() => target.classList.remove("is-visible"), options.duration || 2600);
}
function popupElements(options = {}) {
return {
popup: options.popup || document.querySelector("#doc-popup"),
title: options.title || document.querySelector("#doc-popup-title"),
body: options.body || document.querySelector("#doc-popup-body"),
backdrop: options.backdrop || document.querySelector("#modal-backdrop"),
};
}
function openPopup(titleText, html, options = {}) {
const parts = popupElements(options);
if (!parts.popup || !parts.title || !parts.body) return;
parts.title.textContent = titleText;
parts.body.innerHTML = html;
parts.popup.classList.toggle("is-about-popup", Boolean(options.about));
parts.popup.classList.toggle("is-properties-popup", Boolean(options.properties));
parts.popup.classList.toggle("is-preview-popup", Boolean(options.preview));
parts.popup.classList.add("is-visible");
parts.backdrop?.classList.add("is-visible");
}
function closePopup(options = {}) {
const parts = popupElements(options);
parts.popup?.classList.remove("is-visible", "is-about-popup", "is-properties-popup", "is-preview-popup");
parts.backdrop?.classList.remove("is-visible");
}
function htmlEscape(value) {
return String(value || "")
.replaceAll("&", "&")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#39;");
}
function renderTemplate(template, data = {}) {
return String(template).replace(/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g, (_, key) => {
return Object.prototype.hasOwnProperty.call(data, key) ? String(data[key]) : "";
});
}
function bindMenuBar(options = {}) {
const root = options.root || document;
const itemSelector = options.itemSelector || ".menu-item";
const buttonSelector = options.buttonSelector || ".menu-button";
const items = Array.from(root.querySelectorAll(itemSelector));
function close() {
items.forEach((item) => {
item.classList.remove("is-open");
item.querySelector(buttonSelector)?.setAttribute("aria-expanded", "false");
});
}
function open(item) {
close();
item.classList.add("is-open");
item.querySelector(buttonSelector)?.setAttribute("aria-expanded", "true");
}
items.forEach((item) => {
const button = item.querySelector(buttonSelector);
button?.addEventListener("click", (event) => {
event.stopPropagation();
const wasOpen = item.classList.contains("is-open");
close();
if (!wasOpen) open(item);
});
item.addEventListener("mouseenter", () => {
if (!root.querySelector(`${itemSelector}.is-open`)) return;
open(item);
});
});
document.addEventListener("click", (event) => {
if (!event.target.closest(itemSelector)) close();
});
return { close, open };
}
return { toast, openPopup, closePopup, htmlEscape, renderTemplate, bindMenuBar };
})();