68 lines
3.1 KiB
JavaScript
68 lines
3.1 KiB
JavaScript
// 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);
|
|
}
|