kerfjs 4.4.1 → 5.0.0-beta.4
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 +111 -0
- package/LICENSE +1 -1
- package/README.md +3 -3
- package/ai/cursorrules +46 -1
- package/ai/manifest.json +62 -5
- package/ai/skill.md +53 -2
- package/dist/actions.d.ts +1 -1
- package/dist/actions.js +4 -4
- package/dist/actions.js.map +1 -1
- package/dist/array-signal.js +5 -5
- package/dist/async.js +17 -10
- package/dist/async.js.map +1 -1
- package/dist/attach.d.ts +11 -8
- package/dist/attach.js +52 -4
- package/dist/attach.js.map +1 -1
- package/dist/chunk-GY4XV2UV.js +1 -1
- package/dist/{chunk-VVDJLWMP.js → chunk-HW7KSM2Y.js} +2 -2
- package/dist/chunk-HW7KSM2Y.js.map +1 -0
- package/dist/{chunk-KEZTD6H4.js → chunk-KPXIOG2C.js} +3 -3
- package/dist/{chunk-KEZTD6H4.js.map → chunk-KPXIOG2C.js.map} +1 -1
- package/dist/{chunk-SAYPJ6XR.js → chunk-KZJXHFIB.js} +10 -6
- package/dist/chunk-KZJXHFIB.js.map +1 -0
- package/dist/{chunk-MRYM3O3V.js → chunk-LVH3GC6B.js} +11 -8
- package/dist/chunk-LVH3GC6B.js.map +1 -0
- package/dist/chunk-QIP723L4.js +1 -1
- package/dist/{chunk-SRWQKB33.js → chunk-SVATPF5R.js} +97 -74
- package/dist/chunk-SVATPF5R.js.map +1 -0
- package/dist/{chunk-3APBEVHF.js → chunk-U6FK33SG.js} +3 -3
- package/dist/{chunk-3APBEVHF.js.map → chunk-U6FK33SG.js.map} +1 -1
- package/dist/{chunk-U32TFTGZ.js → chunk-UZJ6I4T6.js} +3 -3
- package/dist/chunk-UZJ6I4T6.js.map +1 -0
- package/dist/{chunk-YHH7OUFA.js → chunk-V46JKE44.js} +3 -3
- package/dist/chunk-V46JKE44.js.map +1 -0
- package/dist/{chunk-SUPUPSBE.js → chunk-ZDCJZCNO.js} +9 -8
- package/dist/chunk-ZDCJZCNO.js.map +1 -0
- package/dist/dev.d.ts +9 -6
- package/dist/dev.js +5 -5
- package/dist/dev.js.map +1 -1
- package/dist/html.d.ts +1 -1
- package/dist/html.js +5 -5
- package/dist/html.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +11 -11
- package/dist/jsx-runtime.js +5 -5
- package/dist/list.d.ts +1 -1
- package/dist/list.js +273 -211
- package/dist/list.js.map +1 -1
- package/dist/overlay.d.ts +190 -280
- package/dist/overlay.js +401 -361
- package/dist/overlay.js.map +1 -1
- package/dist/remount.d.ts +5 -3
- package/dist/remount.js +29 -10
- package/dist/remount.js.map +1 -1
- package/dist/router.d.ts +1 -1
- package/dist/router.js +44 -22
- package/dist/router.js.map +1 -1
- package/dist/scope.d.ts +4 -3
- package/dist/scope.js +10 -10
- package/dist/scope.js.map +1 -1
- package/dist/testing.js +4 -4
- package/dist/timing.d.ts +3 -2
- package/dist/timing.js +5 -5
- package/dist/timing.js.map +1 -1
- package/llms.txt +7 -4
- package/package.json +12 -8
- package/dist/chunk-MRYM3O3V.js.map +0 -1
- package/dist/chunk-SAYPJ6XR.js.map +0 -1
- package/dist/chunk-SRWQKB33.js.map +0 -1
- package/dist/chunk-SUPUPSBE.js.map +0 -1
- package/dist/chunk-U32TFTGZ.js.map +0 -1
- package/dist/chunk-VVDJLWMP.js.map +0 -1
- package/dist/chunk-YHH7OUFA.js.map +0 -1
- /package/dist/{attrSelector-Cmu2ZoGO.d.ts → attr-Cmu2ZoGO.d.ts} +0 -0
package/dist/html.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/templateParse.ts","../src/html.ts"],"names":[],"mappings":"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","file":"html.js","sourcesContent":["/**\n * Static-parts parser for the `kerfjs/html` tagged template (`html\\`\\``).\n *\n * Parses a template's static string chunks ONCE into a `ParsedTemplate`:\n * the (possibly trimmed) chunks plus an ordered hole-descriptor list that\n * classifies each `${…}` hole as a TEXT hole (a child position) or an ATTR\n * hole (a complete attribute value, `attr=${v}` / `attr=\"${v}\"`). The\n * renderer in `src/html.ts` caches the parse per template-strings-array\n * identity, so repeated renders of the same call site skip this entirely.\n *\n * The state machine is deliberately small and strict: it tracks only\n * text / inside-open-tag / inside-comment modes and quote state. Holes in\n * any position it can't prove safe — tag names, attribute names, partial\n * attribute values, comments — THROW with an actionable message rather\n * than guessing. Static chunks are author-written markup and pass through\n * verbatim (the same trust model as JSX tag/attribute names).\n */\n\nexport interface AttrHole {\n kind: 'attr';\n /** The attribute name scanned from the preceding static chunk, verbatim. */\n name: string;\n /** The surrounding quote character, or null for an unquoted `attr=${v}`. */\n quote: '\"' | \"'\" | null;\n}\n\nexport interface TextHole {\n kind: 'text';\n}\n\nexport type TemplateHole = AttrHole | TextHole;\n\nexport interface ParsedTemplate {\n /**\n * Static chunks with attr-hole scaffolding (`name=`, quotes) stripped —\n * the renderer re-emits the whole ` name=\"value\"` via the JSX attribute\n * renderer. Always `holes.length + 1` entries.\n */\n chunks: string[];\n holes: TemplateHole[];\n /**\n * Per-chunk offset of the `>` that closes the element tag open at the\n * chunk's start, or -1. Only chunks that follow an attr hole can start\n * inside a tag; the renderer uses this to inject the bound-attribute\n * marker (`data-kfb` / `data-kfbrow`) for signal attribute holes.\n */\n tagClose: number[];\n}\n\nconst TEXT_HOLE: TextHole = { kind: 'text' };\n\n/** Matches a chunk tail of `name=` optionally followed by an opening quote. */\nconst ATTR_TAIL = /(\\s*)([^\\s\"'<>/=]+)=([\"'])?$/;\n\nfunction holeError(detail: string): Error {\n return new Error(`html\\`\\`: ${detail}`);\n}\n\nfunction partialValueError(tail: string): Error {\n return holeError(\n `partial attribute values are not supported (near ${JSON.stringify(tail.slice(-30))}) — `\n + 'a hole must be the COMPLETE attribute value: attr=${v} or attr=\"${v}\". '\n + 'For class=\"a ${b}\"-style composition, build the full string first '\n + '(a plain template literal, or computed(() => `a ${b.value}`) for a bound attribute).',\n );\n}\n\nexport function parseTemplate(strings: readonly string[]): ParsedTemplate {\n const chunks: string[] = new Array(strings.length);\n const holes: TemplateHole[] = new Array(strings.length - 1);\n const tagClose: number[] = new Array<number>(strings.length).fill(-1);\n let mode: 'text' | 'tag' | 'comment' = 'text';\n let quote: '\"' | \"'\" | null = null;\n\n for (let i = 0; i < strings.length; i++) {\n let s = strings[i];\n // A quoted attr hole (`attr=\"${v}\"`) must be immediately closed by its\n // quote — the renderer emits the full quoted attribute itself, so the\n // author's closing quote is consumed here.\n if (i > 0) {\n const prev = holes[i - 1];\n if (prev.kind === 'attr' && prev.quote !== null) {\n if (s[0] !== prev.quote) throw partialValueError(strings[i - 1]);\n s = s.slice(1);\n }\n }\n\n const startedInTag = mode === 'tag';\n for (let j = 0; j < s.length; j++) {\n const ch = s[j];\n if (mode === 'text') {\n if (ch === '<') {\n if (s.startsWith('<!--', j)) {\n mode = 'comment';\n j += 3;\n } else if (j + 1 < s.length && /[a-zA-Z!/?]/.test(s[j + 1])) {\n mode = 'tag';\n }\n }\n } else if (mode === 'tag') {\n if (quote !== null) {\n if (ch === quote) quote = null;\n } else if (ch === '\"' || ch === \"'\") {\n quote = ch;\n } else if (ch === '>') {\n if (startedInTag && tagClose[i] === -1) tagClose[i] = j;\n mode = 'text';\n }\n } else if (ch === '-' && s.startsWith('-->', j)) {\n mode = 'text';\n j += 2;\n }\n }\n\n // Classify the hole that follows this chunk (no hole after the last one).\n if (i < strings.length - 1) {\n if (mode === 'comment') {\n throw holeError(\n 'holes inside HTML comments are not supported — move the ${…} hole outside the <!-- --> comment.',\n );\n }\n if (mode === 'text') {\n if (/<\\/?$/.test(s)) {\n throw holeError(\n 'tag-name holes (`<${…}>`) are not supported — write tag names statically. '\n + 'For a literal \"<\" before a hole, escape it as <.',\n );\n }\n holes[i] = TEXT_HOLE;\n } else {\n const m = ATTR_TAIL.exec(s);\n if (quote !== null) {\n // Inside a quoted attribute value: only valid if the quote opened\n // as the chunk's very last character (i.e. the hole IS the value).\n if (m === null || m[3] === undefined) throw partialValueError(s);\n holes[i] = { kind: 'attr', name: m[2], quote: m[3] as '\"' | \"'\" };\n s = s.slice(0, s.length - m[0].length);\n quote = null;\n } else if (m !== null && m[3] === undefined) {\n // Unquoted `attr=${v}`: the next static chunk must resume with an\n // attribute delimiter, or the template may simply end there.\n const next = strings[i + 1];\n const validNext = next.length > 0 ? /^[\\s>/]/.test(next) : i + 1 === strings.length - 1;\n if (!validNext) throw partialValueError(s);\n holes[i] = { kind: 'attr', name: m[2], quote: null };\n s = s.slice(0, s.length - m[0].length);\n } else if (/<\\/?$/.test(s)) {\n // The tag opened as the chunk's last characters (`</${…}`).\n throw holeError(\n 'tag-name holes (`<${…}>`) are not supported — write tag names statically. '\n + 'For a literal \"<\" before a hole, escape it as <.',\n );\n } else {\n throw holeError(\n 'a hole inside a tag must be a complete attribute value — attr=${…} or attr=\"${…}\". '\n + 'Tag-name and attribute-name holes are not supported; write those statically.',\n );\n }\n }\n }\n chunks[i] = s;\n }\n return { chunks, holes, tagClose };\n}\n","/**\n * `html` — tagged-template authoring at the `kerfjs/html` subpath, so\n * \"no build step\" is literally true: a CDN / importmap consumer can author\n * kerf UIs without a JSX transform.\n *\n * import { html } from 'kerfjs/html';\n * html`<div class=\"${cls}\">Count: ${count}</div>`\n * html`<ul>${each(items.value, (i) => html`<li id=\"${i.id}\">${i.label}</li>`)}</ul>`\n *\n * A thin front-end over the exact machinery JSX uses — the runtime paths are\n * IDENTICAL. Text holes go through the JSX child pipeline (`_toSegment`):\n * SafeHtml/list-segment passthrough (so `each()` composes and the keyed\n * reconciler owns its rows), string escaping, number stringify, boolean /\n * nullish → nothing, signal → fine-grained text binding, DOM-node and\n * unsupported-type errors. Attribute holes go through the JSX attribute\n * branch: signal → `_assertEmittableAttrName` + `bindAttr` (grouped into one\n * marker attribute per element); anything else → the JSX attribute renderer\n * (booleans, SafeHtml, URL screening, `on*` / malformed-name rejection).\n * `mount()` / `morph()` / the reconcilers need zero changes.\n *\n * Unlike JSX, NO camelCase attribute aliasing is applied — template authors\n * write real HTML attribute names (`class`, not `className`).\n *\n * Hole contract (enforced by the parser in `utils/templateParse.ts`): holes\n * are allowed in text/child positions and as a COMPLETE attribute value\n * (`attr=${v}` or `attr=\"${v}\"`). Tag-name holes, attribute-name holes,\n * partial attribute values (`class=\"a ${b}\"`), and holes inside comments\n * throw. Static chunks are author-written markup and pass through verbatim\n * (same trust model as JSX tags/attrs).\n *\n * Perf: the parse runs once per template call site — the tagged-template\n * strings array has stable identity, so a `WeakMap` keyed on it caches the\n * `ParsedTemplate`. Rendering is a chunk walk with string concatenation,\n * the same cost shape as the JSX runtime.\n */\n\nimport { bindAttr, bindMarkerAttr, isSignal } from './bindings.js';\nimport {\n _assertEmittableAttrName,\n _renderAttrVerbatim,\n _toSegment,\n SafeHtml,\n} from './jsx-runtime.js';\nimport type { ReadonlySignal, Signal } from './reactive.js';\nimport { mergeChildSegments, type Segment } from './segment.js';\nimport { type ParsedTemplate, parseTemplate } from './utils/templateParse.js';\n\n/**\n * Values accepted in `html\\`\\`` holes — the same set JSX accepts for\n * children (text holes) and attribute values (attr holes), including a\n * signal/`computed` itself for a fine-grained binding.\n */\nexport type HtmlValue =\n | SafeHtml\n | string\n | number\n | boolean\n | null\n | undefined\n | ReadonlySignal<unknown>\n | readonly HtmlValue[];\n\nconst PARSE_CACHE = new WeakMap<TemplateStringsArray, ParsedTemplate>();\n\n// Module-level mutable counter, sanctioned as observability-only state (see\n// CLAUDE.md design rule 5): it feeds the `_parseCount()` test hook that pins\n// the parse-once-per-callsite cache behavior and never influences rendering.\nlet parseCount = 0;\n\n/** Test hook: number of template parses performed (cache misses) so far. */\nexport function _parseCount(): number {\n return parseCount;\n}\n\nfunction getParsed(strings: TemplateStringsArray): ParsedTemplate {\n let parsed = PARSE_CACHE.get(strings);\n if (parsed === undefined) {\n parsed = parseTemplate(strings);\n parseCount++;\n PARSE_CACHE.set(strings, parsed);\n }\n return parsed;\n}\n\nexport function html(strings: TemplateStringsArray, ...values: HtmlValue[]): SafeHtml {\n const { chunks, holes, tagClose } = getParsed(strings);\n const parts: Segment[] = [];\n let buf = '';\n // Signal-attr binding ids for the currently-open element; flushed as one\n // marker attribute (`data-kfb` / `data-kfbrow`) at the tag's closing `>`,\n // mirroring the per-element grouping in `jsx()`.\n let pendingBindIds: string[] | null = null;\n\n for (let i = 0; i < chunks.length; i++) {\n let chunk = chunks[i];\n if (pendingBindIds !== null && tagClose[i] !== -1) {\n const off = tagClose[i];\n chunk = `${chunk.slice(0, off)} ${bindMarkerAttr()}=\"${pendingBindIds.join(',')}\"${chunk.slice(off)}`;\n pendingBindIds = null;\n }\n buf += chunk;\n if (i === holes.length) break;\n\n const hole = holes[i];\n const value = values[i];\n if (hole.kind === 'text') {\n const seg = _toSegment(value);\n if (seg.kind === 'static') {\n buf += seg.html;\n } else {\n parts.push({ kind: 'static', html: buf });\n buf = '';\n parts.push(seg);\n }\n } else if (isSignal(value)) {\n // Same contract as the `jsx()` signal-attribute branch: reject `on*` /\n // malformed names unconditionally (a signal's value changes over time),\n // then register the binding — or snapshot outside a mount render.\n _assertEmittableAttrName(hole.name, hole.name, false);\n const id = bindAttr(hole.name, value as Signal<unknown>);\n if (id !== null) {\n (pendingBindIds ??= []).push(id);\n } else {\n buf += _renderAttrVerbatim(hole.name, (value as Signal<unknown>).value);\n }\n } else {\n buf += _renderAttrVerbatim(hole.name, value);\n }\n }\n if (buf !== '' || parts.length === 0) parts.push({ kind: 'static', html: buf });\n return new SafeHtml(mergeChildSegments(parts));\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/utils/parseTemplate.ts","../src/html.ts"],"names":[],"mappings":"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","file":"html.js","sourcesContent":["/**\n * Static-parts parser for the `kerfjs/html` tagged template (`html\\`\\``).\n *\n * Parses a template's static string chunks ONCE into a `ParsedTemplate`:\n * the (possibly trimmed) chunks plus an ordered hole-descriptor list that\n * classifies each `${…}` hole as a TEXT hole (a child position) or an ATTR\n * hole (a complete attribute value, `attr=${v}` / `attr=\"${v}\"`). The\n * renderer in `src/html.ts` caches the parse per template-strings-array\n * identity, so repeated renders of the same call site skip this entirely.\n *\n * The state machine is deliberately small and strict: it tracks only\n * text / inside-open-tag / inside-comment modes and quote state. Holes in\n * any position it can't prove safe — tag names, attribute names, partial\n * attribute values, comments — THROW with an actionable message rather\n * than guessing. Static chunks are author-written markup and pass through\n * verbatim (the same trust model as JSX tag/attribute names).\n */\n\nexport interface AttrHole {\n kind: 'attr';\n /** The attribute name scanned from the preceding static chunk, verbatim. */\n name: string;\n /** The surrounding quote character, or null for an unquoted `attr=${v}`. */\n quote: '\"' | \"'\" | null;\n}\n\nexport interface TextHole {\n kind: 'text';\n}\n\nexport type TemplateHole = AttrHole | TextHole;\n\nexport interface ParsedTemplate {\n /**\n * Static chunks with attr-hole scaffolding (`name=`, quotes) stripped —\n * the renderer re-emits the whole ` name=\"value\"` via the JSX attribute\n * renderer. Always `holes.length + 1` entries.\n */\n chunks: string[];\n holes: TemplateHole[];\n /**\n * Per-chunk offset of the `>` that closes the element tag open at the\n * chunk's start, or -1. Only chunks that follow an attr hole can start\n * inside a tag; the renderer uses this to inject the bound-attribute\n * marker (`data-kfb` / `data-kfbrow`) for signal attribute holes.\n */\n tagClose: number[];\n}\n\nconst TEXT_HOLE: TextHole = { kind: 'text' };\n\n/** Matches a chunk tail of `name=` optionally followed by an opening quote. */\nconst ATTR_TAIL = /(\\s*)([^\\s\"'<>/=]+)=([\"'])?$/;\n\n/** Keep the actionable end of a malformed attribute without echoing an entire template. */\nconst PARTIAL_VALUE_DIAGNOSTIC_TAIL_LENGTH = 30;\n\nfunction holeError(detail: string): Error {\n return new Error(`html\\`\\`: ${detail}`);\n}\n\nfunction partialValueError(tail: string): Error {\n return holeError(\n `partial attribute values are not supported (near ${JSON.stringify(tail.slice(-PARTIAL_VALUE_DIAGNOSTIC_TAIL_LENGTH))}) — `\n + 'a hole must be the COMPLETE attribute value: attr=${v} or attr=\"${v}\". '\n + 'For class=\"a ${b}\"-style composition, build the full string first '\n + '(a plain template literal, or computed(() => `a ${b.value}`) for a bound attribute).',\n );\n}\n\nexport function parseTemplate(strings: readonly string[]): ParsedTemplate {\n const chunks: string[] = new Array(strings.length);\n const holes: TemplateHole[] = new Array(strings.length - 1);\n const tagClose: number[] = new Array<number>(strings.length).fill(-1);\n let mode: 'text' | 'tag' | 'comment' = 'text';\n let quote: '\"' | \"'\" | null = null;\n\n for (let i = 0; i < strings.length; i++) {\n let s = strings[i];\n // A quoted attr hole (`attr=\"${v}\"`) must be immediately closed by its\n // quote — the renderer emits the full quoted attribute itself, so the\n // author's closing quote is consumed here.\n if (i > 0) {\n const prev = holes[i - 1];\n if (prev.kind === 'attr' && prev.quote !== null) {\n if (s[0] !== prev.quote) throw partialValueError(strings[i - 1]);\n s = s.slice(1);\n }\n }\n\n const startedInTag = mode === 'tag';\n for (let j = 0; j < s.length; j++) {\n const ch = s[j];\n if (mode === 'text') {\n if (ch === '<') {\n if (s.startsWith('<!--', j)) {\n mode = 'comment';\n j += 3;\n } else if (j + 1 < s.length && /[a-zA-Z!/?]/.test(s[j + 1])) {\n mode = 'tag';\n }\n }\n } else if (mode === 'tag') {\n if (quote !== null) {\n if (ch === quote) quote = null;\n } else if (ch === '\"' || ch === \"'\") {\n quote = ch;\n } else if (ch === '>') {\n if (startedInTag && tagClose[i] === -1) tagClose[i] = j;\n mode = 'text';\n }\n } else if (ch === '-' && s.startsWith('-->', j)) {\n mode = 'text';\n j += 2;\n }\n }\n\n // Classify the hole that follows this chunk (no hole after the last one).\n if (i < strings.length - 1) {\n if (mode === 'comment') {\n throw holeError(\n 'holes inside HTML comments are not supported — move the ${…} hole outside the <!-- --> comment.',\n );\n }\n if (mode === 'text') {\n if (/<\\/?$/.test(s)) {\n throw holeError(\n 'tag-name holes (`<${…}>`) are not supported — write tag names statically. '\n + 'For a literal \"<\" before a hole, escape it as <.',\n );\n }\n holes[i] = TEXT_HOLE;\n } else {\n const m = ATTR_TAIL.exec(s);\n if (quote !== null) {\n // Inside a quoted attribute value: only valid if the quote opened\n // as the chunk's very last character (i.e. the hole IS the value).\n if (m === null || m[3] === undefined) throw partialValueError(s);\n holes[i] = { kind: 'attr', name: m[2], quote: m[3] as '\"' | \"'\" };\n s = s.slice(0, s.length - m[0].length);\n quote = null;\n } else if (m !== null && m[3] === undefined) {\n // Unquoted `attr=${v}`: the next static chunk must resume with an\n // attribute delimiter, or the template may simply end there.\n const next = strings[i + 1];\n const validNext = next.length > 0 ? /^[\\s>/]/.test(next) : i + 1 === strings.length - 1;\n if (!validNext) throw partialValueError(s);\n holes[i] = { kind: 'attr', name: m[2], quote: null };\n s = s.slice(0, s.length - m[0].length);\n } else if (/<\\/?$/.test(s)) {\n // The tag opened as the chunk's last characters (`</${…}`).\n throw holeError(\n 'tag-name holes (`<${…}>`) are not supported — write tag names statically. '\n + 'For a literal \"<\" before a hole, escape it as <.',\n );\n } else {\n throw holeError(\n 'a hole inside a tag must be a complete attribute value — attr=${…} or attr=\"${…}\". '\n + 'Tag-name and attribute-name holes are not supported; write those statically.',\n );\n }\n }\n }\n chunks[i] = s;\n }\n return { chunks, holes, tagClose };\n}\n","/**\n * `html` — tagged-template authoring at the `kerfjs/html` subpath, so\n * \"no build step\" is literally true: a CDN / importmap consumer can author\n * kerf UIs without a JSX transform.\n *\n * import { html } from 'kerfjs/html';\n * html`<div class=\"${cls}\">Count: ${count}</div>`\n * html`<ul>${each(items.value, (i) => html`<li id=\"${i.id}\">${i.label}</li>`)}</ul>`\n *\n * A thin front-end over the exact machinery JSX uses — the runtime paths are\n * IDENTICAL. Text holes go through the JSX child pipeline (`_toSegment`):\n * SafeHtml/list-segment passthrough (so `each()` composes and the keyed\n * reconciler owns its rows), string escaping, number stringify, boolean /\n * nullish → nothing, signal → fine-grained text binding, DOM-node and\n * unsupported-type errors. Attribute holes go through the JSX attribute\n * branch: signal → `_assertEmittableAttrName` + `bindAttr` (grouped into one\n * marker attribute per element); anything else → the JSX attribute renderer\n * (booleans, SafeHtml, URL screening, `on*` / malformed-name rejection).\n * `mount()` / `morph()` / the reconcilers need zero changes.\n *\n * Unlike JSX, NO camelCase attribute aliasing is applied — template authors\n * write real HTML attribute names (`class`, not `className`).\n *\n * Hole contract (enforced by the parser in `utils/parseTemplate.ts`): holes\n * are allowed in text/child positions and as a COMPLETE attribute value\n * (`attr=${v}` or `attr=\"${v}\"`). Tag-name holes, attribute-name holes,\n * partial attribute values (`class=\"a ${b}\"`), and holes inside comments\n * throw. Static chunks are author-written markup and pass through verbatim\n * (same trust model as JSX tags/attrs).\n *\n * Perf: the parse runs once per template call site — the tagged-template\n * strings array has stable identity, so a `WeakMap` keyed on it caches the\n * `ParsedTemplate`. Rendering is a chunk walk with string concatenation,\n * the same cost shape as the JSX runtime.\n */\n\nimport { bindAttr, bindMarkerAttr, isSignal } from './bindings.js';\nimport {\n _assertEmittableAttrName,\n _renderAttrVerbatim,\n _toSegment,\n SafeHtml,\n} from './jsx-runtime.js';\nimport type { ReadonlySignal, Signal } from './reactive.js';\nimport { mergeChildSegments, type Segment } from './segment.js';\nimport { type ParsedTemplate, parseTemplate } from './utils/parseTemplate.js';\n\n/**\n * Values accepted in `html\\`\\`` holes — the same set JSX accepts for\n * children (text holes) and attribute values (attr holes), including a\n * signal/`computed` itself for a fine-grained binding.\n */\nexport type HtmlValue =\n | SafeHtml\n | string\n | number\n | boolean\n | null\n | undefined\n | ReadonlySignal<unknown>\n | readonly HtmlValue[];\n\nconst PARSE_CACHE = new WeakMap<TemplateStringsArray, ParsedTemplate>();\n\n// Module-level mutable counter, sanctioned as observability-only state (see\n// CLAUDE.md design rule 5): it feeds the `_parseCount()` test hook that pins\n// the parse-once-per-callsite cache behavior and never influences rendering.\nlet parseCount = 0;\n\n/** Test hook: number of template parses performed (cache misses) so far. */\nexport function _parseCount(): number {\n return parseCount;\n}\n\nfunction getParsed(strings: TemplateStringsArray): ParsedTemplate {\n let parsed = PARSE_CACHE.get(strings);\n if (parsed === undefined) {\n parsed = parseTemplate(strings);\n parseCount++;\n PARSE_CACHE.set(strings, parsed);\n }\n return parsed;\n}\n\nexport function html(strings: TemplateStringsArray, ...values: HtmlValue[]): SafeHtml {\n const { chunks, holes, tagClose } = getParsed(strings);\n const parts: Segment[] = [];\n let buf = '';\n // Signal-attr binding ids for the currently-open element; flushed as one\n // marker attribute (`data-kfb` / `data-kfbrow`) at the tag's closing `>`,\n // mirroring the per-element grouping in `jsx()`.\n let pendingBindIds: string[] | null = null;\n\n for (let i = 0; i < chunks.length; i++) {\n let chunk = chunks[i];\n if (pendingBindIds !== null && tagClose[i] !== -1) {\n const off = tagClose[i];\n chunk = `${chunk.slice(0, off)} ${bindMarkerAttr()}=\"${pendingBindIds.join(',')}\"${chunk.slice(off)}`;\n pendingBindIds = null;\n }\n buf += chunk;\n if (i === holes.length) break;\n\n const hole = holes[i];\n const value = values[i];\n if (hole.kind === 'text') {\n const seg = _toSegment(value);\n if (seg.kind === 'static') {\n buf += seg.html;\n } else {\n parts.push({ kind: 'static', html: buf });\n buf = '';\n parts.push(seg);\n }\n } else if (isSignal(value)) {\n // Same contract as the `jsx()` signal-attribute branch: reject `on*` /\n // malformed names unconditionally (a signal's value changes over time),\n // then register the binding — or snapshot outside a mount render.\n _assertEmittableAttrName(hole.name, hole.name, false);\n const id = bindAttr(hole.name, value as Signal<unknown>);\n if (id !== null) {\n (pendingBindIds ??= []).push(id);\n } else {\n buf += _renderAttrVerbatim(hole.name, (value as Signal<unknown>).value);\n }\n } else {\n buf += _renderAttrVerbatim(hole.name, value);\n }\n }\n if (buf !== '' || parts.length === 0) parts.push({ kind: 'static', html: buf });\n return new SafeHtml(mergeChildSegments(parts));\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as AttrSpec, a as attr } from './
|
|
1
|
+
export { A as AttrSpec, a as attr } from './attr-Cmu2ZoGO.js';
|
|
2
2
|
export { D as DelegateOptions, d as delegate, a as delegateCapture } from './delegate-CL9VTZFb.js';
|
|
3
3
|
import { ArraySignal } from './array-signal.js';
|
|
4
4
|
import { SafeHtml } from './jsx-runtime.js';
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
export { each, morph, mount } from './chunk-
|
|
2
|
-
export { defineStore, resetAllStores } from './chunk-
|
|
3
|
-
export { attr } from './chunk-
|
|
4
|
-
export { delegate, delegateCapture } from './chunk-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export { Fragment, SafeHtml, isSafeHtml, raw } from './chunk-
|
|
8
|
-
export { batch, computed, effect, signal } from './chunk-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
export { each, morph, mount } from './chunk-SVATPF5R.js';
|
|
2
|
+
export { defineStore, resetAllStores } from './chunk-KZJXHFIB.js';
|
|
3
|
+
export { attr } from './chunk-UZJ6I4T6.js';
|
|
4
|
+
export { delegate, delegateCapture } from './chunk-KPXIOG2C.js';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export { Fragment, SafeHtml, isSafeHtml, raw } from './chunk-ZDCJZCNO.js';
|
|
8
|
+
export { batch, computed, effect, signal } from './chunk-U6FK33SG.js';
|
|
9
|
+
|
|
10
|
+
|
|
11
11
|
|
|
12
12
|
// src/renderDocument.ts
|
|
13
13
|
function renderDocument(node, options = {}) {
|
|
@@ -104,5 +104,5 @@ function toElement(jsx) {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
export { renderDocument, toElement };
|
|
107
|
-
|
|
107
|
+
|
|
108
108
|
//# sourceMappingURL=index.js.map
|
package/dist/jsx-runtime.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { Fragment, SafeHtml, assertEmittableAttrName as _assertEmittableAttrName, _renderAttrVerbatim, _toSegment, granularListSafeHtml, isSafeHtml, jsx, jsx as jsxDEV, jsx as jsxs, listSafeHtml, raw } from './chunk-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
export { Fragment, SafeHtml, assertEmittableAttrName as _assertEmittableAttrName, _renderAttrVerbatim, _toSegment, granularListSafeHtml, isSafeHtml, jsx, jsx as jsxDEV, jsx as jsxs, listSafeHtml, raw } from './chunk-ZDCJZCNO.js';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
6
|
//# sourceMappingURL=jsx-runtime.js.map
|
package/dist/list.d.ts
CHANGED
|
@@ -57,7 +57,7 @@ type BindListHandle = (() => void) & {
|
|
|
57
57
|
};
|
|
58
58
|
/** Options for {@link bindList}. */
|
|
59
59
|
interface BindListOptions<T> {
|
|
60
|
-
/** Stable per-row key.
|
|
60
|
+
/** Stable, unique per-row key. Duplicate keys are rejected before DOM mutation. */
|
|
61
61
|
key: (item: T) => ListKey;
|
|
62
62
|
/**
|
|
63
63
|
* Build a row. Two modes, chosen per call by what you return:
|