Files
deskhub/chrome-erweiterung/lib/dom.js
T
2026-11-01 11:15:09 +00:00

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);
}