pub const DOM_JS: &str = "// DOM rendering for ZDeceptron.\n//\n// Direct DOM manipulation, no virtual DOM. Every binding is an `effect`,\n// so a signal write reaches exactly the text nodes and attributes that\n// read it \u{2014} a component never re-renders as a unit because there are no\n// components at this layer, only bindings.\n//\n// Generated code calls into this module. It is not written by hand and is\n// not a user-facing API, which is why it optimises for the code generator\n// rather than for ergonomics.\n\nimport { signal, effect, batch, owned, onCleanup } from \'./signal.js\';\n\n/** A value that may be a signal getter or a constant. */\nfunction read(value) {\n return typeof value === \'function\' ? value() : value;\n}\n\n/**\n * Create an element with reactive properties and children.\n *\n * `props` values may be getters; each becomes its own effect, so changing\n * one attribute does not touch the others.\n */\nexport function el(tag, props = {}, children = []) {\n const node = document.createElement(tag);\n\n for (const [name, value] of Object.entries(props)) {\n if (name.startsWith(\'on\')) {\n on(node, name.slice(2).toLowerCase(), value);\n } else if (name === \'style\' && typeof value === \'object\') {\n for (const [prop, v] of Object.entries(value)) {\n effect(() => {\n node.style.setProperty(prop, String(read(v)));\n });\n }\n } else if (typeof value === \'function\') {\n effect(() => setAttribute(node, name, value()));\n } else {\n setAttribute(node, name, value);\n }\n }\n\n appendChildren(node, children);\n return node;\n}\n\nfunction setAttribute(node, name, value) {\n if (name === \'value\' && \'value\' in node) {\n if (node.value !== String(value)) node.value = String(value);\n } else if (name === \'checked\' && \'checked\' in node) {\n node.checked = Boolean(value);\n } else if (value === false || value === null || value === undefined) {\n node.removeAttribute(name);\n } else if (value === true) {\n node.setAttribute(name, \'\');\n } else {\n node.setAttribute(name, String(value));\n }\n}\n\nfunction appendChildren(parent, children) {\n for (const child of [].concat(children)) {\n if (child === null || child === undefined) continue;\n if (child instanceof Node) {\n parent.appendChild(child);\n } else if (typeof child === \'function\') {\n parent.appendChild(dynamic(child));\n } else {\n parent.appendChild(document.createTextNode(String(child)));\n }\n }\n}\n\n/**\n * A text node bound to a getter.\n *\n * Updating it writes `nodeValue` rather than replacing the node, so the\n * browser keeps selection and caret position \u{2014} one of the things a\n * virtual-DOM diff has to work to preserve and this gets for free.\n */\nexport function text(getter) {\n const node = document.createTextNode(\'\');\n effect(() => {\n const value = read(getter);\n node.nodeValue = value === null || value === undefined ? \'\' : String(value);\n });\n return node;\n}\n\n// --- the template surface (spec \u{a7}16.2 R2) --------------------------------\n//\n// Generated code does not build the DOM node by node. It parses one static\n// HTML string per view region into a `<template>`, clones it per\n// instantiation, walks to compile-time-computed offsets, and attaches a\n// binding only at the holes. Everything below is what that emission needs;\n// it is additive, and `dynamic`, `each` and `when` are re-expressed as thin\n// wrappers over it so there is one implementation of each rather than two.\n\n/**\n * Parse `html` once, then hand out a fresh clone per call.\n *\n * The returned value is a *fragment*, not its first child. A view region\n * may legally have several roots \u{2014} `view`, a `when` arm and an `each` body\n * are all node lists \u{2014} and returning `content.firstChild` silently discards\n * every root but the first (spec \u{a7}16.9, finding 8).\n *\n * `html` is never a runtime value. The compiler interpolates only\n * compile-time string *literals* into it, HTML-escaped (spec \u{a7}16.3.5);\n * every value a program computes reaches the DOM through `nodeValue`,\n * `setAttribute`, `.value` or `.checked`, none of which parses HTML. So\n * template cloning adds no injection surface over the node-by-node path.\n */\nexport function template(html) {\n let content;\n return () => {\n if (content === undefined) {\n const element = document.createElement(\'template\');\n element.innerHTML = html;\n content = element.content;\n }\n return content.cloneNode(true);\n };\n}\n\n/**\n * A fragment holding an empty anchored region: a start and an end comment.\n *\n * A region that is nothing but a hole has no markup to clone, so the\n * emitter calls this instead of parsing a template made of two comments.\n */\nexport function anchors() {\n const fragment = document.createDocumentFragment();\n fragment.append(document.createComment(\'\'), document.createComment(\'\'));\n return fragment;\n}\n\n/**\n * Bind an existing text node to a getter.\n *\n * The write is guarded by a comparison (spec \u{a7}16.2 R7). A list re-supplies\n * every surviving row\'s item on every change, which re-runs every row\'s\n * binding; without the guard, one changed row dirties layout for all of\n * them. `setAttribute` below already does exactly this for `value`.\n */\nexport function bindText(node, getter) {\n effect(() => {\n const value = read(getter);\n const next = value === null || value === undefined ? \'\' : String(value);\n if (node.nodeValue !== next) node.nodeValue = next;\n });\n}\n\n/**\n * The schemes a URL-bearing attribute may name (spec \u{a7}16.3.5, corrected).\n *\n * \u{a7}16.3.5\'s escaping argument is about the *markup* grammar: it\n * establishes that a value cannot close a tag or open one. It says nothing\n * about `href` and `src`, which the browser hands to the URL parser\n * instead. `setAttribute(\'href\', v)` stores `v` verbatim, and\n * `javascript:alert(1)` in an `href` executes on click \u{2014} there is nothing\n * in it for an HTML escaper to escape. Escaping for HTML text is not\n * escaping for a URL; they are different grammars.\n *\n * An allowlist, not a list of the dangerous schemes. `javascript:`,\n * `data:` and `vbscript:` are the three usually named, but which schemes a\n * browser executes is the browser\'s decision and it changes; a denylist is\n * out of date the day it is written.\n *\n * The compiler settles every URL it can see \u{2014} a literal in an `href` is a\n * compile error, not a value filtered here \u{2014} so this runs only on values\n * the compiler could not see. It is the Rust half\'s exact mirror\n * (`zdc_hir::url_is_safe`), and `crates/zdc-codegen/tests/url.rs` runs the\n * two against one table so that changing one without the other fails.\n *\n * A refused URL becomes the empty string, not `#`: a link that goes\n * nowhere should not scroll the page to the top when it is clicked.\n */\nconst URL_SCHEMES = [\'http\', \'https\', \'mailto\', \'tel\'];\n\nexport function safeUrl(value) {\n const url = value === null || value === undefined ? \'\' : String(value);\n // Leading whitespace is stripped by the browser before it parses the\n // scheme, so `\\njavascript:alert(1)` is a `javascript:` URL.\n const trimmed = url.trimStart();\n const colon = trimmed.indexOf(\':\');\n if (colon === -1) return url;\n const scheme = trimmed.slice(0, colon);\n // A colon inside a path or a query is not a scheme: `/a:b` is relative.\n if (/[/?#]/.test(scheme)) return url;\n return URL_SCHEMES.includes(scheme.toLowerCase()) ? url : \'\';\n}\n\n/** Bind an existing element\'s attribute to a getter. */\nexport function bindAttr(node, name, getter) {\n effect(() => setAttribute(node, name, read(getter)));\n}\n\n/** Bind one CSS property of an existing element to a getter. */\nexport function bindStyle(node, property, getter) {\n effect(() => {\n node.style.setProperty(property, String(read(getter)));\n });\n}\n\n/**\n * Attach an event listener to an existing element.\n *\n * Batched, so generated code emits no `batch(...)` of its own, and `el`\n * routes here rather than repeating the listener: one place decides what a\n * handler is.\n *\n * **A handler that throws is contained and reported (#139)** \u{2014} the page\n * keeps running, its writes stand, and `reportError` is the platform\'s own\n * uncaught-error channel. `docs/reference.md` \u{a7}10 argues it.\n */\nexport function on(node, event, handler) {\n node.addEventListener(event, (e) => {\n try {\n batch(() => handler(e));\n } catch (failure) {\n reportError(failure);\n }\n });\n}\n\n/**\n * A region between two existing anchors whose content is replaced when its\n * getter changes.\n */\nexport function dynamicInto(start, end, getter) {\n effect(() => {\n const value = read(getter);\n clearBetween(start, end);\n const rendered = value instanceof Node ? value : document.createTextNode(String(value ?? \'\'));\n end.parentNode.insertBefore(rendered, end);\n });\n}\n\n/**\n * A region whose content is replaced when its getter changes.\n *\n * Anchored between two comment nodes so the region\'s extent is known\n * without wrapping it in an element the program did not ask for.\n */\nexport function dynamic(getter) {\n const fragment = anchors();\n dynamicInto(fragment.firstChild, fragment.lastChild, getter);\n return fragment;\n}\n\n/**\n * Variant dispatch \u{2014} `when value` over `Remote`, `Option`, or a `choice`.\n *\n * `arms` maps a variant name to a function receiving that variant\'s\n * fields positionally. Spec \u{a7}14G.1.6 requires every arm to be present,\n * so a missing arm is a compiler bug rather than a runtime fallback.\n */\nexport function when(getter, arms) {\n const fragment = anchors();\n whenInto(fragment.firstChild, fragment.lastChild, getter, arms);\n return fragment;\n}\n\n/** Variant dispatch between two existing anchors. */\nexport function whenInto(start, end, getter, arms) {\n // The arm\'s payload lives in a signal, and each field is handed to the\n // arm as a getter. So a changed payload flows to the bindings that read\n // it, and only a changed TAG rebuilds the subtree.\n //\n // The earlier implementation was `dynamic(derived(...))`, which rebuilt\n // on any change. Since every list in the language sits inside a `when`\n // arm, one changed cell tore down and recreated the entire list.\n const [fields, setFields] = signal([]);\n let currentTag = null;\n let disposeArm = null;\n\n onCleanup(() => disposeArm && disposeArm());\n\n effect(() => {\n const value = read(getter);\n setFields(value.fields ?? []);\n if (value.tag === currentTag) return;\n\n const arm = arms[value.tag];\n // $dev\n // Development only (#140). \u{a7}14G.1.6 makes every arm present, so this\n // states a compiler invariant rather than handling a case: a release\n // build calling `arm(...)` on `undefined` throws too, and #139\'s\n // containment reports it. What is lost is the sentence, not the\n // failure.\n if (arm === undefined) {\n throw new Error(\n `No arm for variant ${JSON.stringify(value.tag)}. The compiler should have rejected this.`\n );\n }\n // $end\n currentTag = value.tag;\n // The outgoing arm\'s bindings read this `when`\'s own `fields` signal,\n // which keeps being written, so leaving them subscribed would keep\n // running them against detached nodes for the life of the page.\n if (disposeArm !== null) disposeArm();\n clearBetween(start, end);\n const binders = (value.fields ?? []).map((_, index) => () => fields()[index]);\n const [rendered, dispose] = owned(() => arm(...binders));\n disposeArm = dispose;\n end.parentNode.insertBefore(rendered, end);\n });\n}\n\n/**\n * Conditional rendering between two existing anchors \u{2014} `if cond`.\n *\n * Not a `whenInto` with two arms: there is no variant here and no `choice`\n * the program declared, so there is no tag to switch on. The branch is\n * rebuilt only when the condition\'s *truth* changes, for exactly the reason\n * `whenInto` rebuilds only on a tag change \u{2014} a condition that reads a\n * signal which keeps changing without crossing the boundary would otherwise\n * tear down and recreate the whole subtree on every write.\n *\n * `otherwise` may be null, which renders nothing.\n */\nexport function ifInto(start, end, condition, render, otherwise) {\n // `null` rather than a boolean, so the first run always renders: neither\n // branch has been built yet, and `false` would look like \"already\n // showing the else\".\n let current = null;\n let disposeBranch = null;\n\n onCleanup(() => disposeBranch && disposeBranch());\n\n effect(() => {\n const taken = Boolean(read(condition));\n if (taken === current) return;\n current = taken;\n\n // The outgoing branch\'s bindings read signals that keep being written,\n // so leaving them subscribed would keep running them against detached\n // nodes for the life of the page.\n if (disposeBranch !== null) disposeBranch();\n disposeBranch = null;\n clearBetween(start, end);\n\n const branch = taken ? render : otherwise;\n if (branch === null || branch === undefined) return;\n const [rendered, dispose] = owned(() => branch());\n disposeBranch = dispose;\n end.parentNode.insertBefore(rendered, end);\n });\n}\n\n/** Construct a variant value. */\nexport function variant(tag, ...fields) {\n return { tag, fields };\n}\n\n/** Mount a rendered tree into a container, replacing its contents. */\nexport function mount(node, container) {\n container.replaceChildren(node);\n return node;\n}\n\nfunction clearBetween(start, end) {\n let node = start.nextSibling;\n while (node && node !== end) {\n const next = node.nextSibling;\n node.remove();\n node = next;\n }\n}\n";Expand description
DOM rendering. Requires a document, so it is embedded for shipping rather than for evaluation here.