// Kleiner DOM-Baukasten. Serverdaten gelangen nie über innerHTML in die Seite (nur als Text oder Attribut). /** h('div', {class:'x', onclick: fn}, 'Text', kind, …) */ export function h(tag, attrs = {}, ...kids) { const el = document.createElement(tag); for (const [k, v] of Object.entries(attrs || {})) { if (v === undefined || v === null || v === false) continue; if (k.startsWith('on') && typeof v === 'function') el.addEventListener(k.slice(2), v); else if (k === 'class') el.className = v; else if (k === 'value') el.value = v; else if (k === 'checked') el.checked = !!v; else if (k === 'style' && typeof v === 'object') Object.assign(el.style, v); else el.setAttribute(k, v === true ? '' : String(v)); } for (const kid of kids.flat(Infinity)) { if (kid === null || kid === undefined || kid === false) continue; el.append(kid.nodeType ? kid : document.createTextNode(String(kid))); } return el; } export const clear = (el) => { while (el.firstChild) el.removeChild(el.firstChild); return el; }; const ICONS = { home: 'M3 11l9-8 9 8M5 10v10h5v-6h4v6h5V10', list: 'M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01', check: 'M20 6L9 17l-5-5', plus: 'M12 5v14M5 12h14', menu: 'M4 6h16M4 12h16M4 18h16', back: 'M15 18l-6-6 6-6', search: 'M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14zM21 21l-4.3-4.3', external: 'M14 4h6v6M20 4l-9 9M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5', panel: 'M4 5h16v14H4zM14 5v14', send: 'M22 2L11 13M22 2l-7 20-4-9-9-4z', note: 'M4 4h16v12H8l-4 4zM8 9h8M8 12h5', clock: 'M12 7v5l3 2M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z', eye: 'M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12zM12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6z', user: 'M20 21a8 8 0 0 0-16 0M12 3a4 4 0 1 0 0 8 4 4 0 0 0 0-8z', refresh: 'M21 12a9 9 0 1 1-3-6.7L21 8M21 3v5h-5', clip: 'M21 12.5l-8.5 8.5a5.5 5.5 0 0 1-8-8L13 4.5a3.5 3.5 0 0 1 5 5l-8.5 8.5a1.5 1.5 0 0 1-2-2L15 8', logout: 'M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9', warn: 'M12 3l10 18H2zM12 10v5M12 18h.01', x: 'M18 6L6 18M6 6l12 12', }; /** Symbol als SVG (nur feste Pfade aus der Liste oben). */ export function icon(name, size = 18) { const ns = 'http://www.w3.org/2000/svg'; const svg = document.createElementNS(ns, 'svg'); for (const [k, v] of Object.entries({ viewBox: '0 0 24 24', width: size, height: size, fill: 'none', stroke: 'currentColor', 'stroke-width': 2, 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'aria-hidden': 'true' })) svg.setAttribute(k, String(v)); const p = document.createElementNS(ns, 'path'); p.setAttribute('d', ICONS[name] || ICONS.menu); svg.append(p); return svg; } export const errText = (e) => (e && e.message) || 'Es ist ein Fehler aufgetreten.'; let toastTimer; /** Kurze Meldung unten im Fenster. */ export function toast(text, kind = 'ok') { const host = document.getElementById('toasts'); if (!host) return; clear(host).append(h('div', { class: `toast ${kind}`, role: kind === 'err' ? 'alert' : 'status' }, text)); clearTimeout(toastTimer); toastTimer = setTimeout(() => clear(host), kind === 'err' ? 6000 : 2600); }