This commit is contained in:
2025-12-04 22:33:05 +01:00
parent 316175e158
commit 9dee06cdd6
145 changed files with 16865 additions and 88 deletions

57
public/assets/js/toast.js Normal file
View File

@@ -0,0 +1,57 @@
// assets/js/toast.js
// Shows toast in the TOP LAYER: if a <dialog open> exists, append inside it.
// Otherwise append to <body>. Default type = success (green).
window.Toast = (function () {
// create (or reuse) a toast root inside a given container
function ensureRoot(container) {
// Find last (topmost) open dialog if not explicitly passed
const host = container || (() => {
const dialogs = Array.from(document.querySelectorAll('dialog[open]'));
return dialogs.length ? dialogs[dialogs.length - 1] : document.body;
})();
// Root per container (class-based to allow multiple roots)
let root = host.querySelector(':scope > .toast-root');
if (!root) {
root = document.createElement('div');
root.className = 'toast-root';
host.appendChild(root);
}
return root;
}
function render(msg, type, duration, container) {
const root = ensureRoot(container);
const n = document.createElement('div');
n.className = 'toast' + (type === 'error' ? ' error' : ' success');
n.innerHTML = `
<span class="icon">${type === 'error' ? '⚠️' : '✅'}</span>
<span class="content">${msg || ''}</span>
<button class="close" aria-label="Schließen">✕</button>
`;
root.appendChild(n);
const close = () => { try { n.remove(); } catch {} };
n.querySelector('.close')?.addEventListener('click', close);
const t = setTimeout(close, Number(duration) || 2200);
// clean timer if user closes early
n.addEventListener('remove', () => clearTimeout(t), { once:true });
}
// API
function show(msg, opt) {
opt = opt || {};
render(
msg,
opt.type === 'error' ? 'error' : 'success',
opt.duration,
opt.container // optional: pass a specific container
);
}
return { show };
})();