Spaces:
Running
Running
| /** Tiny DOM helpers (no framework). */ | |
| /** | |
| * el("button.cls#id", {onclick, ...attrs}, [children|string]) | |
| * Tag is optional (defaults to div); `.class` and `#id` tokens may appear in | |
| * any order and any count after it (e.g. "section.view#view-compose"). | |
| */ | |
| export function el(spec, attrs = {}, children = []) { | |
| const tagMatch = spec.match(/^[a-z][a-z0-9]*/i); | |
| const tag = tagMatch ? tagMatch[0] : "div"; | |
| const node = document.createElement(tag); | |
| const rest = spec.slice(tagMatch ? tagMatch[0].length : 0); | |
| const idMatch = rest.match(/#([\w-]+)/); | |
| if (idMatch) node.id = idMatch[1]; | |
| const classes = [...rest.matchAll(/\.([\w-]+)/g)].map((x) => x[1]); | |
| if (classes.length) node.className = classes.join(" "); | |
| for (const [k, v] of Object.entries(attrs)) { | |
| if (v == null || v === false) continue; | |
| if (k.startsWith("on") && typeof v === "function") { | |
| node.addEventListener(k.slice(2).toLowerCase(), v); | |
| } else if (k === "class") { | |
| node.className = node.className ? `${node.className} ${v}` : v; | |
| } else if (k === "html") { | |
| node.innerHTML = v; | |
| } else if (k in node && k !== "list") { | |
| try { node[k] = v; } catch { node.setAttribute(k, v); } | |
| } else { | |
| node.setAttribute(k, v); | |
| } | |
| } | |
| const kids = Array.isArray(children) ? children : [children]; | |
| for (const c of kids) { | |
| if (c == null || c === false) continue; | |
| node.append(c.nodeType ? c : document.createTextNode(String(c))); | |
| } | |
| return node; | |
| } | |
| export function clear(node) { | |
| while (node.firstChild) node.removeChild(node.firstChild); | |
| return node; | |
| } | |
| /** Render a morse pattern string ('.-') as styled dot/dash chips. */ | |
| export function morsePattern(pattern) { | |
| const wrap = el("span.pattern"); | |
| for (const ch of pattern) { | |
| wrap.append(el(`span.${ch === "." ? "dot" : "dash"}`)); | |
| } | |
| return wrap; | |
| } | |