(function () {
"use strict";
const banners = new Map();
const GAP = 12;
const DEFAULT_ID = "default";
function normalizeId(id) {
return String(id || DEFAULT_ID);
}
function getBanner(id) {
id = normalizeId(id);
if (banners.has(id)) return banners.get(id).element;
const banner = document.createElement("div");
banner.className = "operation-banner";
banner.dataset.operationBannerId = id;
banner.setAttribute("role", "status");
banner.setAttribute("aria-live", "polite");
banner.innerHTML = `
`;
banner.querySelector(".ob-close").addEventListener("click", () => hide(id));
document.body.appendChild(banner);
banners.set(id, { element: banner, timer: null });
return banner;
}
function clearTimer(entry) {
if (entry && entry.timer !== null) {
clearTimeout(entry.timer);
entry.timer = null;
}
}
function reposition() {
let bottom = 20;
for (const entry of [...banners.values()]) {
const banner = entry.element;
if (!banner.classList.contains("is-visible")) continue;
banner.style.bottom = `${bottom}px`;
bottom += banner.getBoundingClientRect().height + GAP;
}
}
function hide(id = DEFAULT_ID) {
id = normalizeId(id);
const entry = banners.get(id);
if (!entry) return;
clearTimer(entry);
entry.element.classList.remove("is-visible", "is-busy");
reposition();
}
function show(message, options = {}) {
const id = normalizeId(options.id);
const banner = getBanner(id);
const entry = banners.get(id);
clearTimer(entry);
const type = options.type || "info";
const busy = options.busy === true;
const sticky = options.sticky === true;
const icons = {
info: "ℹ",
upload: "↑",
download: "↓",
success: "✓",
error: "!"
};
banner.classList.remove(
"is-info", "is-upload", "is-download", "is-success", "is-error"
);
banner.classList.add("is-" + type);
banner.classList.toggle("is-busy", busy);
banner.querySelector(".ob-icon").textContent = icons[type] || icons.info;
banner.querySelector(".ob-content").textContent = String(message || "");
banner.classList.add("is-visible");
reposition();
if (!busy && !sticky) {
const duration = Number.isFinite(options.duration)
? options.duration
: (type === "error" ? 6000 : 3500);
entry.timer = setTimeout(() => hide(id), duration);
}
}
function setProgress(id, value) {
id = normalizeId(id);
const banner = getBanner(id);
const percent = Math.max(0, Math.min(100, Number(value) || 0));
banner.classList.add("is-determinate");
banner.classList.remove("is-busy");
banner.querySelector(".ob-progress-fill").style.width = `${percent}%`;
banner.querySelector(".ob-progress-label").textContent = `${Math.round(percent)}%`;
reposition();
}
window.operationBanner = { show, hide, setProgress };
})();