kerfjs 0.16.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/README.md +29 -7
- package/ai/cursorrules +15 -1
- package/ai/manifest.json +5 -5
- package/ai/skill.md +16 -1
- package/dist/array-signal.js +1 -1
- package/dist/{chunk-N4KF3GD2.js → chunk-4E26PO2C.js} +7 -4
- package/dist/chunk-4E26PO2C.js.map +1 -0
- package/dist/{chunk-4TJEO4AO.js → chunk-7SKIIA5P.js} +3 -3
- package/dist/{chunk-4TJEO4AO.js.map → chunk-7SKIIA5P.js.map} +1 -1
- package/dist/chunk-QNYOMGI4.js +543 -0
- package/dist/chunk-QNYOMGI4.js.map +1 -0
- package/dist/index.d.ts +22 -22
- package/dist/index.js +125 -37
- package/dist/index.js.map +1 -1
- package/dist/jsx-runtime.d.ts +89 -4
- package/dist/jsx-runtime.js +2 -1
- package/dist/testing.js +2 -2
- package/llms.txt +1 -1
- package/package.json +3 -2
- package/dist/chunk-4VT4YZOO.js +0 -297
- package/dist/chunk-4VT4YZOO.js.map +0 -1
- package/dist/chunk-N4KF3GD2.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/urlScreen.ts","../src/bindings.ts","../src/segment.ts","../src/utils/escapeHtml.ts","../src/utils/jsx-attr-aliases.ts","../src/jsx-runtime.ts"],"names":["SAFE_HTML_BRAND"],"mappings":"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","file":"chunk-QNYOMGI4.js","sourcesContent":["/**\n * URL-attribute screening, shared by the JSX runtime's static-attribute\n * serializer (`renderAttr` in `jsx-runtime.ts`) and the fine-grained binding's\n * live-attribute writer (`setBoundAttr` in `bindings.ts`) — KF-297.\n *\n * A stored-XSS payload like `href={userInput}` with `javascript:alert(1)` is\n * dropped (attribute omitted from the string / removed from the live node)\n * rather than written. `raw(...)` / `SafeHtml` values bypass this — the\n * documented opt-out for legitimate cases (bookmarklet builders, values\n * sanitized by a separate trust layer).\n *\n * The screen is a scheme allow/deny check, not a full URL sanitizer, but it is\n * hardened against the obfuscations a real browser sees through:\n *\n * - **Control-character / whitespace obfuscation of the scheme.** Browsers\n * strip leading C0 controls + spaces and remove TAB/LF/CR from anywhere in\n * a URL before resolving the scheme, so `java\\tscript:`, `\\x01javascript:`,\n * and `javascript\\x00:` all resolve to `javascript:`. We normalize the same\n * way (strip every C0 control 0x00-0x1F + DEL, trim leading whitespace)\n * BEFORE extracting the scheme, so none of those slip past.\n *\n * - **`data:` is subtype-specific.** `data:image/png` in `<img src>` is safe\n * and common; `data:text/html` / `data:image/svg+xml` / `data:*xml` load as\n * a document and run script (SVG `<script>`, HTML/XHTML `<script>`). We\n * allowlist the inert media families (raster images, fonts, audio, video,\n * plain text/css) and drop every other `data:` — so a novel dangerous\n * subtype fails closed.\n */\n\n/**\n * URL-bearing HTML/SVG attributes whose plain-string values are screened.\n * `data` covers `<object data>` (loads its target as a document, so\n * `data:text/html` there executes). Image-context URL attributes\n * (`srcset` / `poster` / `background` / `ping`) are intentionally NOT screened:\n * a `javascript:` / `data:` value in them does not execute as script in modern\n * browsers, so screening would add false positives without closing a real sink.\n */\nconst URL_ATTRS = new Set(['href', 'src', 'xlink:href', 'formaction', 'action', 'data']);\n\n/** Schemes that execute script when a browser resolves them as a URL. */\nconst DANGEROUS_SCHEMES = new Set(['javascript', 'vbscript']);\n\n/** Matches every C0 control character (0x00-0x1F) and DEL (0x7F). */\n// eslint-disable-next-line no-control-regex -- deliberately matching C0 + DEL.\nconst CONTROL_CHARS = /[\\u0000-\\u001F\\u007F]/g;\n\n/**\n * Normalize a URL the way a browser does before scheme resolution: remove every\n * C0 control (0x00-0x1F) and DEL (0x7F) from anywhere — no legitimate URL\n * carries a raw control char (they must be percent-encoded), so this collapses\n * `java\\tscript:` / `\\x01javascript:` / `javascript\\x00:` back to their real\n * scheme — then trim leading whitespace. Internal spaces are left intact: a\n * space is not a valid scheme character, so a browser treats `java script:` as\n * a relative URL, not the `javascript:` scheme, and we must not \"repair\" it\n * into a false positive.\n */\nfunction normalizeUrl(value: string): string {\n return value.replace(CONTROL_CHARS, '').replace(/^\\s+/, '');\n}\n\n/** The lowercased URL scheme (text before the first `:`), or null if none. */\nfunction extractScheme(value: string): string | null {\n const m = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(normalizeUrl(value));\n return m ? m[1].toLowerCase() : null;\n}\n\n/**\n * True if a `data:` URL is a script-executing document type. Allowlists the\n * inert media families and fails closed on everything else — so `text/html`,\n * `image/svg+xml`, XHTML/XML, and any unknown/novel subtype are all treated as\n * dangerous, while `data:image/png` / fonts / audio / video / plain text pass.\n */\nfunction isDangerousDataUrl(value: string): boolean {\n const media = /^data:([^;,]*)/.exec(normalizeUrl(value).toLowerCase())?.[1].trim() ?? '';\n if (media === '' || media === 'text/plain' || media === 'text/css') return false;\n if (media === 'image/svg+xml') return true; // SVG can carry <script>\n if (media.startsWith('image/')) return false;\n if (media.startsWith('font/') || media.startsWith('application/font')) return false;\n if (media.startsWith('audio/') || media.startsWith('video/')) return false;\n return true; // text/html, application/xhtml+xml, *xml, unknown → block\n}\n\n/** True if `value` is a dangerous URL for the URL-bearing attribute `name`. */\nexport function isDangerousUrlValue(name: string, value: string): boolean {\n if (!URL_ATTRS.has(name)) return false;\n const scheme = extractScheme(value);\n if (scheme === null) return false;\n if (DANGEROUS_SCHEMES.has(scheme)) return true;\n return scheme === 'data' && isDangerousDataUrl(value);\n}\n\n/**\n * The shared warning body for a dropped dangerous URL. Each caller prefixes\n * its own context (`JSX:` for the string serializer, `kerf binding:` for the\n * live-attribute writer).\n */\nexport function dangerousUrlWarning(name: string, value: string): string {\n return `dropped dangerous URL value for ${name}=${JSON.stringify(value.slice(0, 80))}. `\n + 'kerf blocks javascript:, vbscript:, and script-executing data: URLs '\n + '(text/html, image/svg+xml, xml) in href/src/data/formaction/action/xlink:href by default. '\n + 'Wrap in raw() if this is intentional (e.g. bookmarklets), or sanitize upstream.';\n}\n","/**\n * Fine-grained signal bindings (KF-294 spike).\n *\n * When a `Signal` is interpolated straight into a JSX attribute\n * (`class={sig}`) or a text child (`{sig}`) INSIDE a `mount()` render, the\n * JSX runtime stops stringifying it. Instead it emits a marker into the HTML\n * string and records a binding here; after the string is parsed to DOM, a\n * wiring pass attaches one `effect` per binding that writes straight to the\n * live node. A later change to that signal then updates the node WITHOUT\n * re-running the render function or walking the list reconciler.\n *\n * This reuses the \"marker in string, wire up after parse\" mechanism the\n * keyed-list reconciler already uses for `<!--kf-list:{id}-->` markers.\n *\n * TWO SCOPES of binding, with disjoint marker namespaces so their wiring\n * passes never collide:\n *\n * - GLOBAL holes — signals in the static surrounds (outside any `each()`\n * row). Markers: `data-kfb` attribute / `<!--kfb:{id}-->` comment. Ids come\n * from the mount render context's counter; wired by `wireBindings()` over\n * the whole mount root; disposed/re-wired by `mount()` each render.\n *\n * - ROW holes — signals inside an `each()` row. Markers: `data-kfbrow`\n * attribute / `<!--kfbr:{id}-->` comment. Ids are row-LOCAL (reset per row)\n * so they stay stable and collision-free as rows are inserted/removed/moved.\n * Captured per row by `captureRowBindings()`, carried on the list segment\n * item, and wired/disposed by the list reconciler at each row node's\n * create/remove — so a binding's lifetime tracks its row node's lifetime,\n * and row reorders (which reuse the same node) are free.\n *\n * Outside a `mount()` render (SSR / `SafeHtml.toString()`) neither scope is\n * active: the runtime snapshots `signal.value` and emits no markers, so server\n * output is correct and legacy `.toString()` callers are unaffected.\n *\n * Module-level mutable state note: `context` / `rowSink` here are a third\n * sanctioned module-level mutable location (alongside `store.ts:REGISTRY` and\n * `each.ts:context`). They hold the current render's binding sinks and are set\n * / cleared by `mount()` and `each()` around the render calls.\n */\n\nimport { effect, isSignal, type Signal } from './reactive.js';\nimport { dangerousUrlWarning, isDangerousUrlValue } from './utils/urlScreen.js';\n\n// RESERVED NAMESPACE (consumer contract, KF-314). The wiring pass finds these\n// markers by scanning the mounted subtree and matching by id, and resolves an\n// id collision by document order (last/first wins) with no ownership check — so\n// a consumer element that carries `data-kfb`/`data-kfbrow`, or a comment\n// beginning `kfb:`/`kfbr:`/`kf-list:`, can collide with a real binding's id and\n// steal its effect (the update wires to the wrong node). These names are\n// therefore reserved: consumers must not emit them (documented in\n// docs/2-reactivity.md § \"Reserved marker names\"). Kerf's own escaping already\n// prevents a plain text/attribute *value* from forging one; the remaining ways\n// in are a hand-written attribute or a `raw()` payload — i.e. the consumer's\n// own trust boundary. A dev-mode collision warning is a possible future\n// enhancement (fix direction (a) on KF-314) if silent mis-wiring proves easy to\n// hit in practice.\n\n/** Marker attribute for GLOBAL (static-surround) attribute bindings. */\nexport const BIND_ATTR = 'data-kfb';\n/** Comment-marker prefix for a GLOBAL text binding: `<!--kfb:{id}-->`. */\nexport const TEXT_MARKER_PREFIX = 'kfb:';\n/** Marker attribute for ROW (each()-scoped) attribute bindings. */\nexport const BIND_ATTR_ROW = 'data-kfbrow';\n/** Comment-marker prefix for a ROW text binding: `<!--kfbr:{id}-->`. */\nexport const ROW_TEXT_PREFIX = 'kfbr:';\n\ninterface AttrBinding {\n kind: 'attr';\n id: string;\n attr: string;\n signal: Signal<unknown>;\n}\ninterface TextBinding {\n kind: 'text';\n id: string;\n signal: Signal<unknown>;\n}\nexport type Binding = AttrBinding | TextBinding;\n\n/**\n * Per-render GLOBAL binding sink. `counter` assigns ids by registration order\n * (stable across renders since the JSX render is deterministic); `list`\n * accumulates this render's global (static-surround) holes.\n */\nexport interface BindingContext {\n counter: number;\n list: Binding[];\n}\n\nlet context: BindingContext | null = null;\n\n// Active row-capture sink + its row-local id counter. Non-null only while\n// `each()` renders a single row through `captureRowBindings()`.\nlet rowSink: Binding[] | null = null;\nlet rowCounter = 0;\n\nconst NO_DISPOSERS: Array<() => void> = [];\n\nexport function newBindingContext(): BindingContext {\n return { counter: 0, list: [] };\n}\n\nexport function _setBindingContext(c: BindingContext | null): void {\n context = c;\n}\n\n/**\n * Render one `each()` row while capturing the row-scoped bindings it emits.\n * Row ids are local (reset to 0) so they're stable + collision-free per row.\n * Returns the row HTML and its captured bindings (empty when the row has no\n * signal holes). Restores the prior capture state (supports nested `each()`).\n */\nexport function captureRowBindings(renderRow: () => string): { html: string; bindings: Binding[] } {\n const prevSink = rowSink;\n const prevCounter = rowCounter;\n rowSink = [];\n rowCounter = 0;\n try {\n const html = renderRow();\n return { html, bindings: rowSink };\n } finally {\n rowSink = prevSink;\n rowCounter = prevCounter;\n }\n}\n\n/**\n * Register a signal attribute. Routes to the row sink when a row capture is\n * active, else the global context. Returns the marker id,\n * or null to snapshot. The marker attribute NAME (`data-kfb` vs `data-kfbrow`)\n * is fetched once per element via `bindMarkerAttr()` — all of an element's\n * signal attrs share the same scope — so this avoids a per-attr object alloc.\n */\nexport function bindAttr(attr: string, signal: Signal<unknown>): string | null {\n if (rowSink !== null) {\n const id = `a${rowCounter++}`;\n rowSink.push({ kind: 'attr', id, attr, signal });\n return id;\n }\n if (context !== null) {\n const id = `a${context.counter++}`;\n context.list.push({ kind: 'attr', id, attr, signal });\n return id;\n }\n return null;\n}\n\n/**\n * The marker attribute name for the currently-active binding scope — row holes\n * use `data-kfbrow`, global holes `data-kfb`. Called once per element by the\n * JSX runtime after collecting the element's signal-attr ids.\n */\nexport function bindMarkerAttr(): string {\n return rowSink !== null ? BIND_ATTR_ROW : BIND_ATTR;\n}\n\n/**\n * Register a signal text hole. Returns the comment-marker HTML to emit, or\n * null to snapshot. Same routing as `bindAttr()`.\n */\nexport function bindText(signal: Signal<unknown>): string | null {\n if (rowSink !== null) {\n const id = `t${rowCounter++}`;\n rowSink.push({ kind: 'text', id, signal });\n return `<!--${ROW_TEXT_PREFIX}${id}-->`;\n }\n if (context !== null) {\n const id = `t${context.counter++}`;\n context.list.push({ kind: 'text', id, signal });\n return `<!--${TEXT_MARKER_PREFIX}${id}-->`;\n }\n return null;\n}\n\n/**\n * Wire this render's GLOBAL bindings against the freshly-built live DOM.\n * Disposes `prevDisposers` first (a surrounds-changed morph strips bound attrs\n * and removes inserted text nodes, so we re-establish them), then attaches one\n * effect per hole. Zero-cost when there are no global holes.\n */\nexport function wireBindings(\n rootEl: Element,\n ctx: BindingContext,\n prevDisposers: Array<() => void>,\n): Array<() => void> {\n for (const d of prevDisposers) d();\n if (ctx.list.length === 0) return NO_DISPOSERS;\n const disposers: Array<() => void> = [];\n wireInto(rootEl, BIND_ATTR, TEXT_MARKER_PREFIX, ctx.list, disposers);\n return disposers;\n}\n\n/**\n * Wire one row's ROW-scoped bindings against its freshly-built row node\n * (including the node itself, since a row's top-level element may carry the\n * marker, e.g. `<tr data-kfbrow>`). Returns the row's disposers for the\n * reconciler to store on the bound item and call when the row node is removed.\n */\nexport function wireRowBindings(rowNode: Element, bindings: Binding[]): Array<() => void> {\n // Callers (snapshot buildFreshNodes, mount first-render inline) only invoke\n // this for rows with at least one binding, so no empty-guard is needed.\n //\n // Hot path: most row holes are an attribute on the row's ROOT element (e.g.\n // `<tr class={sig}>`). For those we resolve the node with a single\n // `getAttribute` + allocation-free id membership check — no `querySelectorAll`,\n // no `collectComments` walk, no Map. The descendant-attr index and the\n // text-comment index are built lazily, only if a hole actually needs them.\n const disposers: Array<() => void> = new Array(bindings.length);\n const rootIds = rowNode.getAttribute(BIND_ATTR_ROW);\n // For the common single-root-binding row, `rootIds === b.id` resolves the\n // node with zero allocation. Only a row with 2+ root bindings builds the Set.\n let rootIdSet: Set<string> | null = null;\n let descIndex: Map<string, Element> | null = null;\n let textMarkers: Map<string, Comment> | null = null;\n\n for (let i = 0; i < bindings.length; i++) {\n const b = bindings[i];\n if (b.kind === 'attr') {\n let onRoot = false;\n if (rootIds !== null) {\n if (rootIds === b.id) {\n onRoot = true;\n } else if (rootIds.indexOf(',') !== -1) {\n rootIdSet ??= new Set(rootIds.split(','));\n onRoot = rootIdSet.has(b.id);\n }\n }\n let el: Element | undefined;\n if (onRoot) {\n el = rowNode;\n } else {\n descIndex ??= indexAttrEls(rowNode, BIND_ATTR_ROW);\n el = descIndex.get(b.id);\n }\n /* c8 ignore next -- defensive: a registered binding always emits its marker into the row. */\n if (el === undefined) continue;\n disposers[i] = attachAttrEffect(el, b.attr, b.signal);\n } else {\n if (textMarkers === null) {\n textMarkers = new Map();\n collectComments(rowNode, ROW_TEXT_PREFIX, textMarkers);\n }\n const marker = textMarkers.get(b.id);\n /* c8 ignore next -- defensive: same invariant as the attr branch. */\n if (marker === undefined) continue;\n disposers[i] = attachTextEffect(marker, b.signal);\n }\n }\n return disposers;\n}\n\n/** Dispose a row's binding effects (called when its node leaves the DOM). */\nexport function disposeRowBindings(disposers: Array<() => void> | undefined): void {\n if (disposers === undefined) return;\n for (const d of disposers) d();\n}\n\n/**\n * Shared wiring core. Indexes marked elements + comment markers under `scope`\n * by binding id, then attaches an effect per binding. `includeRoot` also\n * indexes `scope` itself (row roots carry their own marker).\n */\nfunction wireInto(\n scope: Element,\n attrName: string,\n textPrefix: string,\n bindings: Binding[],\n disposers: Array<() => void>,\n): void {\n const attrEls = indexAttrEls(scope, attrName);\n const textMarkers = new Map<string, Comment>();\n collectComments(scope, textPrefix, textMarkers);\n\n for (const b of bindings) {\n if (b.kind === 'attr') {\n const el = attrEls.get(b.id);\n /* c8 ignore next -- defensive: a registered binding always emits its marker into `scope`. */\n if (el === undefined) continue;\n disposers.push(attachAttrEffect(el, b.attr, b.signal));\n } else {\n const marker = textMarkers.get(b.id);\n /* c8 ignore next -- defensive: same invariant as the attr branch. */\n if (marker === undefined) continue;\n disposers.push(attachTextEffect(marker, b.signal));\n }\n }\n}\n\n/**\n * Index every DESCENDANT element under `scope` carrying `attrName` by binding\n * id. Callers that also need the scope root itself check it separately (row\n * roots via the `rootIds` fast path; global holes are never on the mount root).\n */\nfunction indexAttrEls(scope: Element, attrName: string): Map<string, Element> {\n const map = new Map<string, Element>();\n for (const el of scope.querySelectorAll(`[${attrName}]`)) {\n for (const id of (el.getAttribute(attrName) as string).split(',')) map.set(id, el);\n }\n return map;\n}\n\n/** Attach a fine-grained attribute effect; returns its disposer. */\nfunction attachAttrEffect(el: Element, attr: string, signal: Signal<unknown>): () => void {\n return effect(() => setBoundAttr(el, attr, signal.value));\n}\n\n/** Insert a live text node after `marker` and bind it to `signal`; returns disposer. */\nfunction attachTextEffect(marker: Comment, signal: Signal<unknown>): () => void {\n const text = (marker.ownerDocument as Document).createTextNode('');\n (marker.parentNode as Node).insertBefore(text, marker.nextSibling);\n return effect(() => { text.data = coerceText(signal.value); });\n}\n\n/**\n * Apply a bound value to a live attribute, mirroring the JSX runtime's\n * `renderAttr` rules: boolean/nullish toggling, `SafeHtml` (raw()) unwrapping,\n * and the KF-297 URL-screening that drops `javascript:` / `vbscript:` /\n * `data:text/html` on `href`/`src`/`formaction`/`action`/`xlink:href`.\n *\n * Unlike `renderAttr` (which builds an HTML string, so escapes with\n * `escapeAttr`), this writes the RAW value via `setAttribute` — the DOM stores\n * attribute values verbatim, so no HTML-escaping is applied here.\n *\n * The attribute NAME is trusted here: `on*` and malformed names are rejected at\n * binding-registration time in the JSX runtime's `jsx()` signal branch (shared\n * `assertEmittableAttrName`, KF-322), so `setAttribute('onclick', …)` — which\n * would install a live inline handler — can never reach this writer.\n */\nfunction setBoundAttr(el: Element, name: string, value: unknown): void {\n if (value == null || value === false) {\n el.removeAttribute(name);\n return;\n }\n if (value === true) {\n el.setAttribute(name, '');\n return;\n }\n // SafeHtml (raw()) is the documented opt-out — bypasses URL screening and is\n // written verbatim, matching renderAttr's SafeHtml branch.\n if (isSafeHtmlValue(value)) {\n el.setAttribute(name, value.__html);\n return;\n }\n const str = String(value);\n if (isDangerousUrlValue(name, str)) {\n console.warn(`kerf binding: ${dangerousUrlWarning(name, str)}`);\n el.removeAttribute(name);\n return;\n }\n el.setAttribute(name, str);\n}\n\n// Cross-bundle SafeHtml brand check (mirrors jsx-runtime's `isSafeHtml`).\n// Duplicated here — rather than importing `isSafeHtml` from jsx-runtime — to\n// keep the bindings ← jsx-runtime dependency acyclic. `Symbol.for` makes it\n// recognize SafeHtml instances from any copy of the module.\nconst SAFE_HTML_BRAND = Symbol.for('kerfjs.SafeHtml');\nfunction isSafeHtmlValue(v: unknown): v is { __html: string } {\n return typeof v === 'object' && v !== null\n && (v as Record<symbol, unknown>)[SAFE_HTML_BRAND] === true;\n}\n\n/** Coerce a bound text value: nullish + boolean render nothing (React-style). */\nfunction coerceText(value: unknown): string {\n if (value == null || typeof value === 'boolean') return '';\n return String(value);\n}\n\n/** Collect comment markers with the given prefix under `node`, keyed by id. */\nfunction collectComments(node: Node, prefix: string, out: Map<string, Comment>): void {\n for (let c: Node | null = node.firstChild; c !== null; c = c.nextSibling) {\n if (c.nodeType === Node.COMMENT_NODE) {\n const data = (c as Comment).data;\n if (data.startsWith(prefix)) out.set(data.slice(prefix.length), c as Comment);\n } else if (c.nodeType === Node.ELEMENT_NODE) {\n collectComments(c, prefix, out);\n }\n }\n}\n\n// Re-exported so the JSX runtime imports signal-detection + binding\n// registration from one place.\nexport { isSignal };\n","/**\n * `Segment` — kerf's structured render output.\n *\n * The JSX runtime emits a `SafeHtml` wrapping a `Segment`. Most renders\n * produce a single static segment (just an HTML string), which behaves\n * exactly like a string for backward compatibility. When the tree\n * contains a list (`each()`) or a parent whose children include a list,\n * the runtime emits a structured segment that `mount()` can dispatch\n * on — running its native keyed reconciler for the list parts and\n * leaving the static surrounds to the general-purpose diff.\n *\n * Why have a structured form at all: the perf bottleneck for huge\n * keyed lists isn't the per-row JSX work (which `each()` already\n * memoizes). It's that flattening every render's whole tree to one\n * big HTML string forces a full `innerHTML` parse and a tree walk\n * over rows we know are unchanged. The segment shape lets mount()\n * skip both for the list parts.\n */\n\nimport type { Binding } from './bindings.js';\n\nexport type Segment = StaticSegment | ListSegment | MixedSegment;\n\nexport interface StaticSegment {\n kind: 'static';\n html: string;\n}\n\nexport interface ListItem {\n /**\n * The row's object identity. Used by the reconciler to match new items\n * against live DOM nodes across renders. Unchanged ref → reuse the\n * existing live node; replaced ref → build a fresh node.\n */\n ref: object;\n /**\n * KF-294: the row's fine-grained binding specs (signals in row attrs/text).\n * Undefined for granular-path rows (which snapshot in this spike). The\n * snapshot reconciler wires these to the fresh row node and disposes them\n * when the row is removed.\n */\n bindings?: Binding[];\n /**\n * Optional cache-invalidation key that captures external state affecting\n * this row's render (e.g. selection class). Different cacheKey on the\n * same `ref` triggers a cache miss for that row. `undefined` when the\n * user didn't pass a `key` callback to `each()`.\n */\n cacheKey: unknown;\n html: string;\n}\n\nexport interface ListSegment {\n kind: 'list';\n id: string;\n items: ListItem[];\n /**\n * Optional granular patches (KF-92). When present, the list reconciler\n * applies these directly to the existing binding instead of doing a\n * full classify+reconcile pass. Emitted by `each()` when bound to an\n * `arraySignal`. Mutually exclusive with the `items` snapshot in the\n * sense that the snapshot is treated as informational/fall-back when\n * patches are present.\n */\n patches?: ArrayPatchInternal[];\n}\n\n/**\n * Internal patch shape used inside list segments. Mirrors `ArrayPatch<T>`\n * from `array-signal.ts` but typed against `object` so the segment layer\n * doesn't need to be generic. `update` / `insert` patches carry the row's\n * pre-rendered HTML — `each()` renders them at JSX-evaluation time inside a\n * try/catch so a throwing render falls back to the snapshot path (KF-99)\n * instead of leaving the signal and DOM divergent.\n */\nexport type ArrayPatchInternal =\n | { type: 'update'; index: number; item: object; html: string; bindings?: Binding[] }\n | { type: 'insert'; index: number; item: object; html: string; bindings?: Binding[] }\n | { type: 'remove'; index: number }\n | { type: 'move'; from: number; to: number }\n | { type: 'replace'; items: readonly object[] };\n\nexport interface MixedSegment {\n kind: 'mixed';\n parts: Segment[];\n}\n\n/**\n * Flatten a segment to a complete HTML string. Used for first render\n * (bulk innerHTML), for SSR-style consumption via `toString()`, and\n * for diagnostics.\n *\n * If `withMarkers` is set, list segments are wrapped in\n * `<!--kf-list:{id}-->` comments so the post-parse walk can find each\n * list's live parent. Plain (non-marker) flatten is what JSX consumers\n * see when they call `.toString()` on the SafeHtml.\n */\nexport function flatten(segment: Segment, withMarkers: boolean): string {\n if (segment.kind === 'static') return segment.html;\n if (segment.kind === 'list') {\n const items = segment.items.map((i) => i.html).join('');\n return withMarkers ? `<!--kf-list:${segment.id}-->${items}` : items;\n }\n return segment.parts.map((p) => flatten(p, withMarkers)).join('');\n}\n\n/**\n * Variant of `flatten` for the static-only diff path on subsequent\n * renders. Lists are reduced to a single marker comment with no items\n * inside — the actual list children stay in the live DOM and are\n * reconciled separately. Keeping list items out of this string is\n * what makes the morph cheap on huge lists where most rows are\n * unchanged.\n */\nexport function flattenWithoutListItems(segment: Segment): string {\n if (segment.kind === 'static') return segment.html;\n if (segment.kind === 'list') return `<!--kf-list:${segment.id}-->`;\n return segment.parts.map(flattenWithoutListItems).join('');\n}\n\n/** Collect every `ListSegment` in the tree, keyed by its id. */\nexport function collectLists(\n segment: Segment,\n out: Map<string, ListSegment> = new Map(),\n): Map<string, ListSegment> {\n if (segment.kind === 'list') out.set(segment.id, segment);\n else if (segment.kind === 'mixed') {\n for (const part of segment.parts) collectLists(part, out);\n }\n return out;\n}\n\n/**\n * Combine a list of child segments into the smallest equivalent\n * representation: collapses adjacent statics into one static, returns\n * a single static if everything is static, otherwise a mixed segment\n * with statics coalesced.\n */\nexport function mergeChildSegments(parts: Segment[]): Segment {\n if (parts.length === 0) return { kind: 'static', html: '' };\n if (parts.every((p) => p.kind === 'static')) {\n return {\n kind: 'static',\n html: parts.map((p) => (p as StaticSegment).html).join(''),\n };\n }\n const merged: Segment[] = [];\n let coalesced = '';\n for (const p of parts) {\n if (p.kind === 'static') {\n coalesced += p.html;\n } else {\n if (coalesced !== '') {\n merged.push({ kind: 'static', html: coalesced });\n coalesced = '';\n }\n merged.push(p);\n }\n }\n if (coalesced !== '') merged.push({ kind: 'static', html: coalesced });\n return { kind: 'mixed', parts: merged };\n}\n\n/**\n * Wrap a child segment with surrounding open/close tags from the\n * parent JSX element. Used by the JSX runtime when constructing\n * `_jsx(tag, ...)` output.\n */\nexport function wrapWithTags(child: Segment, openTag: string, closeTag: string): Segment {\n if (child.kind === 'static') {\n return { kind: 'static', html: openTag + child.html + closeTag };\n }\n if (child.kind === 'mixed') {\n return {\n kind: 'mixed',\n parts: [\n { kind: 'static', html: openTag },\n ...child.parts,\n { kind: 'static', html: closeTag },\n ],\n };\n }\n return {\n kind: 'mixed',\n parts: [\n { kind: 'static', html: openTag },\n child,\n { kind: 'static', html: closeTag },\n ],\n };\n}\n","/**\n * HTML / attribute escaping for the JSX runtime. Identical to the helpers\n * used in any reasonable HTML emitter — included here so kerf has no extra\n * runtime dependencies beyond `@preact/signals-core`.\n */\n\nexport function escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n\nexport function escapeAttr(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n}\n","/**\n * JSX → HTML / SVG attribute name aliases.\n *\n * The JSX runtime translates camelCase attributes (React convention) to\n * the kebab-case / colon-form names the browser actually wants. Anything\n * not in this map is passed through verbatim — `data-*`, `aria-*`, and\n * any custom attribute work without ceremony.\n *\n * Lives in its own module so `src/jsx-runtime.ts` can stay under the\n * 200-LOC project guideline; the bulk of `jsx-runtime.ts` was this table.\n */\n\nexport const ATTR_ALIASES: Record<string, string> = {\n // HTML attributes\n className: 'class',\n htmlFor: 'for',\n httpEquiv: 'http-equiv',\n acceptCharset: 'accept-charset',\n accessKey: 'accesskey',\n autoCapitalize: 'autocapitalize',\n autoComplete: 'autocomplete',\n autoFocus: 'autofocus',\n autoPlay: 'autoplay',\n colSpan: 'colspan',\n contentEditable: 'contenteditable',\n crossOrigin: 'crossorigin',\n dateTime: 'datetime',\n defaultChecked: 'checked',\n defaultValue: 'value',\n encType: 'enctype',\n formAction: 'formaction',\n formEncType: 'formenctype',\n formMethod: 'formmethod',\n formNoValidate: 'formnovalidate',\n formTarget: 'formtarget',\n hrefLang: 'hreflang',\n inputMode: 'inputmode',\n maxLength: 'maxlength',\n minLength: 'minlength',\n noModule: 'nomodule',\n noValidate: 'novalidate',\n readOnly: 'readonly',\n referrerPolicy: 'referrerpolicy',\n rowSpan: 'rowspan',\n spellCheck: 'spellcheck',\n srcDoc: 'srcdoc',\n srcLang: 'srclang',\n srcSet: 'srcset',\n tabIndex: 'tabindex',\n useMap: 'usemap',\n\n // SVG presentation attributes (camelCase → kebab-case)\n strokeWidth: 'stroke-width',\n strokeLinecap: 'stroke-linecap',\n strokeLinejoin: 'stroke-linejoin',\n strokeDasharray: 'stroke-dasharray',\n strokeDashoffset: 'stroke-dashoffset',\n strokeMiterlimit: 'stroke-miterlimit',\n strokeOpacity: 'stroke-opacity',\n fillOpacity: 'fill-opacity',\n fillRule: 'fill-rule',\n clipPath: 'clip-path',\n clipRule: 'clip-rule',\n colorInterpolation: 'color-interpolation',\n colorInterpolationFilters: 'color-interpolation-filters',\n floodColor: 'flood-color',\n floodOpacity: 'flood-opacity',\n lightingColor: 'lighting-color',\n stopColor: 'stop-color',\n stopOpacity: 'stop-opacity',\n shapeRendering: 'shape-rendering',\n imageRendering: 'image-rendering',\n textRendering: 'text-rendering',\n pointerEvents: 'pointer-events',\n vectorEffect: 'vector-effect',\n paintOrder: 'paint-order',\n\n // SVG text/font attributes\n fontFamily: 'font-family',\n fontSize: 'font-size',\n fontStyle: 'font-style',\n fontVariant: 'font-variant',\n fontWeight: 'font-weight',\n fontStretch: 'font-stretch',\n textAnchor: 'text-anchor',\n textDecoration: 'text-decoration',\n dominantBaseline: 'dominant-baseline',\n alignmentBaseline: 'alignment-baseline',\n baselineShift: 'baseline-shift',\n letterSpacing: 'letter-spacing',\n wordSpacing: 'word-spacing',\n writingMode: 'writing-mode',\n\n // SVG marker attributes\n markerStart: 'marker-start',\n markerMid: 'marker-mid',\n markerEnd: 'marker-end',\n\n // SVG xlink (legacy but still used)\n xlinkHref: 'xlink:href',\n xlinkShow: 'xlink:show',\n xlinkActuate: 'xlink:actuate',\n xlinkType: 'xlink:type',\n xlinkRole: 'xlink:role',\n xlinkTitle: 'xlink:title',\n xlinkArcrole: 'xlink:arcrole',\n xmlBase: 'xml:base',\n xmlLang: 'xml:lang',\n xmlSpace: 'xml:space',\n xmlnsXlink: 'xmlns:xlink',\n};\n","/**\n * kerf JSX runtime.\n *\n * JSX renders to `SafeHtml`, which wraps both:\n * - `__html`: the flattened HTML string (what `toString()` returns; what\n * legacy/SSR consumers care about)\n * - `__segment`: a structured representation that distinguishes \"static\n * html\", \"keyed list\", and \"mixed\" content.\n *\n * Most renders are pure-static and the segment is just `{kind:'static',html}`.\n * When the tree contains a list (via `each()`) or a parent whose children\n * include a non-static segment, the runtime threads that structure up so\n * `mount()` can dispatch on it — running its native keyed reconciler for\n * the list parts and leaving the static surrounds to the general-purpose\n * diff.\n *\n * Configure in your `tsconfig.json`:\n *\n * \"jsx\": \"react-jsx\",\n * \"jsxImportSource\": \"kerfjs\"\n *\n * Then write JSX as you normally would — kerf provides the `jsx` /\n * `jsxs` / `jsxDEV` / `Fragment` exports the JSX transform looks for.\n */\n\nimport { bindAttr, bindMarkerAttr, bindText, isSignal } from './bindings.js';\nimport type { KerfBuiltinIntrinsicElements } from './jsx-types.js';\nimport type { ReadonlySignal, Signal } from './reactive.js';\nimport {\n flatten,\n type ListSegment,\n mergeChildSegments,\n type Segment,\n wrapWithTags,\n} from './segment.js';\nimport { escapeAttr, escapeHtml } from './utils/escapeHtml.js';\nimport { ATTR_ALIASES } from './utils/jsx-attr-aliases.js';\nimport { dangerousUrlWarning, isDangerousUrlValue } from './utils/urlScreen.js';\n\n// Cross-realm/cross-bundle brand. Using `Symbol.for` (the global registry)\n// means two `SafeHtml` classes from different module copies still recognize\n// each other. Same approach React uses for `$$typeof: Symbol.for('react.element')`.\n// Without this, `instanceof SafeHtml` fails when the consumer's bundler ends\n// up loading two copies of kerf (separate barrel + jsx-runtime entries,\n// monorepo dedup misses, ESM/CJS interop, etc.).\nconst SAFE_HTML_BRAND = Symbol.for('kerfjs.SafeHtml');\n\nexport class SafeHtml {\n readonly __html: string;\n readonly __segment: Segment;\n // Branded so `isSafeHtml()` recognizes instances from any copy of this module.\n readonly [SAFE_HTML_BRAND] = true as const;\n constructor(input: string | Segment) {\n if (typeof input === 'string') {\n this.__segment = { kind: 'static', html: input };\n this.__html = input;\n } else {\n this.__segment = input;\n this.__html = flatten(input, false);\n }\n }\n toString(): string {\n return this.__html;\n }\n}\n\n/**\n * Type guard for `SafeHtml`. Prefer this over `instanceof SafeHtml` — it works\n * across module copies (e.g. when the consumer's bundler loads kerf's barrel\n * and JSX-runtime entries as independent modules).\n *\n * Security note (KF-321): this is a duck-check on the global `Symbol.for` brand,\n * so same-realm code *can* forge a \"trusted\" value — `{ [SAFE_HTML_BRAND]: true,\n * __html: '<img onerror=…>' }` passes and bypasses escaping + the URL screen.\n * This is intentional and not a vulnerability: minting the brand requires a\n * Symbol key, which no data channel (JSON.parse, form/query/localStorage,\n * structuredClone, JSON-based prototype-pollution) can produce — those all yield\n * string keys. The only way to forge it is to run JS that writes the symbol, and\n * such code can equally `import { raw }`. Forgery therefore grants no capability\n * an attacker with code execution lacks — the same posture as React's\n * `$$typeof: Symbol.for('react.element')`. The global `Symbol.for` (vs a\n * module-private symbol) is a deliberate cross-bundle-recognition tradeoff (see\n * the `SAFE_HTML_BRAND` note above); a private symbol would additionally block\n * same-realm forgery, but only closes a non-threat at the cost of that\n * recognition, so it's kept global by design.\n */\nexport function isSafeHtml(value: unknown): value is SafeHtml {\n return typeof value === 'object'\n && value !== null\n && (value as Record<symbol, unknown>)[SAFE_HTML_BRAND] === true;\n}\n\n/** Inject a pre-escaped HTML string. Use sparingly — caller is responsible for escaping. */\nexport function raw(html: string): SafeHtml {\n return new SafeHtml(html);\n}\n\n/**\n * Internal: build a `SafeHtml` representing a list segment. Used by\n * `each()` so the JSX runtime is the sole owner of `SafeHtml` construction.\n */\nexport function listSafeHtml(id: string, items: ListSegment['items']): SafeHtml {\n return new SafeHtml({ kind: 'list', id, items });\n}\n\n/**\n * Internal: build a `SafeHtml` representing a granular list segment with\n * patches (KF-92). The reconciler applies the patches to the existing\n * binding directly, skipping the per-item iteration that the snapshot\n * `listSafeHtml` requires. `items` is included for fall-through paths\n * (toString during SSR, fall-back when the binding doesn't exist yet).\n *\n * Patch HTML is rendered upstream (in `each()`) inside a try/catch — see\n * KF-99 — so by the time we get here every `update` / `insert` patch\n * already carries a `html` string, and the reconciler does no further\n * row rendering.\n */\nexport function granularListSafeHtml(\n id: string,\n items: ListSegment['items'],\n patches: NonNullable<ListSegment['patches']>,\n): SafeHtml {\n return new SafeHtml({ kind: 'list', id, items, patches });\n}\n\n// KF-294: `ReadonlySignal<unknown>` (covariant) accepts both `signal()` and\n// `computed()` values of any T handed straight into a text hole — the runtime\n// binds them fine-grained instead of stringifying.\ntype Child = SafeHtml | string | number | boolean | null | undefined | ReadonlySignal<unknown>;\ntype Children = Child | Children[];\n\ninterface Props {\n children?: Children;\n [key: string]: unknown;\n}\n\nconst VOID_TAGS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'source', 'track', 'wbr',\n]);\n\n/**\n * Convert a single JSX child into a Segment. Handles SafeHtml passthrough,\n * primitive coercion + escaping, arrays (recursive), and the nullish/false\n * skip cases.\n */\nfunction toSegment(child: Children): Segment {\n if (child == null || typeof child === 'boolean') return { kind: 'static', html: '' };\n // KF-294: a signal handed straight into a text position. Inside a mount\n // render, emit a comment marker and record a binding so the text node\n // updates fine-grained (no render re-run). Outside a mount (SSR/toString),\n // snapshot the current value as escaped text.\n if (isSignal(child)) {\n const marker = bindText(child);\n if (marker !== null) return { kind: 'static', html: marker };\n const v = (child as Signal<unknown>).value;\n return { kind: 'static', html: v == null || typeof v === 'boolean' ? '' : escapeHtml(String(v)) };\n }\n if (isSafeHtml(child)) {\n // Cross-bundle SafeHtml shims (KF-14 case) may have only `__html`.\n return child.__segment ?? { kind: 'static', html: child.__html };\n }\n if (typeof child === 'string') return { kind: 'static', html: escapeHtml(child) };\n if (typeof child === 'number') return { kind: 'static', html: String(child) };\n if (Array.isArray(child)) return mergeChildSegments(child.map(toSegment));\n // Catch the common mistake of passing a DOM element (e.g. the result of\n // toElement(...)) as a JSX child. The runtime renders to HTML strings, so\n // DOM nodes can't be composed — they'd silently serialize to \"\" and their\n // event listeners would be lost. Throw loudly so this can't sneak in.\n const maybeNode = child as unknown;\n if (typeof maybeNode === 'object' && maybeNode !== null\n && ('nodeType' in maybeNode || 'outerHTML' in maybeNode)) {\n throw new Error(\n 'JSX: DOM elements cannot be passed as children (the JSX runtime renders to HTML strings). '\n + 'Build the tree in one JSX expression and use querySelector after toElement() to get element refs.',\n );\n }\n throw new Error(\n `JSX: unsupported child of type ${describeValue(child)}. `\n + 'Children must be SafeHtml, string, number, boolean, null, undefined, or an array of those. '\n + 'Common mistakes: passing a Signal/Store object directly (use signal.value or store.state.value), '\n + 'passing a function (call it first), or passing a Promise (await it before render).',\n );\n}\n\nfunction describeValue(v: unknown): string {\n if (Array.isArray(v)) return 'array';\n if (typeof v === 'object' && v !== null) {\n const ctor = (v as { constructor?: { name?: string } }).constructor?.name;\n return ctor && ctor !== 'Object' ? `object (${ctor})` : 'object';\n }\n return typeof v;\n}\n\n/**\n * A well-formed HTML/SVG attribute name: a letter/underscore/colon, then\n * letters, digits, or `_.:-` — covers `class`, `data-id`, `aria-label`,\n * `xlink:href`, `xmlns:xlink`, `stroke-width`, `viewBox`, etc. A name outside\n * this shape can only come from spreading untrusted KEYS into JSX\n * (`<div {...obj}>`), and is exactly the shape that could carry the whitespace,\n * `>`, `=`, quotes, or control chars needed to break out of the open tag and\n * inject markup — so `renderAttr` rejects it (KF-306).\n */\nconst SAFE_ATTR_NAME = /^[A-Za-z_:][\\w.:-]*$/;\n\n/**\n * Reject an attribute NAME that kerf must never route into HTML or the DOM.\n * Shared by BOTH attribute paths so they enforce one contract (KF-306, KF-322):\n *\n * - the static string path (`renderAttr`) — a name is emitted verbatim into\n * the open tag, and `on*` values become live inline handlers once parsed;\n * - the fine-grained bound path (`jsx()` signal branch → `bindAttr` →\n * `setBoundAttr` → `el.setAttribute(name, …)`) — `setAttribute('onclick', …)`\n * installs a LIVE handler in the browser, an XSS vector that would otherwise\n * bypass the static guard entirely.\n *\n * Two rejections:\n * 1. `on*` (any case) — kerf's model is event delegation, never inline\n * handlers. `isFn` tailors the message: function values get the\n * delegate() migration pointer; string/signal/other values get the\n * XSS-aware message.\n * 2. a malformed name (spread of untrusted keys, `<div {...obj}>`) that could\n * break out of the open tag. The bound path can't actually inject markup\n * this way — `setAttribute` throws `InvalidCharacterError` rather than\n * parsing — but the same guard runs on both paths for one consistent\n * contract. Validated post-alias; every `ATTR_ALIASES` value is itself a\n * valid name, so aliasing is unaffected.\n */\nfunction assertEmittableAttrName(key: string, name: string, isFn: boolean): void {\n if (/^on[a-z]/i.test(name)) {\n if (isFn) {\n throw new Error(\n `JSX: inline event handlers like ${key}={fn} are not supported by kerf's JSX → HTML-string runtime. `\n + 'Use event delegation from the mount root instead:\\n\\n'\n + ' delegate(rootEl, \\'click\\', \\'[data-action=\"...\"]\\', (evt, target) => { ... });\\n'\n + ' <button data-action=\"...\">click</button>\\n\\n'\n + 'See docs/5-event-delegation.md for the tier-1/tier-2/tier-3 model.',\n );\n }\n throw new Error(\n `JSX: event-handler attribute ${JSON.stringify(key)} is not allowed — an `\n + '`on*` attribute (whether a string emitted into HTML or a signal bound via '\n + 'setAttribute) installs a live inline handler, an XSS vector. '\n + 'kerf uses event delegation: delegate(rootEl, \\'click\\', \\'[data-action=\"...\"]\\', handler). '\n + 'See docs/5-event-delegation.md.',\n );\n }\n if (!SAFE_ATTR_NAME.test(name)) {\n throw new Error(\n `JSX: invalid attribute name ${JSON.stringify(key)}. Attribute names must be a `\n + 'letter/underscore/colon followed by letters, digits, or \"_.:-\" (e.g. class, '\n + 'data-id, aria-label, xlink:href). This usually means an untrusted object was '\n + 'spread into JSX ({...obj}) with attacker-controlled keys — validate keys first.',\n );\n }\n}\n\nfunction renderAttr(key: string, value: unknown): string {\n const name = ATTR_ALIASES[key] ?? key;\n if (value == null || value === false) return '';\n assertEmittableAttrName(key, name, typeof value === 'function');\n if (value === true) return ` ${name}`;\n let strValue: string;\n if (isSafeHtml(value)) {\n strValue = value.__html;\n } else if (typeof value === 'number') {\n strValue = String(value);\n } else if (typeof value === 'string') {\n // URL-screening shared with the fine-grained binding writer (KF-297).\n // `SafeHtml` values (raw()) skip this — they hit the branch above.\n if (isDangerousUrlValue(name, value)) {\n console.warn(`JSX: ${dangerousUrlWarning(name, value)}`);\n return '';\n }\n strValue = escapeAttr(value);\n } else {\n throw new Error(\n `JSX: unsupported value for attribute \"${key}\" — got ${describeValue(value)}. `\n + 'Attribute values must be string, number, boolean, null, undefined, or SafeHtml. '\n + 'Did you mean to read .value off a Signal, or stringify the object first?',\n );\n }\n return ` ${name}=\"${strValue}\"`;\n}\n\nexport function jsx(tag: string | ((props: Props) => SafeHtml), props: Props): SafeHtml {\n if (typeof tag === 'function') return tag(props);\n\n const { children, ...attrs } = props;\n let attrStr = '';\n let bindIds: string[] | null = null;\n for (const [k, v] of Object.entries(attrs)) {\n // KF-294: a signal handed straight into an attribute. Inside a mount\n // render, register a binding and mark the element (via `data-kfb` for\n // static-surround holes / `data-kfbrow` for each()-row holes) instead of\n // emitting the attribute — the wiring pass sets it after parse and keeps\n // it fine-grained. Outside a mount, snapshot the current value.\n if (isSignal(v)) {\n const name = ATTR_ALIASES[k] ?? k;\n // Reject `on*` / malformed names on the bound path too (KF-322 completes\n // KF-306). Unconditional: a signal's value can change over time, so we\n // can't gate on the current value — an `onclick={signal}` binding would\n // otherwise install a live inline handler via setAttribute, bypassing the\n // static renderAttr guard entirely. `isFn` is false: a signal is not a\n // function value, so this routes to the XSS-aware message.\n assertEmittableAttrName(k, name, false);\n const id = bindAttr(name, v);\n if (id !== null) {\n (bindIds ??= []).push(id);\n continue;\n }\n attrStr += renderAttr(k, (v as Signal<unknown>).value);\n continue;\n }\n attrStr += renderAttr(k, v);\n }\n // All signal attrs on one element share a scope, so the marker attr name is\n // fetched once (avoids a per-attr object alloc from bindAttr).\n if (bindIds !== null) attrStr += ` ${bindMarkerAttr()}=\"${bindIds.join(',')}\"`;\n\n if (VOID_TAGS.has(tag)) return new SafeHtml(`<${tag}${attrStr}>`);\n\n const childSegment: Segment = children != null\n ? toSegment(children)\n : { kind: 'static', html: '' };\n return new SafeHtml(wrapWithTags(childSegment, `<${tag}${attrStr}>`, `</${tag}>`));\n}\n\nexport { jsx as jsxs };\n// vitest's dev-mode JSX transform emits `jsxDEV(tag, props, ...)`; the\n// alias lets tests import this module without the production build pipeline\n// caring.\nexport { jsx as jsxDEV };\n\nexport function Fragment({ children }: { children?: Children }): SafeHtml {\n return new SafeHtml(children != null ? toSegment(children) : { kind: 'static', html: '' });\n}\n\n// eslint-disable-next-line @typescript-eslint/no-namespace\nexport namespace JSX {\n export type Element = SafeHtml;\n export interface ElementChildrenAttribute {\n children: unknown;\n }\n // Per-tag attribute contracts live in `./jsx-types.ts` as\n // `KerfBuiltinIntrinsicElements`. Re-exposed as an **interface** (not a\n // type alias) so consumers can declaration-merge custom-element tags\n // (KF-100):\n //\n // declare module 'kerfjs/jsx-runtime' {\n // namespace JSX {\n // interface IntrinsicElements {\n // 'my-element': KerfCustomElement & { foo?: string };\n // }\n // }\n // }\n //\n // KF-123: the imported interface is named `KerfBuiltinIntrinsicElements`\n // upstream so tsup/tsc cannot strip an import alias and end up emitting\n // `interface IntrinsicElements extends IntrinsicElements {}` in the .d.ts\n // — that shadowed form self-resolves to empty and breaks every `<tag>` in\n // consumer .tsx with TS2339. Verified against `dist/jsx-runtime.d.ts` by\n // `tests/dist/jsx-typing/` on every `npm run build`.\n // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n export interface IntrinsicElements extends KerfBuiltinIntrinsicElements {}\n}\n\n/**\n * Public re-exports of the JSX type primitives so consumers can compose\n * attribute interfaces for custom elements without reaching into\n * `kerfjs/jsx-types` (which is intentionally not in `package.json#exports`).\n */\nexport type {\n AttrLike,\n AttrValue,\n DataAriaAttrs,\n KerfBaseAttrs,\n KerfCustomElement,\n} from './jsx-types.js';\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -142,6 +142,28 @@ declare function delegate<T extends Element = Element>(rootEl: HTMLElement, type
|
|
|
142
142
|
*/
|
|
143
143
|
declare function delegateCapture<T extends Element = Element>(rootEl: HTMLElement, type: string, selector: string, handler: (event: Event, target: T) => void): () => void;
|
|
144
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Re-exports of `@preact/signals-core`. Lets the rest of the codebase depend
|
|
147
|
+
* on `'./reactive.js'` without naming the underlying lib, so swapping it out
|
|
148
|
+
* later (or fronting it with a hand-rolled implementation) is a one-file
|
|
149
|
+
* change.
|
|
150
|
+
*
|
|
151
|
+
* Two dev-gated wrappers sit in front of the bare re-exports:
|
|
152
|
+
*
|
|
153
|
+
* - `signal()` returns a `DevSignal` when `KERF_DEV_WARN_UNTRACKED_SIGNALS=1`
|
|
154
|
+
* (KF-176) — warns on writes to signals with no subscribers.
|
|
155
|
+
*
|
|
156
|
+
* - `effect()` wraps the user body in `enterEffect()` / `exitEffect()` calls
|
|
157
|
+
* when `KERF_DEV_WARN_DELEGATE_IN_EFFECT=1` so `delegate()` can detect when
|
|
158
|
+
* it's running inside an effect body and fire the appropriate warning.
|
|
159
|
+
*
|
|
160
|
+
* Both gates short-circuit on `NODE_ENV === 'production'` — production
|
|
161
|
+
* always sees the bare `@preact/signals-core` exports with zero overhead.
|
|
162
|
+
*/
|
|
163
|
+
|
|
164
|
+
declare function signal<T>(value?: T): Signal<T>;
|
|
165
|
+
declare function effect(fn: () => void | (() => void)): () => void;
|
|
166
|
+
|
|
145
167
|
/**
|
|
146
168
|
* `each(items, render, cacheKey?)` — keyed list iteration with per-item memoization.
|
|
147
169
|
*
|
|
@@ -295,28 +317,6 @@ declare function morph(liveRoot: Element, template: Element | SafeHtml | string,
|
|
|
295
317
|
type MountResult = SafeHtml | string | number | boolean | null | undefined;
|
|
296
318
|
declare function mount(rootEl: HTMLElement, render: () => MountResult): () => void;
|
|
297
319
|
|
|
298
|
-
/**
|
|
299
|
-
* Re-exports of `@preact/signals-core`. Lets the rest of the codebase depend
|
|
300
|
-
* on `'./reactive.js'` without naming the underlying lib, so swapping it out
|
|
301
|
-
* later (or fronting it with a hand-rolled implementation) is a one-file
|
|
302
|
-
* change.
|
|
303
|
-
*
|
|
304
|
-
* Two dev-gated wrappers sit in front of the bare re-exports:
|
|
305
|
-
*
|
|
306
|
-
* - `signal()` returns a `DevSignal` when `KERF_DEV_WARN_UNTRACKED_SIGNALS=1`
|
|
307
|
-
* (KF-176) — warns on writes to signals with no subscribers.
|
|
308
|
-
*
|
|
309
|
-
* - `effect()` wraps the user body in `enterEffect()` / `exitEffect()` calls
|
|
310
|
-
* when `KERF_DEV_WARN_DELEGATE_IN_EFFECT=1` so `delegate()` can detect when
|
|
311
|
-
* it's running inside an effect body and fire the appropriate warning.
|
|
312
|
-
*
|
|
313
|
-
* Both gates short-circuit on `NODE_ENV === 'production'` — production
|
|
314
|
-
* always sees the bare `@preact/signals-core` exports with zero overhead.
|
|
315
|
-
*/
|
|
316
|
-
|
|
317
|
-
declare function signal<T>(value?: T): Signal<T>;
|
|
318
|
-
declare function effect(fn: () => void | (() => void)): () => void;
|
|
319
|
-
|
|
320
320
|
/**
|
|
321
321
|
* `toElement(jsx)` — JSX → DOM, with SVG-aware namespace handling.
|
|
322
322
|
*
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { isSafeHtml, listSafeHtml, flattenWithoutListItems, collectLists, flatten, granularListSafeHtml } from './chunk-
|
|
2
|
-
export { Fragment, SafeHtml, isSafeHtml, raw } from './chunk-
|
|
3
|
-
export { defineStore, resetAllStores } from './chunk-
|
|
4
|
-
import { warnIfInsideEffect, effect } from './chunk-
|
|
5
|
-
export { batch, computed, effect, signal } from './chunk-
|
|
1
|
+
import { captureRowBindings, isSafeHtml, listSafeHtml, newBindingContext, _setBindingContext, flattenWithoutListItems, wireBindings, collectLists, disposeRowBindings, flatten, wireRowBindings, granularListSafeHtml } from './chunk-QNYOMGI4.js';
|
|
2
|
+
export { Fragment, SafeHtml, isSafeHtml, raw } from './chunk-QNYOMGI4.js';
|
|
3
|
+
export { defineStore, resetAllStores } from './chunk-7SKIIA5P.js';
|
|
4
|
+
import { warnIfInsideEffect, effect } from './chunk-4E26PO2C.js';
|
|
5
|
+
export { batch, computed, effect, signal } from './chunk-4E26PO2C.js';
|
|
6
6
|
|
|
7
7
|
// src/attrSelector.ts
|
|
8
8
|
function cssEscapeIdent(value) {
|
|
@@ -229,20 +229,22 @@ function eachGranular(sig, render, cacheKey) {
|
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
|
-
const
|
|
232
|
+
const renderRow = (item, index) => captureRowBindings(() => {
|
|
233
233
|
const out = render(item, index);
|
|
234
234
|
return isSafeHtml(out) ? out.toString() : out;
|
|
235
|
-
};
|
|
235
|
+
});
|
|
236
236
|
const internalPatches = new Array(patches.length);
|
|
237
237
|
try {
|
|
238
238
|
for (let i = 0; i < patches.length; i++) {
|
|
239
239
|
const p = patches[i];
|
|
240
240
|
if (p.type === "insert" || p.type === "update") {
|
|
241
|
+
const { html, bindings } = renderRow(p.item, p.index);
|
|
241
242
|
internalPatches[i] = {
|
|
242
243
|
type: p.type,
|
|
243
244
|
index: p.index,
|
|
244
245
|
item: p.item,
|
|
245
|
-
html
|
|
246
|
+
html,
|
|
247
|
+
bindings
|
|
246
248
|
};
|
|
247
249
|
} else {
|
|
248
250
|
internalPatches[i] = p;
|
|
@@ -281,15 +283,21 @@ function eachSnapshotById(items, render, cacheKey, id) {
|
|
|
281
283
|
seen.add(item);
|
|
282
284
|
const k = cacheKey ? cacheKey(item, i) : void 0;
|
|
283
285
|
let html;
|
|
286
|
+
let bindings;
|
|
284
287
|
const cached = cache !== null ? cache.get(item) : void 0;
|
|
285
288
|
if (cached !== void 0 && cached.cacheKey === k) {
|
|
286
289
|
html = cached.html;
|
|
290
|
+
bindings = cached.bindings;
|
|
287
291
|
} else {
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
292
|
+
const captured = captureRowBindings(() => {
|
|
293
|
+
const out = render(item, i);
|
|
294
|
+
return isSafeHtml(out) ? out.toString() : out;
|
|
295
|
+
});
|
|
296
|
+
html = captured.html;
|
|
297
|
+
bindings = captured.bindings;
|
|
298
|
+
if (cache !== null) cache.set(item, { cacheKey: k, html, bindings });
|
|
291
299
|
}
|
|
292
|
-
segItems[i] = { ref: item, cacheKey: k, html };
|
|
300
|
+
segItems[i] = { ref: item, cacheKey: k, html, bindings };
|
|
293
301
|
}
|
|
294
302
|
if (cacheKey !== void 0) {
|
|
295
303
|
maybeWarnDuplicateCacheKeys(id, segItems);
|
|
@@ -815,6 +823,7 @@ function reconcileGranular(binding, patches) {
|
|
|
815
823
|
}
|
|
816
824
|
if (patch.type === "remove") {
|
|
817
825
|
const entry = items[patch.index];
|
|
826
|
+
disposeRowBindings(entry.bindingDisposers);
|
|
818
827
|
liveParent.removeChild(entry.node);
|
|
819
828
|
items.splice(patch.index, 1);
|
|
820
829
|
i += 1;
|
|
@@ -846,26 +855,50 @@ function applySingleInsert(liveParent, items, patch, tailAnchor) {
|
|
|
846
855
|
ref: patch.item,
|
|
847
856
|
cacheKey: void 0,
|
|
848
857
|
html,
|
|
849
|
-
node: newNode
|
|
858
|
+
node: newNode,
|
|
859
|
+
bindings: patch.bindings,
|
|
860
|
+
// KF-294: wire the inserted row's fine-grained bindings to its new node.
|
|
861
|
+
bindingDisposers: wireRowIfBound(newNode, patch.bindings)
|
|
850
862
|
});
|
|
851
863
|
}
|
|
864
|
+
function wireRowIfBound(node, bindings) {
|
|
865
|
+
return bindings !== void 0 && bindings.length > 0 ? wireRowBindings(node, bindings) : void 0;
|
|
866
|
+
}
|
|
852
867
|
function applySingleUpdate(liveParent, items, patch) {
|
|
853
868
|
const { html } = patch;
|
|
854
869
|
const oldEntry = items[patch.index];
|
|
855
870
|
if (html === oldEntry.html) return;
|
|
856
871
|
if (tryAttributeOnlyFastPath(oldEntry.node, oldEntry.html, html) || tryTextContentFastPath(oldEntry.node, oldEntry.html, html)) {
|
|
857
|
-
items[patch.index] =
|
|
872
|
+
items[patch.index] = reuseBound(patch, html, oldEntry);
|
|
858
873
|
return;
|
|
859
874
|
}
|
|
860
875
|
const newNode = parseSingleRow(html);
|
|
861
876
|
if (oldEntry.node.tagName === newNode.tagName) {
|
|
862
877
|
_morphElement(oldEntry.node, newNode);
|
|
863
|
-
items[patch.index] =
|
|
878
|
+
items[patch.index] = reuseBound(patch, html, oldEntry);
|
|
864
879
|
} else {
|
|
880
|
+
disposeRowBindings(oldEntry.bindingDisposers);
|
|
865
881
|
liveParent.replaceChild(newNode, oldEntry.node);
|
|
866
|
-
items[patch.index] = {
|
|
882
|
+
items[patch.index] = {
|
|
883
|
+
ref: patch.item,
|
|
884
|
+
cacheKey: void 0,
|
|
885
|
+
html,
|
|
886
|
+
node: newNode,
|
|
887
|
+
bindings: patch.bindings,
|
|
888
|
+
bindingDisposers: wireRowIfBound(newNode, patch.bindings)
|
|
889
|
+
};
|
|
867
890
|
}
|
|
868
891
|
}
|
|
892
|
+
function reuseBound(patch, html, oldEntry) {
|
|
893
|
+
return {
|
|
894
|
+
ref: patch.item,
|
|
895
|
+
cacheKey: void 0,
|
|
896
|
+
html,
|
|
897
|
+
node: oldEntry.node,
|
|
898
|
+
bindings: oldEntry.bindings,
|
|
899
|
+
bindingDisposers: oldEntry.bindingDisposers
|
|
900
|
+
};
|
|
901
|
+
}
|
|
869
902
|
function applyBulkUpdate(liveParent, items, patches, start, end) {
|
|
870
903
|
const morphChanges = [];
|
|
871
904
|
for (let k = start; k < end; k++) {
|
|
@@ -873,7 +906,7 @@ function applyBulkUpdate(liveParent, items, patches, start, end) {
|
|
|
873
906
|
const oldEntry = items[p.index];
|
|
874
907
|
if (p.html === oldEntry.html) continue;
|
|
875
908
|
if (tryAttributeOnlyFastPath(oldEntry.node, oldEntry.html, p.html) || tryTextContentFastPath(oldEntry.node, oldEntry.html, p.html)) {
|
|
876
|
-
items[p.index] =
|
|
909
|
+
items[p.index] = reuseBound(p, p.html, oldEntry);
|
|
877
910
|
continue;
|
|
878
911
|
}
|
|
879
912
|
morphChanges.push({ patchIdx: k, html: p.html });
|
|
@@ -895,10 +928,18 @@ function applyBulkUpdate(liveParent, items, patches, start, end) {
|
|
|
895
928
|
const oldEntry = items[p.index];
|
|
896
929
|
if (oldEntry.node.tagName === newNodes[k].tagName) {
|
|
897
930
|
_morphElement(oldEntry.node, newNodes[k]);
|
|
898
|
-
items[p.index] =
|
|
931
|
+
items[p.index] = reuseBound(p, c.html, oldEntry);
|
|
899
932
|
} else {
|
|
933
|
+
disposeRowBindings(oldEntry.bindingDisposers);
|
|
900
934
|
liveParent.replaceChild(newNodes[k], oldEntry.node);
|
|
901
|
-
items[p.index] = {
|
|
935
|
+
items[p.index] = {
|
|
936
|
+
ref: p.item,
|
|
937
|
+
cacheKey: void 0,
|
|
938
|
+
html: c.html,
|
|
939
|
+
node: newNodes[k],
|
|
940
|
+
bindings: p.bindings,
|
|
941
|
+
bindingDisposers: wireRowIfBound(newNodes[k], p.bindings)
|
|
942
|
+
};
|
|
902
943
|
}
|
|
903
944
|
}
|
|
904
945
|
}
|
|
@@ -928,7 +969,10 @@ function applyBulkInsert(liveParent, items, patches, start, end, tailAnchor) {
|
|
|
928
969
|
ref: p.item,
|
|
929
970
|
cacheKey: void 0,
|
|
930
971
|
html: htmls[k],
|
|
931
|
-
node: newNodes[k]
|
|
972
|
+
node: newNodes[k],
|
|
973
|
+
bindings: p.bindings,
|
|
974
|
+
bindingDisposers: wireRowIfBound(newNodes[k], p.bindings)
|
|
975
|
+
// KF-294
|
|
932
976
|
};
|
|
933
977
|
}
|
|
934
978
|
items.splice(startIdx, 0, ...newEntries);
|
|
@@ -984,13 +1028,28 @@ function tryInPlaceContentUpdate(binding, listSeg) {
|
|
|
984
1028
|
}
|
|
985
1029
|
function updateRowInPlace(liveParent, old, ni, index) {
|
|
986
1030
|
if (old.html === ni.html || tryAttributeOnlyFastPath(old.node, old.html, ni.html) || tryTextContentFastPath(old.node, old.html, ni.html)) {
|
|
987
|
-
return {
|
|
1031
|
+
return {
|
|
1032
|
+
ref: ni.ref,
|
|
1033
|
+
cacheKey: ni.cacheKey,
|
|
1034
|
+
html: ni.html,
|
|
1035
|
+
node: old.node,
|
|
1036
|
+
bindings: old.bindings,
|
|
1037
|
+
bindingDisposers: old.bindingDisposers
|
|
1038
|
+
};
|
|
988
1039
|
}
|
|
989
1040
|
const newNode = parseSingleRow2(ni.html, index);
|
|
990
1041
|
if (old.node.tagName === newNode.tagName) {
|
|
991
1042
|
_morphElement(old.node, newNode);
|
|
992
|
-
return {
|
|
1043
|
+
return {
|
|
1044
|
+
ref: ni.ref,
|
|
1045
|
+
cacheKey: ni.cacheKey,
|
|
1046
|
+
html: ni.html,
|
|
1047
|
+
node: old.node,
|
|
1048
|
+
bindings: old.bindings,
|
|
1049
|
+
bindingDisposers: old.bindingDisposers
|
|
1050
|
+
};
|
|
993
1051
|
}
|
|
1052
|
+
disposeRowBindings(old.bindingDisposers);
|
|
994
1053
|
liveParent.replaceChild(newNode, old.node);
|
|
995
1054
|
return { ref: ni.ref, cacheKey: ni.cacheKey, html: ni.html, node: newNode };
|
|
996
1055
|
}
|
|
@@ -1004,11 +1063,11 @@ function parseSingleRow2(html, index) {
|
|
|
1004
1063
|
function reconcileSnapshot(binding, listSeg) {
|
|
1005
1064
|
if (tryInPlaceContentUpdate(binding, listSeg)) return;
|
|
1006
1065
|
const { liveParent } = binding;
|
|
1007
|
-
const { newRecord, prevIdx,
|
|
1066
|
+
const { newRecord, prevIdx, removedItems, freshIndices, freshHtmls } = classifyItems(binding.items, listSeg);
|
|
1008
1067
|
const tailAnchor = endAnchor(binding);
|
|
1009
1068
|
buildFreshNodes(newRecord, freshIndices, freshHtmls);
|
|
1010
1069
|
const focusSnap = captureFocus(liveParent);
|
|
1011
|
-
removeOldNodes(liveParent,
|
|
1070
|
+
removeOldNodes(liveParent, removedItems);
|
|
1012
1071
|
applyMoves(liveParent, newRecord, prevIdx, lis(prevIdx), tailAnchor);
|
|
1013
1072
|
if (focusSnap !== null) restoreFocus(focusSnap);
|
|
1014
1073
|
binding.items = newRecord;
|
|
@@ -1023,7 +1082,7 @@ function classifyItems(oldItems, listSeg) {
|
|
|
1023
1082
|
}
|
|
1024
1083
|
const newRecord = new Array(listSeg.items.length);
|
|
1025
1084
|
const prevIdx = new Array(listSeg.items.length);
|
|
1026
|
-
const
|
|
1085
|
+
const removedItems = [];
|
|
1027
1086
|
const freshIndices = [];
|
|
1028
1087
|
const freshHtmls = [];
|
|
1029
1088
|
for (let i = 0; i < listSeg.items.length; i++) {
|
|
@@ -1036,20 +1095,21 @@ function classifyItems(oldItems, listSeg) {
|
|
|
1036
1095
|
prevIdx[i] = oi[1];
|
|
1037
1096
|
continue;
|
|
1038
1097
|
}
|
|
1039
|
-
|
|
1098
|
+
removedItems.push(oi[0]);
|
|
1040
1099
|
}
|
|
1041
1100
|
newRecord[i] = {
|
|
1042
1101
|
ref: ni.ref,
|
|
1043
1102
|
cacheKey: ni.cacheKey,
|
|
1044
1103
|
html: ni.html,
|
|
1045
|
-
node: null
|
|
1104
|
+
node: null,
|
|
1105
|
+
bindings: ni.bindings
|
|
1046
1106
|
};
|
|
1047
1107
|
prevIdx[i] = -1;
|
|
1048
1108
|
freshIndices.push(i);
|
|
1049
1109
|
freshHtmls.push(ni.html);
|
|
1050
1110
|
}
|
|
1051
|
-
for (const [, orphan] of oldByRef)
|
|
1052
|
-
return { newRecord, prevIdx,
|
|
1111
|
+
for (const [, orphan] of oldByRef) removedItems.push(orphan[0]);
|
|
1112
|
+
return { newRecord, prevIdx, removedItems, freshIndices, freshHtmls };
|
|
1053
1113
|
}
|
|
1054
1114
|
function buildFreshNodes(newRecord, freshIndices, freshHtmls) {
|
|
1055
1115
|
if (freshHtmls.length === 0) return;
|
|
@@ -1060,7 +1120,11 @@ function buildFreshNodes(newRecord, freshIndices, freshHtmls) {
|
|
|
1060
1120
|
let node = tpl.content.firstElementChild;
|
|
1061
1121
|
for (const idx of freshIndices) {
|
|
1062
1122
|
const next = node.nextElementSibling;
|
|
1063
|
-
newRecord[idx]
|
|
1123
|
+
const item = newRecord[idx];
|
|
1124
|
+
item.node = node;
|
|
1125
|
+
if (item.bindings !== void 0 && item.bindings.length > 0) {
|
|
1126
|
+
item.bindingDisposers = wireRowBindings(item.node, item.bindings);
|
|
1127
|
+
}
|
|
1064
1128
|
node = next;
|
|
1065
1129
|
}
|
|
1066
1130
|
}
|
|
@@ -1072,9 +1136,10 @@ function findOffendingRow(newRecord, freshIndices, freshHtmls) {
|
|
|
1072
1136
|
}
|
|
1073
1137
|
return new Error("each(): bulk-parse mismatch with no per-row offender (kerf bug).");
|
|
1074
1138
|
}
|
|
1075
|
-
function removeOldNodes(liveParent,
|
|
1076
|
-
for (const
|
|
1077
|
-
|
|
1139
|
+
function removeOldNodes(liveParent, removedItems) {
|
|
1140
|
+
for (const item of removedItems) {
|
|
1141
|
+
disposeRowBindings(item.bindingDisposers);
|
|
1142
|
+
if (item.node.parentElement === liveParent) liveParent.removeChild(item.node);
|
|
1078
1143
|
}
|
|
1079
1144
|
}
|
|
1080
1145
|
function applyMoves(liveParent, newRecord, prevIdx, stable, tailAnchor) {
|
|
@@ -1180,24 +1245,35 @@ function mount(rootEl, render) {
|
|
|
1180
1245
|
caches: /* @__PURE__ */ new Map(),
|
|
1181
1246
|
bindingCounts: /* @__PURE__ */ new Map()
|
|
1182
1247
|
};
|
|
1248
|
+
const bindingCtx = newBindingContext();
|
|
1249
|
+
let bindingDisposers = [];
|
|
1183
1250
|
let isFirst = true;
|
|
1184
1251
|
let prevStaticHtml = "";
|
|
1185
1252
|
const disposeEffect = effect(() => {
|
|
1186
1253
|
renderCtx.counter = 0;
|
|
1254
|
+
bindingCtx.counter = 0;
|
|
1255
|
+
bindingCtx.list = [];
|
|
1187
1256
|
_setRenderContext(renderCtx);
|
|
1257
|
+
_setBindingContext(bindingCtx);
|
|
1188
1258
|
let result;
|
|
1189
1259
|
try {
|
|
1190
1260
|
result = render();
|
|
1191
1261
|
} finally {
|
|
1192
1262
|
_setRenderContext(null);
|
|
1263
|
+
_setBindingContext(null);
|
|
1193
1264
|
}
|
|
1194
1265
|
const segment = isSafeHtml(result) ? result.__segment ?? { kind: "static", html: result.__html } : { kind: "static", html: coerceRenderResult(result) };
|
|
1195
1266
|
if (isFirst) {
|
|
1196
1267
|
runFirstRender(rootEl, segment, bindings);
|
|
1197
1268
|
prevStaticHtml = flattenWithoutListItems(segment);
|
|
1269
|
+
bindingDisposers = wireBindings(rootEl, bindingCtx, bindingDisposers);
|
|
1198
1270
|
isFirst = false;
|
|
1199
1271
|
} else {
|
|
1200
|
-
|
|
1272
|
+
const nextStaticHtml = runSubsequentRender(rootEl, segment, bindings, renderCtx, prevStaticHtml);
|
|
1273
|
+
if (nextStaticHtml !== prevStaticHtml) {
|
|
1274
|
+
bindingDisposers = wireBindings(rootEl, bindingCtx, bindingDisposers);
|
|
1275
|
+
}
|
|
1276
|
+
prevStaticHtml = nextStaticHtml;
|
|
1201
1277
|
}
|
|
1202
1278
|
for (const listSeg of collectLists(segment).values()) {
|
|
1203
1279
|
const binding = bindings.get(listSeg.id);
|
|
@@ -1207,6 +1283,11 @@ function mount(rootEl, render) {
|
|
|
1207
1283
|
});
|
|
1208
1284
|
return () => {
|
|
1209
1285
|
disposeEffect();
|
|
1286
|
+
for (const d of bindingDisposers) d();
|
|
1287
|
+
bindingDisposers = [];
|
|
1288
|
+
for (const b of bindings.values()) {
|
|
1289
|
+
for (const item of b.items) disposeRowBindings(item.bindingDisposers);
|
|
1290
|
+
}
|
|
1210
1291
|
listenerWarnObserver?.disconnect();
|
|
1211
1292
|
delete rootEl[MOUNTED_MARKER];
|
|
1212
1293
|
};
|
|
@@ -1247,12 +1328,18 @@ function bindListsFromMarkers(rootEl, segment, bindings, inlinedItems) {
|
|
|
1247
1328
|
let next = marker.nextElementSibling;
|
|
1248
1329
|
for (let i = 0; i < listSeg.items.length && next !== null; i++) {
|
|
1249
1330
|
validateInlinedRowMatch(listSeg.items[i].html, i, next);
|
|
1250
|
-
items.
|
|
1331
|
+
const rowBindings = listSeg.items[i].bindings;
|
|
1332
|
+
const bound = {
|
|
1251
1333
|
ref: listSeg.items[i].ref,
|
|
1252
1334
|
cacheKey: listSeg.items[i].cacheKey,
|
|
1253
1335
|
html: listSeg.items[i].html,
|
|
1254
|
-
node: next
|
|
1255
|
-
|
|
1336
|
+
node: next,
|
|
1337
|
+
bindings: rowBindings
|
|
1338
|
+
};
|
|
1339
|
+
if (rowBindings !== void 0 && rowBindings.length > 0) {
|
|
1340
|
+
bound.bindingDisposers = wireRowBindings(next, rowBindings);
|
|
1341
|
+
}
|
|
1342
|
+
items.push(bound);
|
|
1256
1343
|
next = next.nextElementSibling;
|
|
1257
1344
|
}
|
|
1258
1345
|
}
|
|
@@ -1282,6 +1369,7 @@ function cleanupOrphanBindings(segment, bindings, renderCtx) {
|
|
|
1282
1369
|
for (const [id, binding] of bindings) {
|
|
1283
1370
|
if (liveIds.has(id)) continue;
|
|
1284
1371
|
for (const item of binding.items) {
|
|
1372
|
+
disposeRowBindings(item.bindingDisposers);
|
|
1285
1373
|
if (item.node.parentElement !== null) {
|
|
1286
1374
|
item.node.parentElement.removeChild(item.node);
|
|
1287
1375
|
}
|