// Undercooked — the screens' tiny DOM helper (phase 07 task 02). // // The screens are built with `document.createElement` and `textContent` // only — never `innerHTML` with server data (names, messages and level // ids arrive from the server and must not parse as HTML). This module // is that rule made cheap: `el()` builds a subtree from a declarative // description, and every string lands in a text node or an attribute. /** * Build a DOM node. * * `el("button", { class: "btn", type: "button", "aria-label": "Copy" }, * el("span", {}, "Copy"), "Copy")` * * Children may be nodes, strings (become text nodes) or null (skipped). * Attribute values that are boolean are set/unset as flags; numbers are * converted; `value`/`textContent` shortcuts are not needed — `text` in * the attributes object sets `textContent` (and forbids children). */ export function el(tag, attrs = {}, ...children) { const node = document.createElement(tag); for (const [name, value] of Object.entries(attrs)) { if (value === null || value === undefined || value === false) continue; if (name === "text") { node.textContent = String(value); } else if (name === "value") { node.value = value; } else if (name === "dataset") { for (const [key, item] of Object.entries(value)) { node.dataset[key] = item; } } else if (name.startsWith("on") && typeof value === "function") { // `onClick` → the DOM event `click` (listeners are case-sensitive). node.addEventListener(name.slice(2).toLowerCase(), value); } else if (value === true) { node.setAttribute(name, ""); } else { node.setAttribute(name, String(value)); } } if ("text" in attrs) return node; for (const child of children) { if (child === null || child === undefined) continue; node.append(child instanceof Node ? child : document.createTextNode(String(child))); } return node; } /** A `document.createDocumentFragment` of the given children. */ export function frag(...children) { const node = document.createDocumentFragment(); for (const child of children) { if (child === null || child === undefined) continue; node.append(child instanceof Node ? child : document.createTextNode(String(child))); } return node; } /** * The 5-character code the user typed, the way the server normalises it * (docs/PROTOCOL.md: Crockford base32 — the confusables `I L O U` are * the server's to map; the client only uppercases and keeps what a code * can contain). Returns "" when the input is not a full code. */ export function normalizeCode(value) { return String(value ?? "") .toUpperCase() .replace(/[^0-9A-Z]/g, "") .slice(0, 5); } export const CODE_LENGTH = 5;