/**
* VMK UI Kit browser toast helper.
*
* Mirrors the public surface of gnexus-ui-kit/src/js/components/toasts.js:
* create, createInfo, createSuccess, createWarning, createError, createDanger
*
* create* return the initialized DOM element synchronously, matching the
* reference contract. Icons are loaded asynchronously and patched into the
* toast when available.
*/
const ICON_BY_TYPE = {
info: "essentials/information/circle",
success: "essentials/check/circle",
warning: "essentials/alert-sign",
danger: "essentials/alert/circle",
error: "essentials/alert/circle"
};
const TITLE_BY_TYPE = {
info: "Info",
success: "Success",
warning: "Warning",
danger: "Error",
error: "Error"
};
let indexPromise = null;
function loadIconIndex() {
if (indexPromise) return indexPromise;
indexPromise = fetch("/assets/icons/index.json")
.then(r => r.ok ? r.json() : {})
.catch(err => {
console.error(err);
return {};
});
return indexPromise;
}
function svgUrl(relativePath) {
return `/assets/icons/svg/${relativePath}`;
}
function sanitizeSvg(svgText) {
return svgText
.replace(/<script[\s\S]*?<\/script>/gi, "")
.replace(/on\w+="[^"]*"/gi, "")
.replace(/on\w+='[^']*'/gi, "")
.replace(/<style[\s\S]*?<\/style>/gi, "")
.replace(/<!--[\s\S]*?-->/g, "");
}
async function fetchIconSvg(name) {
const index = await loadIconIndex();
const rel = index[name];
if (!rel) return "";
const res = await fetch(svgUrl(rel));
if (!res.ok) return "";
return sanitizeSvg(await res.text());
}
function appendIcon(container, svgText) {
if (!svgText) return;
const wrap = document.createElement("span");
wrap.innerHTML = svgText;
const svg = wrap.querySelector("svg");
if (svg) {
svg.setAttribute("aria-hidden", "true");
svg.classList.add("vmk-icon");
container.append(svg);
}
}
function setIcon(placeholder, name) {
fetchIconSvg(name).then(svg => {
if (!svg || !placeholder.isConnected) return;
placeholder.innerHTML = "";
appendIcon(placeholder, svg);
});
}
function template(type, iconName, title, text) {
const toast = document.createElement("div");
toast.className = `toast toast-${type}`;
toast.setAttribute("role", "alert");
const content = document.createElement("div");
content.className = "toast-content";
const header = document.createElement("div");
header.className = "toast-header";
const iconPlaceholder = document.createElement("span");
iconPlaceholder.className = "toast-icon";
header.append(iconPlaceholder);
header.append(document.createTextNode(` ${title ?? ""}`));
content.append(header);
if (text) {
const toastText = document.createElement("p");
toastText.className = "toast-text";
toastText.textContent = text;
content.append(toastText);
}
const progress = document.createElement("div");
progress.className = "toast-progress";
const progressBar = document.createElement("div");
progressBar.className = "toast-progress-bar";
progress.append(progressBar);
const close = document.createElement("button");
close.className = "btn-icon toast-close";
close.type = "button";
close.setAttribute("aria-label", "Close");
close.innerHTML = "✕";
toast.append(content, close, progress);
setIcon(iconPlaceholder, iconName);
return toast;
}
function init(toast, props) {
const lifetime = props?.lifetime !== undefined ? props.lifetime : 4000;
if (props?.alone) {
document.querySelectorAll(".toast").forEach(i => i.close?.());
}
const progressBar = toast.querySelector(".toast-progress-bar");
if (progressBar && lifetime > 0) {
progressBar.style.animationDuration = `${lifetime}ms`;
}
toast.close = function () {
this.classList.add("a-hide");
setTimeout(() => {
this.remove();
}, 300);
};
toast.show = function () {
document.querySelector("body").append(toast);
setTimeout(() => {
toast.classList.add("a-show");
}, 10);
};
toast.addEventListener("mouseover", () => {
toast.ishovered = true;
});
toast.addEventListener("mouseout", () => {
toast.ishovered = false;
});
toast.querySelector(".toast-close").addEventListener("click", () => {
toast.close();
});
if (lifetime > 0) {
let elapsed = 0;
const interval = setInterval(() => {
elapsed += lifetime;
if (!toast.ishovered && elapsed >= lifetime) {
toast.close();
clearInterval(interval);
}
}, lifetime);
}
return toast;
}
function create(type, iconOverride, title, text, props) {
const resolvedType = type === "error" ? "danger" : type;
const iconName = iconOverride || ICON_BY_TYPE[resolvedType] || ICON_BY_TYPE.info;
const toastTitle = title !== undefined ? title : (TITLE_BY_TYPE[resolvedType] || "");
const toast = template(resolvedType, iconName, toastTitle, text);
return init(toast, applyDefaults(props));
}
function applyDefaults(props) {
if (typeof props === "undefined") {
props = {};
}
if (typeof props.lifetime === "undefined") {
props.lifetime = 4000;
}
if (typeof props.alone === "undefined") {
props.alone = true;
}
return props;
}
function createSuccess(title, text, props) {
return create("success", null, title, text, props);
}
function createInfo(title, text, props) {
return create("info", null, title, text, props);
}
function createWarning(title, text, props) {
return create("warning", null, title, text, props);
}
function createError(title, text, props) {
return create("danger", null, title, text, props);
}
export default {
create,
createInfo,
createSuccess,
createWarning,
createError,
createDanger: createError
};