Menu
Public repository / master
AkurAI-Mail / src/framework/dom.js
Latest change 3af23b0e937520000e1aa43c7e2eb9de2675abd1 - OLI-248: webmail list and reader as framework islands on a signals store by Ólafur Búi Ólafsson
// dom.js — tiny DOM builder for island render functions (plain ESM, no build step).
//
// h("button", { class: "icon-button", "aria-label": "Close", onclick: close }, icon("X"), " Close")
//
// Attributes: `class`, `aria-*`, `data-*` and any string attribute; `on<event>` keys bind listeners;
// `false`/`null`/`undefined` values skip the attribute; `true` sets it empty. Children may be
// strings, nodes, arrays or null (skipped). Text children are text nodes, never HTML.
/**
* @param {string} tag
* @param {Record<string, any> | null} [attrs]
* @param {...any} children
* @returns {HTMLElement}
*/
export function h(tag, attrs, ...children) {
const el = document.createElement(tag);
for (const [key, value] of Object.entries(attrs ?? {})) {
if (value === false || value === null || value === undefined) continue;
if (key.startsWith("on") && typeof value === "function") el.addEventListener(key.slice(2), value);
else if (key === "style" && typeof value === "object") Object.assign(el.style, value);
else if (key === "value" && "value" in el) /** @type {any} */ (el).value = value;
else if (key === "checked" && "checked" in el) /** @type {any} */ (el).checked = !!value;
else el.setAttribute(key, value === true ? "" : String(value));
}
append(el, children);
return el;
}
/**
* @param {Node} el
* @param {any} children
*/
export function append(el, children) {
for (const child of Array.isArray(children) ? children : [children]) {
if (child === null || child === undefined || child === false) continue;
if (Array.isArray(child)) append(el, child);
else el.appendChild(child instanceof Node ? child : document.createTextNode(String(child)));
}
}
/**
* Replaces an element's children in one step.
* @param {Element} el
* @param {any} children
*/
export function replace(el, children) {
el.replaceChildren();
append(el, children);
}
/**
* Sanitized HTML fragment: only the markup the rich-text sanitizer allows gets through.
* @param {string} html
* @param {(html: string) => string} sanitize
*/
export function fragment(html, sanitize) {
const tpl = document.createElement("template");
tpl.innerHTML = sanitize(html);
return tpl.content;
}