@vielzeug/ore 3.0.1 → 26.10.1
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/README.md +1 -1
- package/dist/base-element.cjs +1 -1
- package/dist/base-element.cjs.map +1 -1
- package/dist/base-element.d.ts +2 -2
- package/dist/base-element.d.ts.map +1 -1
- package/dist/base-element.js +1 -1
- package/dist/base-element.js.map +1 -1
- package/dist/component-types.d.ts +2 -2
- package/dist/context.cjs.map +1 -1
- package/dist/context.d.ts +2 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js.map +1 -1
- package/dist/define.cjs +1 -1
- package/dist/define.cjs.map +1 -1
- package/dist/define.d.ts +7 -4
- package/dist/define.d.ts.map +1 -1
- package/dist/define.js +1 -1
- package/dist/define.js.map +1 -1
- package/dist/directives/classMap.cjs.map +1 -1
- package/dist/directives/classMap.d.ts +2 -1
- package/dist/directives/classMap.d.ts.map +1 -1
- package/dist/directives/classMap.js.map +1 -1
- package/dist/directives/each.cjs.map +1 -1
- package/dist/directives/each.d.ts +5 -4
- package/dist/directives/each.d.ts.map +1 -1
- package/dist/directives/each.js.map +1 -1
- package/dist/directives/live.cjs.map +1 -1
- package/dist/directives/live.d.ts +1 -1
- package/dist/directives/live.js.map +1 -1
- package/dist/directives/when.cjs.map +1 -1
- package/dist/directives/when.d.ts +1 -2
- package/dist/directives/when.d.ts.map +1 -1
- package/dist/directives/when.js.map +1 -1
- package/dist/errors.cjs +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.ts +10 -10
- package/dist/errors.js +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/forms/field.cjs.map +1 -1
- package/dist/forms/field.d.ts +4 -4
- package/dist/forms/field.js.map +1 -1
- package/dist/host-bind.cjs.map +1 -1
- package/dist/host-bind.d.ts +4 -4
- package/dist/host-bind.d.ts.map +1 -1
- package/dist/host-bind.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/ore.cjs +2 -2
- package/dist/ore.cjs.map +1 -1
- package/dist/ore.iife.js +2 -2
- package/dist/ore.iife.js.map +1 -1
- package/dist/ore.js +2 -2
- package/dist/ore.js.map +1 -1
- package/dist/props.cjs.map +1 -1
- package/dist/props.d.ts +3 -3
- package/dist/props.js.map +1 -1
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.d.ts +5 -5
- package/dist/runtime.js.map +1 -1
- package/dist/slots.cjs.map +1 -1
- package/dist/slots.d.ts +3 -3
- package/dist/slots.js.map +1 -1
- package/dist/template/binding-types.d.ts +2 -2
- package/dist/template/bindings.cjs.map +1 -1
- package/dist/template/bindings.d.ts +2 -2
- package/dist/template/bindings.js.map +1 -1
- package/dist/template/compiler.cjs.map +1 -1
- package/dist/template/compiler.d.ts +1 -1
- package/dist/template/compiler.js.map +1 -1
- package/dist/template/instantiator.cjs.map +1 -1
- package/dist/template/instantiator.d.ts +1 -1
- package/dist/template/instantiator.js.map +1 -1
- package/dist/template/result.cjs.map +1 -1
- package/dist/template/result.d.ts +4 -4
- package/dist/template/result.js.map +1 -1
- package/dist/testing/dom.cjs.map +1 -1
- package/dist/testing/dom.d.ts +3 -3
- package/dist/testing/dom.js.map +1 -1
- package/dist/testing/flush.cjs +1 -1
- package/dist/testing/flush.cjs.map +1 -1
- package/dist/testing/flush.d.ts +4 -4
- package/dist/testing/flush.js +1 -1
- package/dist/testing/flush.js.map +1 -1
- package/dist/testing/form-internals-polyfill.cjs.map +1 -1
- package/dist/testing/form-internals-polyfill.d.ts +2 -2
- package/dist/testing/form-internals-polyfill.js.map +1 -1
- package/dist/testing/mount.cjs.map +1 -1
- package/dist/testing/mount.d.ts +3 -3
- package/dist/testing/mount.d.ts.map +1 -1
- package/dist/testing/mount.js.map +1 -1
- package/dist/testing/render-hook.cjs.map +1 -1
- package/dist/testing/render-hook.d.ts +1 -1
- package/dist/testing/render-hook.js.map +1 -1
- package/dist/testing/reset.cjs.map +1 -1
- package/dist/testing/reset.d.ts +2 -2
- package/dist/testing/reset.js.map +1 -1
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.js.map +1 -1
- package/dist/utils/brand.cjs.map +1 -1
- package/dist/utils/brand.d.ts +2 -2
- package/dist/utils/brand.js.map +1 -1
- package/dist/utils/dom.cjs.map +1 -1
- package/dist/utils/dom.d.ts +11 -5
- package/dist/utils/dom.d.ts.map +1 -1
- package/dist/utils/dom.js.map +1 -1
- package/dist/utils/emit.cjs +1 -1
- package/dist/utils/emit.cjs.map +1 -1
- package/dist/utils/emit.d.ts +9 -6
- package/dist/utils/emit.d.ts.map +1 -1
- package/dist/utils/emit.js +1 -1
- package/dist/utils/emit.js.map +1 -1
- package/package.json +9 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compiler.cjs","names":[],"sources":["../../src/template/compiler.ts"],"sourcesContent":["/**\n * template/compiler.ts — HTML template string parser and static template cache.\n *\n * Responsibilities:\n * - Parse TemplateStringsArray into slot metadata (slot detection).\n * - Build a cached HTMLTemplateElement with path indices for efficient node lookup.\n * - Expose `getStaticTemplate()` for use by the instantiator.\n */\n\nimport { ORE_ERRORS, OreApiError } from '../errors';\n\n// ─── Slot kinds ───────────────────────────────────────────────────────────────\n// Const object + derived union, same pattern as `ComponentPhase`/`LIFECYCLE_EVENTS`\n// in types.ts — used here (rather than plain string literals) because the kind\n// crosses a module boundary (compiler.ts produces it, instantiator.ts consumes\n// it): importing `SlotKind` gives autocomplete and a single rename point at the\n// consuming site, where a bare string literal wouldn't.\n\nexport const SlotKind = {\n ATTR: 'attr',\n BOOL_ATTR: 'boolAttr',\n EVENT: 'event',\n NODE: 'node',\n REF: 'ref',\n} as const;\n\nexport type DetectedSlotKind = (typeof SlotKind)[keyof typeof SlotKind];\n\ntype DetectedSlot = {\n kind: DetectedSlotKind;\n name?: string;\n prefix: string;\n};\n\n// ─── Static template types ────────────────────────────────────────────────────\n\nexport type NodePath = readonly number[];\n\nexport type SlotMeta = {\n commentId?: number;\n elementId?: number;\n kind: DetectedSlotKind;\n mode?: 'attr' | 'bool';\n name?: string;\n};\n\nexport type CompiledStaticTemplate = {\n commentPaths: ReadonlyMap<number, NodePath>;\n element: HTMLTemplateElement;\n elementPaths: ReadonlyMap<number, NodePath>;\n slots: SlotMeta[];\n};\n\n// ─── Slot detection regexes ───────────────────────────────────────────────────\n\nconst EVENT_RE = /\\s+@([a-zA-Z_][-a-zA-Z0-9_.-]*)\\s*=\\s*[\"']?$/;\nconst REF_RE = /\\s+ref\\s*=\\s*[\"']?$/;\nconst BOOL_ATTR_RE = /\\s+\\?([a-zA-Z_][-a-zA-Z0-9_]*)\\s*=\\s*[\"']?$/;\nconst ATTR_RE = /\\s+([a-zA-Z_][-a-zA-Z0-9_]*)\\s*=\\s*[\"']?$/;\n\nconst detectSlot = (str: string): DetectedSlot => {\n let m: RegExpExecArray | null;\n\n if ((m = EVENT_RE.exec(str))) {\n const prefix = str.slice(0, -m[0].length);\n const [name, ...modifiers] = m[1].split('.');\n\n if (modifiers.length > 0) throw new OreApiError(ORE_ERRORS.eventModifiersUnsupported(m[1]));\n\n return { kind: SlotKind.EVENT, name, prefix };\n }\n\n if ((m = REF_RE.exec(str))) {\n return { kind: SlotKind.REF, prefix: str.slice(0, -m[0].length) };\n }\n\n if ((m = BOOL_ATTR_RE.exec(str))) {\n return { kind: SlotKind.BOOL_ATTR, name: m[1], prefix: str.slice(0, -m[0].length) };\n }\n\n if ((m = ATTR_RE.exec(str))) {\n return { kind: SlotKind.ATTR, name: m[1], prefix: str.slice(0, -m[0].length) };\n }\n\n const lastOpen = str.lastIndexOf('<');\n const lastClose = str.lastIndexOf('>');\n\n if (lastOpen > lastClose && str[lastOpen + 1] !== '/') {\n throw new OreApiError(ORE_ERRORS.templateInterpolationInTag);\n }\n\n return { kind: SlotKind.NODE, prefix: str };\n};\n\n// ─── Static template cache ────────────────────────────────────────────────────\n\nconst templateCache = new WeakMap<TemplateStringsArray, CompiledStaticTemplate>();\n\n/**\n * Matches a string that ends in an attribute-assignment context, e.g. `...attr=`,\n * `...@click=`, `...?disabled=`. Used together with tag-context\n * tracking (see below) to decide whether a quote immediately before an\n * interpolation is an attribute-value quote (strip it) or a literal text quote\n * (keep it) — previously every adjacent quote was stripped, so both\n * `` html`\"${value}\"` `` and prose like `area = \"${area}\"` lost their quotes.\n */\nconst ATTR_VALUE_CONTEXT_RE = /[@?]?[a-zA-Z_][-a-zA-Z0-9_.]*\\s*=\\s*$/;\n\n/**\n * Pre-process template strings to strip surrounding attribute quotes. This lets\n * the main loop operate on clean strings with no per-iteration state flags.\n *\n * Quote stripping requires BOTH an attr-assignment tail AND start-tag context\n * (tracked by replaying the raw strings): `class = \"${c}\"` inside a tag is\n * stripped; `area = \"${a}\"` in prose is not.\n */\nconst normalizeTemplateStrings = (strings: TemplateStringsArray): string[] => {\n const out = Array.from(strings);\n let insideTag = false;\n\n for (let i = 0; i < out.length - 1; i++) {\n const s = out[i];\n const lastChar = s[s.length - 1];\n\n // Tag context at the interpolation boundary is determined by all raw string\n // content up to it — including this string's own text before its final quote\n // (last angle bracket wins; attribute values containing '<'/'>' are outside\n // the supported syntax either way).\n for (const ch of s) {\n if (ch === '<') insideTag = true;\n else if (ch === '>') insideTag = false;\n }\n\n // Strip wrapping attribute quotes: attr=\"${value}\" → attr=${value}\n if ((lastChar === '\"' || lastChar === \"'\") && insideTag && ATTR_VALUE_CONTEXT_RE.test(s.slice(0, -1))) {\n out[i] = s.slice(0, -1);\n\n const next = out[i + 1];\n\n if (next.startsWith(lastChar)) out[i + 1] = next.slice(1);\n }\n }\n\n return out;\n};\n\n/**\n * Attribute names that mark a binding target element, and the comment prefix for\n * node-slot anchors. Namespaced (`data-ore-*` / `ore:N`) so user-authored markup in\n * static template regions can never collide with them — a plain `u` attribute or a\n * numeric comment was previously hijacked as a binding marker and stripped.\n */\nconst ELEMENT_MARKER_ATTR = 'data-ore-b';\nconst COMMENT_MARKER_RE = /^ore:(\\d+)$/;\n\nconst walkNode = (\n node: Node,\n path: number[],\n elementPaths: Map<number, NodePath>,\n commentPaths: Map<number, NodePath>,\n): void => {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const el = node as Element;\n const marker = el.getAttribute(ELEMENT_MARKER_ATTR);\n\n if (marker !== null) {\n elementPaths.set(Number(marker), [...path]);\n el.removeAttribute(ELEMENT_MARKER_ATTR);\n }\n } else if (node.nodeType === Node.COMMENT_NODE) {\n const content = (node as Comment).nodeValue;\n const m = content !== null ? COMMENT_MARKER_RE.exec(content) : null;\n\n if (m) {\n commentPaths.set(Number(m[1]), [...path]);\n }\n }\n\n const children = node.childNodes;\n\n for (let i = 0; i < children.length; i++) walkNode(children[i], [...path, i], elementPaths, commentPaths);\n};\n\nconst buildStaticTemplate = (strings: TemplateStringsArray): CompiledStaticTemplate => {\n const normalized = normalizeTemplateStrings(strings);\n let html = '';\n let activeElementId: number | undefined;\n let elementCounter = 0;\n let commentCounter = 0;\n const slots: SlotMeta[] = [];\n\n for (let i = 0; i < normalized.length - 1; i++) {\n const raw = normalized[i];\n const slot = detectSlot(raw);\n\n if (slot.kind === SlotKind.NODE) {\n html += `${slot.prefix}<!--ore:${commentCounter}-->`;\n slots.push({ commentId: commentCounter, kind: SlotKind.NODE });\n commentCounter++;\n activeElementId = undefined;\n } else {\n const needsNewMarker =\n activeElementId === undefined || slot.prefix.lastIndexOf('<') > slot.prefix.lastIndexOf('>');\n\n if (needsNewMarker) {\n activeElementId = elementCounter++;\n html += `${slot.prefix} ${ELEMENT_MARKER_ATTR}=\"${activeElementId}\"`;\n } else {\n html += slot.prefix;\n }\n\n const mode: 'attr' | 'bool' | undefined =\n slot.kind === SlotKind.BOOL_ATTR ? 'bool' : slot.kind === SlotKind.ATTR ? 'attr' : undefined;\n\n slots.push({ elementId: activeElementId, kind: slot.kind, mode, name: slot.name });\n }\n }\n\n html += normalized[normalized.length - 1] ?? '';\n\n const tpl = document.createElement('template');\n\n tpl.innerHTML = html;\n\n const elementPaths = new Map<number, NodePath>();\n const commentPaths = new Map<number, NodePath>();\n const topChildren = tpl.content.childNodes;\n\n for (let i = 0; i < topChildren.length; i++) walkNode(topChildren[i], [i], elementPaths, commentPaths);\n\n return { commentPaths, element: tpl, elementPaths, slots };\n};\n\nexport const getStaticTemplate = (strings: TemplateStringsArray): CompiledStaticTemplate => {\n let tpl = templateCache.get(strings);\n\n if (!tpl) {\n tpl = buildStaticTemplate(strings);\n templateCache.set(strings, tpl);\n }\n\n return tpl;\n};\n\n// ─── Path navigation (used by instantiator) ───────────────────────────────────\n\nexport const followPath = (root: Node, path: NodePath): Node => {\n let node: Node = root;\n\n for (const i of path) node = node.childNodes[i];\n\n return node;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"compiler.cjs","names":[],"sources":["../../src/template/compiler.ts"],"sourcesContent":["/**\n * template/compiler.ts: HTML template string parser and static template cache.\n *\n * Responsibilities:\n * - Parse TemplateStringsArray into slot metadata (slot detection).\n * - Build a cached HTMLTemplateElement with path indices for efficient node lookup.\n * - Expose `getStaticTemplate()` for use by the instantiator.\n */\n\nimport { ORE_ERRORS, OreApiError } from '../errors';\n\n// ─── Slot kinds ───────────────────────────────────────────────────────────────\n// Const object + derived union, same pattern as `ComponentPhase`\n// in base-element.ts: used here (rather than plain string literals) because the kind\n// crosses a module boundary (compiler.ts produces it, instantiator.ts consumes\n// it): importing `SlotKind` gives autocomplete and a single rename point at the\n// consuming site, where a bare string literal wouldn't.\n\nexport const SlotKind = {\n ATTR: 'attr',\n BOOL_ATTR: 'boolAttr',\n EVENT: 'event',\n NODE: 'node',\n REF: 'ref',\n} as const;\n\nexport type DetectedSlotKind = (typeof SlotKind)[keyof typeof SlotKind];\n\ntype DetectedSlot = {\n kind: DetectedSlotKind;\n name?: string;\n prefix: string;\n};\n\n// ─── Static template types ────────────────────────────────────────────────────\n\nexport type NodePath = readonly number[];\n\nexport type SlotMeta = {\n commentId?: number;\n elementId?: number;\n kind: DetectedSlotKind;\n mode?: 'attr' | 'bool';\n name?: string;\n};\n\nexport type CompiledStaticTemplate = {\n commentPaths: ReadonlyMap<number, NodePath>;\n element: HTMLTemplateElement;\n elementPaths: ReadonlyMap<number, NodePath>;\n slots: SlotMeta[];\n};\n\n// ─── Slot detection regexes ───────────────────────────────────────────────────\n\nconst EVENT_RE = /\\s+@([a-zA-Z_][-a-zA-Z0-9_.-]*)\\s*=\\s*[\"']?$/;\nconst REF_RE = /\\s+ref\\s*=\\s*[\"']?$/;\nconst BOOL_ATTR_RE = /\\s+\\?([a-zA-Z_][-a-zA-Z0-9_]*)\\s*=\\s*[\"']?$/;\nconst ATTR_RE = /\\s+([a-zA-Z_][-a-zA-Z0-9_]*)\\s*=\\s*[\"']?$/;\n\nconst detectSlot = (str: string): DetectedSlot => {\n let m: RegExpExecArray | null;\n\n if ((m = EVENT_RE.exec(str))) {\n const prefix = str.slice(0, -m[0].length);\n const [name, ...modifiers] = m[1].split('.');\n\n if (modifiers.length > 0) throw new OreApiError(ORE_ERRORS.eventModifiersUnsupported(m[1]));\n\n return { kind: SlotKind.EVENT, name, prefix };\n }\n\n if ((m = REF_RE.exec(str))) {\n return { kind: SlotKind.REF, prefix: str.slice(0, -m[0].length) };\n }\n\n if ((m = BOOL_ATTR_RE.exec(str))) {\n return { kind: SlotKind.BOOL_ATTR, name: m[1], prefix: str.slice(0, -m[0].length) };\n }\n\n if ((m = ATTR_RE.exec(str))) {\n return { kind: SlotKind.ATTR, name: m[1], prefix: str.slice(0, -m[0].length) };\n }\n\n const lastOpen = str.lastIndexOf('<');\n const lastClose = str.lastIndexOf('>');\n\n if (lastOpen > lastClose && str[lastOpen + 1] !== '/') {\n throw new OreApiError(ORE_ERRORS.templateInterpolationInTag);\n }\n\n return { kind: SlotKind.NODE, prefix: str };\n};\n\n// ─── Static template cache ────────────────────────────────────────────────────\n\nconst templateCache = new WeakMap<TemplateStringsArray, CompiledStaticTemplate>();\n\n/**\n * Matches a string that ends in an attribute-assignment context, e.g. `...attr=`,\n * `...@click=`, `...?disabled=`. Used together with tag-context\n * tracking (see below) to decide whether a quote immediately before an\n * interpolation is an attribute-value quote (strip it) or a literal text quote\n * (keep it): previously every adjacent quote was stripped, so both\n * `` html`\"${value}\"` `` and prose like `area = \"${area}\"` lost their quotes.\n */\nconst ATTR_VALUE_CONTEXT_RE = /[@?]?[a-zA-Z_][-a-zA-Z0-9_.]*\\s*=\\s*$/;\n\n/**\n * Pre-process template strings to strip surrounding attribute quotes. This lets\n * the main loop operate on clean strings with no per-iteration state flags.\n *\n * Quote stripping requires BOTH an attr-assignment tail AND start-tag context\n * (tracked by replaying the raw strings): `class = \"${c}\"` inside a tag is\n * stripped; `area = \"${a}\"` in prose is not.\n */\nconst normalizeTemplateStrings = (strings: TemplateStringsArray): string[] => {\n const out = Array.from(strings);\n let insideTag = false;\n\n for (let i = 0; i < out.length - 1; i++) {\n const s = out[i];\n const lastChar = s[s.length - 1];\n\n // Tag context at the interpolation boundary is determined by all raw string\n // content up to it: including this string's own text before its final quote\n // (last angle bracket wins; attribute values containing '<'/'>' are outside\n // the supported syntax either way).\n for (const ch of s) {\n if (ch === '<') insideTag = true;\n else if (ch === '>') insideTag = false;\n }\n\n // Strip wrapping attribute quotes: attr=\"${value}\" → attr=${value}\n if ((lastChar === '\"' || lastChar === \"'\") && insideTag && ATTR_VALUE_CONTEXT_RE.test(s.slice(0, -1))) {\n out[i] = s.slice(0, -1);\n\n const next = out[i + 1];\n\n if (next.startsWith(lastChar)) out[i + 1] = next.slice(1);\n }\n }\n\n return out;\n};\n\n/**\n * Attribute names that mark a binding target element, and the comment prefix for\n * node-slot anchors. Namespaced (`data-ore-*` / `ore:N`) so user-authored markup in\n * static template regions can never collide with them: a plain `u` attribute or a\n * numeric comment was previously hijacked as a binding marker and stripped.\n */\nconst ELEMENT_MARKER_ATTR = 'data-ore-b';\nconst COMMENT_MARKER_RE = /^ore:(\\d+)$/;\n\nconst walkNode = (\n node: Node,\n path: number[],\n elementPaths: Map<number, NodePath>,\n commentPaths: Map<number, NodePath>,\n): void => {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const el = node as Element;\n const marker = el.getAttribute(ELEMENT_MARKER_ATTR);\n\n if (marker !== null) {\n elementPaths.set(Number(marker), [...path]);\n el.removeAttribute(ELEMENT_MARKER_ATTR);\n }\n } else if (node.nodeType === Node.COMMENT_NODE) {\n const content = (node as Comment).nodeValue;\n const m = content !== null ? COMMENT_MARKER_RE.exec(content) : null;\n\n if (m) {\n commentPaths.set(Number(m[1]), [...path]);\n }\n }\n\n const children = node.childNodes;\n\n for (let i = 0; i < children.length; i++) walkNode(children[i], [...path, i], elementPaths, commentPaths);\n};\n\nconst buildStaticTemplate = (strings: TemplateStringsArray): CompiledStaticTemplate => {\n const normalized = normalizeTemplateStrings(strings);\n let html = '';\n let activeElementId: number | undefined;\n let elementCounter = 0;\n let commentCounter = 0;\n const slots: SlotMeta[] = [];\n\n for (let i = 0; i < normalized.length - 1; i++) {\n const raw = normalized[i];\n const slot = detectSlot(raw);\n\n if (slot.kind === SlotKind.NODE) {\n html += `${slot.prefix}<!--ore:${commentCounter}-->`;\n slots.push({ commentId: commentCounter, kind: SlotKind.NODE });\n commentCounter++;\n activeElementId = undefined;\n } else {\n const needsNewMarker =\n activeElementId === undefined || slot.prefix.lastIndexOf('<') > slot.prefix.lastIndexOf('>');\n\n if (needsNewMarker) {\n activeElementId = elementCounter++;\n html += `${slot.prefix} ${ELEMENT_MARKER_ATTR}=\"${activeElementId}\"`;\n } else {\n html += slot.prefix;\n }\n\n const mode: 'attr' | 'bool' | undefined =\n slot.kind === SlotKind.BOOL_ATTR ? 'bool' : slot.kind === SlotKind.ATTR ? 'attr' : undefined;\n\n slots.push({ elementId: activeElementId, kind: slot.kind, mode, name: slot.name });\n }\n }\n\n html += normalized[normalized.length - 1] ?? '';\n\n const tpl = document.createElement('template');\n\n tpl.innerHTML = html;\n\n const elementPaths = new Map<number, NodePath>();\n const commentPaths = new Map<number, NodePath>();\n const topChildren = tpl.content.childNodes;\n\n for (let i = 0; i < topChildren.length; i++) walkNode(topChildren[i], [i], elementPaths, commentPaths);\n\n return { commentPaths, element: tpl, elementPaths, slots };\n};\n\nexport const getStaticTemplate = (strings: TemplateStringsArray): CompiledStaticTemplate => {\n let tpl = templateCache.get(strings);\n\n if (!tpl) {\n tpl = buildStaticTemplate(strings);\n templateCache.set(strings, tpl);\n }\n\n return tpl;\n};\n\n// ─── Path navigation (used by instantiator) ───────────────────────────────────\n\nexport const followPath = (root: Node, path: NodePath): Node => {\n let node: Node = root;\n\n for (const i of path) node = node.childNodes[i];\n\n return node;\n};\n"],"mappings":"iCAkBA,IAAa,EAAW,CACtB,KAAM,OACN,UAAW,WACX,MAAO,QACP,KAAM,OACN,IAAK,KACP,EA+BM,EAAW,+CACX,EAAS,sBACT,EAAe,8CACf,EAAU,4CAEV,EAAc,GAA8B,CAChD,IAAI,EAEJ,GAAK,EAAI,EAAS,KAAK,CAAG,EAAI,CAC5B,IAAM,EAAS,EAAI,MAAM,EAAG,CAAC,EAAE,EAAE,CAAC,MAAM,EAClC,CAAC,EAAM,GAAG,GAAa,EAAE,EAAE,CAAC,MAAM,GAAG,EAE3C,GAAI,EAAU,OAAS,EAAG,MAAM,IAAI,EAAA,YAAY,EAAA,WAAW,0BAA0B,EAAE,EAAE,CAAC,EAE1F,MAAO,CAAE,KAAM,EAAS,MAAO,OAAM,QAAO,CAC9C,CAEA,GAAK,EAAI,EAAO,KAAK,CAAG,EACtB,MAAO,CAAE,KAAM,EAAS,IAAK,OAAQ,EAAI,MAAM,EAAG,CAAC,EAAE,EAAE,CAAC,MAAM,CAAE,EAGlE,GAAK,EAAI,EAAa,KAAK,CAAG,EAC5B,MAAO,CAAE,KAAM,EAAS,UAAW,KAAM,EAAE,GAAI,OAAQ,EAAI,MAAM,EAAG,CAAC,EAAE,EAAE,CAAC,MAAM,CAAE,EAGpF,GAAK,EAAI,EAAQ,KAAK,CAAG,EACvB,MAAO,CAAE,KAAM,EAAS,KAAM,KAAM,EAAE,GAAI,OAAQ,EAAI,MAAM,EAAG,CAAC,EAAE,EAAE,CAAC,MAAM,CAAE,EAG/E,IAAM,EAAW,EAAI,YAAY,GAAG,EAGpC,GAAI,EAFc,EAAI,YAAY,GAEnB,GAAa,EAAI,EAAW,KAAO,IAChD,MAAM,IAAI,EAAA,YAAY,EAAA,WAAW,0BAA0B,EAG7D,MAAO,CAAE,KAAM,EAAS,KAAM,OAAQ,CAAI,CAC5C,EAIM,EAAgB,IAAI,QAUpB,EAAwB,wCAUxB,EAA4B,GAA4C,CAC5E,IAAM,EAAM,MAAM,KAAK,CAAO,EAC1B,EAAY,GAEhB,IAAK,IAAI,EAAI,EAAG,EAAI,EAAI,OAAS,EAAG,IAAK,CACvC,IAAM,EAAI,EAAI,GACR,EAAW,EAAE,EAAE,OAAS,GAM9B,IAAK,IAAM,KAAM,EACX,IAAO,IAAK,EAAY,GACnB,IAAO,MAAK,EAAY,IAInC,IAAK,IAAa,KAAO,IAAa,MAAQ,GAAa,EAAsB,KAAK,EAAE,MAAM,EAAG,EAAE,CAAC,EAAG,CACrG,EAAI,GAAK,EAAE,MAAM,EAAG,EAAE,EAEtB,IAAM,EAAO,EAAI,EAAI,GAEjB,EAAK,WAAW,CAAQ,IAAG,EAAI,EAAI,GAAK,EAAK,MAAM,CAAC,EAC1D,CACF,CAEA,OAAO,CACT,EAQM,EAAsB,aACtB,EAAoB,cAEpB,GACJ,EACA,EACA,EACA,IACS,CACT,GAAI,EAAK,WAAa,KAAK,aAAc,CACvC,IAAM,EAAK,EACL,EAAS,EAAG,aAAa,CAAmB,EAE9C,IAAW,OACb,EAAa,IAAI,OAAO,CAAM,EAAG,CAAC,GAAG,CAAI,CAAC,EAC1C,EAAG,gBAAgB,CAAmB,EAE1C,MAAO,GAAI,EAAK,WAAa,KAAK,aAAc,CAC9C,IAAM,EAAW,EAAiB,UAC5B,EAAI,IAAY,KAAyC,KAAlC,EAAkB,KAAK,CAAO,EAEvD,GACF,EAAa,IAAI,OAAO,EAAE,EAAE,EAAG,CAAC,GAAG,CAAI,CAAC,CAE5C,CAEA,IAAM,EAAW,EAAK,WAEtB,IAAK,IAAI,EAAI,EAAG,EAAI,EAAS,OAAQ,IAAK,EAAS,EAAS,GAAI,CAAC,GAAG,EAAM,CAAC,EAAG,EAAc,CAAY,CAC1G,EAEM,EAAuB,GAA0D,CACrF,IAAM,EAAa,EAAyB,CAAO,EAC/C,EAAO,GACP,EACA,EAAiB,EACjB,EAAiB,EACf,EAAoB,CAAC,EAE3B,IAAK,IAAI,EAAI,EAAG,EAAI,EAAW,OAAS,EAAG,IAAK,CAC9C,IAAM,EAAM,EAAW,GACjB,EAAO,EAAW,CAAG,EAE3B,GAAI,EAAK,OAAS,EAAS,KACzB,GAAQ,GAAG,EAAK,OAAO,UAAU,EAAe,KAChD,EAAM,KAAK,CAAE,UAAW,EAAgB,KAAM,EAAS,IAAK,CAAC,EAC7D,IACA,EAAkB,IAAA,OACb,CAEH,IAAoB,IAAA,IAAa,EAAK,OAAO,YAAY,GAAG,EAAI,EAAK,OAAO,YAAY,GAAG,GAG3F,EAAkB,IAClB,GAAQ,GAAG,EAAK,OAAO,GAAG,EAAoB,IAAI,EAAgB,IAElE,GAAQ,EAAK,OAGf,IAAM,EACJ,EAAK,OAAS,EAAS,UAAY,OAAS,EAAK,OAAS,EAAS,KAAO,OAAS,IAAA,GAErF,EAAM,KAAK,CAAE,UAAW,EAAiB,KAAM,EAAK,KAAM,OAAM,KAAM,EAAK,IAAK,CAAC,CACnF,CACF,CAEA,GAAQ,EAAW,EAAW,OAAS,IAAM,GAE7C,IAAM,EAAM,SAAS,cAAc,UAAU,EAE7C,EAAI,UAAY,EAEhB,IAAM,EAAe,IAAI,IACnB,EAAe,IAAI,IACnB,EAAc,EAAI,QAAQ,WAEhC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAY,OAAQ,IAAK,EAAS,EAAY,GAAI,CAAC,CAAC,EAAG,EAAc,CAAY,EAErG,MAAO,CAAE,eAAc,QAAS,EAAK,eAAc,OAAM,CAC3D,EAEa,EAAqB,GAA0D,CAC1F,IAAI,EAAM,EAAc,IAAI,CAAO,EAOnC,OALK,IACH,EAAM,EAAoB,CAAO,EACjC,EAAc,IAAI,EAAS,CAAG,GAGzB,CACT,EAIa,GAAc,EAAY,IAAyB,CAC9D,IAAI,EAAa,EAEjB,IAAK,IAAM,KAAK,EAAM,EAAO,EAAK,WAAW,GAE7C,OAAO,CACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compiler.js","names":[],"sources":["../../src/template/compiler.ts"],"sourcesContent":["/**\n * template/compiler.ts — HTML template string parser and static template cache.\n *\n * Responsibilities:\n * - Parse TemplateStringsArray into slot metadata (slot detection).\n * - Build a cached HTMLTemplateElement with path indices for efficient node lookup.\n * - Expose `getStaticTemplate()` for use by the instantiator.\n */\n\nimport { ORE_ERRORS, OreApiError } from '../errors';\n\n// ─── Slot kinds ───────────────────────────────────────────────────────────────\n// Const object + derived union, same pattern as `ComponentPhase`/`LIFECYCLE_EVENTS`\n// in types.ts — used here (rather than plain string literals) because the kind\n// crosses a module boundary (compiler.ts produces it, instantiator.ts consumes\n// it): importing `SlotKind` gives autocomplete and a single rename point at the\n// consuming site, where a bare string literal wouldn't.\n\nexport const SlotKind = {\n ATTR: 'attr',\n BOOL_ATTR: 'boolAttr',\n EVENT: 'event',\n NODE: 'node',\n REF: 'ref',\n} as const;\n\nexport type DetectedSlotKind = (typeof SlotKind)[keyof typeof SlotKind];\n\ntype DetectedSlot = {\n kind: DetectedSlotKind;\n name?: string;\n prefix: string;\n};\n\n// ─── Static template types ────────────────────────────────────────────────────\n\nexport type NodePath = readonly number[];\n\nexport type SlotMeta = {\n commentId?: number;\n elementId?: number;\n kind: DetectedSlotKind;\n mode?: 'attr' | 'bool';\n name?: string;\n};\n\nexport type CompiledStaticTemplate = {\n commentPaths: ReadonlyMap<number, NodePath>;\n element: HTMLTemplateElement;\n elementPaths: ReadonlyMap<number, NodePath>;\n slots: SlotMeta[];\n};\n\n// ─── Slot detection regexes ───────────────────────────────────────────────────\n\nconst EVENT_RE = /\\s+@([a-zA-Z_][-a-zA-Z0-9_.-]*)\\s*=\\s*[\"']?$/;\nconst REF_RE = /\\s+ref\\s*=\\s*[\"']?$/;\nconst BOOL_ATTR_RE = /\\s+\\?([a-zA-Z_][-a-zA-Z0-9_]*)\\s*=\\s*[\"']?$/;\nconst ATTR_RE = /\\s+([a-zA-Z_][-a-zA-Z0-9_]*)\\s*=\\s*[\"']?$/;\n\nconst detectSlot = (str: string): DetectedSlot => {\n let m: RegExpExecArray | null;\n\n if ((m = EVENT_RE.exec(str))) {\n const prefix = str.slice(0, -m[0].length);\n const [name, ...modifiers] = m[1].split('.');\n\n if (modifiers.length > 0) throw new OreApiError(ORE_ERRORS.eventModifiersUnsupported(m[1]));\n\n return { kind: SlotKind.EVENT, name, prefix };\n }\n\n if ((m = REF_RE.exec(str))) {\n return { kind: SlotKind.REF, prefix: str.slice(0, -m[0].length) };\n }\n\n if ((m = BOOL_ATTR_RE.exec(str))) {\n return { kind: SlotKind.BOOL_ATTR, name: m[1], prefix: str.slice(0, -m[0].length) };\n }\n\n if ((m = ATTR_RE.exec(str))) {\n return { kind: SlotKind.ATTR, name: m[1], prefix: str.slice(0, -m[0].length) };\n }\n\n const lastOpen = str.lastIndexOf('<');\n const lastClose = str.lastIndexOf('>');\n\n if (lastOpen > lastClose && str[lastOpen + 1] !== '/') {\n throw new OreApiError(ORE_ERRORS.templateInterpolationInTag);\n }\n\n return { kind: SlotKind.NODE, prefix: str };\n};\n\n// ─── Static template cache ────────────────────────────────────────────────────\n\nconst templateCache = new WeakMap<TemplateStringsArray, CompiledStaticTemplate>();\n\n/**\n * Matches a string that ends in an attribute-assignment context, e.g. `...attr=`,\n * `...@click=`, `...?disabled=`. Used together with tag-context\n * tracking (see below) to decide whether a quote immediately before an\n * interpolation is an attribute-value quote (strip it) or a literal text quote\n * (keep it) — previously every adjacent quote was stripped, so both\n * `` html`\"${value}\"` `` and prose like `area = \"${area}\"` lost their quotes.\n */\nconst ATTR_VALUE_CONTEXT_RE = /[@?]?[a-zA-Z_][-a-zA-Z0-9_.]*\\s*=\\s*$/;\n\n/**\n * Pre-process template strings to strip surrounding attribute quotes. This lets\n * the main loop operate on clean strings with no per-iteration state flags.\n *\n * Quote stripping requires BOTH an attr-assignment tail AND start-tag context\n * (tracked by replaying the raw strings): `class = \"${c}\"` inside a tag is\n * stripped; `area = \"${a}\"` in prose is not.\n */\nconst normalizeTemplateStrings = (strings: TemplateStringsArray): string[] => {\n const out = Array.from(strings);\n let insideTag = false;\n\n for (let i = 0; i < out.length - 1; i++) {\n const s = out[i];\n const lastChar = s[s.length - 1];\n\n // Tag context at the interpolation boundary is determined by all raw string\n // content up to it — including this string's own text before its final quote\n // (last angle bracket wins; attribute values containing '<'/'>' are outside\n // the supported syntax either way).\n for (const ch of s) {\n if (ch === '<') insideTag = true;\n else if (ch === '>') insideTag = false;\n }\n\n // Strip wrapping attribute quotes: attr=\"${value}\" → attr=${value}\n if ((lastChar === '\"' || lastChar === \"'\") && insideTag && ATTR_VALUE_CONTEXT_RE.test(s.slice(0, -1))) {\n out[i] = s.slice(0, -1);\n\n const next = out[i + 1];\n\n if (next.startsWith(lastChar)) out[i + 1] = next.slice(1);\n }\n }\n\n return out;\n};\n\n/**\n * Attribute names that mark a binding target element, and the comment prefix for\n * node-slot anchors. Namespaced (`data-ore-*` / `ore:N`) so user-authored markup in\n * static template regions can never collide with them — a plain `u` attribute or a\n * numeric comment was previously hijacked as a binding marker and stripped.\n */\nconst ELEMENT_MARKER_ATTR = 'data-ore-b';\nconst COMMENT_MARKER_RE = /^ore:(\\d+)$/;\n\nconst walkNode = (\n node: Node,\n path: number[],\n elementPaths: Map<number, NodePath>,\n commentPaths: Map<number, NodePath>,\n): void => {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const el = node as Element;\n const marker = el.getAttribute(ELEMENT_MARKER_ATTR);\n\n if (marker !== null) {\n elementPaths.set(Number(marker), [...path]);\n el.removeAttribute(ELEMENT_MARKER_ATTR);\n }\n } else if (node.nodeType === Node.COMMENT_NODE) {\n const content = (node as Comment).nodeValue;\n const m = content !== null ? COMMENT_MARKER_RE.exec(content) : null;\n\n if (m) {\n commentPaths.set(Number(m[1]), [...path]);\n }\n }\n\n const children = node.childNodes;\n\n for (let i = 0; i < children.length; i++) walkNode(children[i], [...path, i], elementPaths, commentPaths);\n};\n\nconst buildStaticTemplate = (strings: TemplateStringsArray): CompiledStaticTemplate => {\n const normalized = normalizeTemplateStrings(strings);\n let html = '';\n let activeElementId: number | undefined;\n let elementCounter = 0;\n let commentCounter = 0;\n const slots: SlotMeta[] = [];\n\n for (let i = 0; i < normalized.length - 1; i++) {\n const raw = normalized[i];\n const slot = detectSlot(raw);\n\n if (slot.kind === SlotKind.NODE) {\n html += `${slot.prefix}<!--ore:${commentCounter}-->`;\n slots.push({ commentId: commentCounter, kind: SlotKind.NODE });\n commentCounter++;\n activeElementId = undefined;\n } else {\n const needsNewMarker =\n activeElementId === undefined || slot.prefix.lastIndexOf('<') > slot.prefix.lastIndexOf('>');\n\n if (needsNewMarker) {\n activeElementId = elementCounter++;\n html += `${slot.prefix} ${ELEMENT_MARKER_ATTR}=\"${activeElementId}\"`;\n } else {\n html += slot.prefix;\n }\n\n const mode: 'attr' | 'bool' | undefined =\n slot.kind === SlotKind.BOOL_ATTR ? 'bool' : slot.kind === SlotKind.ATTR ? 'attr' : undefined;\n\n slots.push({ elementId: activeElementId, kind: slot.kind, mode, name: slot.name });\n }\n }\n\n html += normalized[normalized.length - 1] ?? '';\n\n const tpl = document.createElement('template');\n\n tpl.innerHTML = html;\n\n const elementPaths = new Map<number, NodePath>();\n const commentPaths = new Map<number, NodePath>();\n const topChildren = tpl.content.childNodes;\n\n for (let i = 0; i < topChildren.length; i++) walkNode(topChildren[i], [i], elementPaths, commentPaths);\n\n return { commentPaths, element: tpl, elementPaths, slots };\n};\n\nexport const getStaticTemplate = (strings: TemplateStringsArray): CompiledStaticTemplate => {\n let tpl = templateCache.get(strings);\n\n if (!tpl) {\n tpl = buildStaticTemplate(strings);\n templateCache.set(strings, tpl);\n }\n\n return tpl;\n};\n\n// ─── Path navigation (used by instantiator) ───────────────────────────────────\n\nexport const followPath = (root: Node, path: NodePath): Node => {\n let node: Node = root;\n\n for (const i of path) node = node.childNodes[i];\n\n return node;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"compiler.js","names":[],"sources":["../../src/template/compiler.ts"],"sourcesContent":["/**\n * template/compiler.ts: HTML template string parser and static template cache.\n *\n * Responsibilities:\n * - Parse TemplateStringsArray into slot metadata (slot detection).\n * - Build a cached HTMLTemplateElement with path indices for efficient node lookup.\n * - Expose `getStaticTemplate()` for use by the instantiator.\n */\n\nimport { ORE_ERRORS, OreApiError } from '../errors';\n\n// ─── Slot kinds ───────────────────────────────────────────────────────────────\n// Const object + derived union, same pattern as `ComponentPhase`\n// in base-element.ts: used here (rather than plain string literals) because the kind\n// crosses a module boundary (compiler.ts produces it, instantiator.ts consumes\n// it): importing `SlotKind` gives autocomplete and a single rename point at the\n// consuming site, where a bare string literal wouldn't.\n\nexport const SlotKind = {\n ATTR: 'attr',\n BOOL_ATTR: 'boolAttr',\n EVENT: 'event',\n NODE: 'node',\n REF: 'ref',\n} as const;\n\nexport type DetectedSlotKind = (typeof SlotKind)[keyof typeof SlotKind];\n\ntype DetectedSlot = {\n kind: DetectedSlotKind;\n name?: string;\n prefix: string;\n};\n\n// ─── Static template types ────────────────────────────────────────────────────\n\nexport type NodePath = readonly number[];\n\nexport type SlotMeta = {\n commentId?: number;\n elementId?: number;\n kind: DetectedSlotKind;\n mode?: 'attr' | 'bool';\n name?: string;\n};\n\nexport type CompiledStaticTemplate = {\n commentPaths: ReadonlyMap<number, NodePath>;\n element: HTMLTemplateElement;\n elementPaths: ReadonlyMap<number, NodePath>;\n slots: SlotMeta[];\n};\n\n// ─── Slot detection regexes ───────────────────────────────────────────────────\n\nconst EVENT_RE = /\\s+@([a-zA-Z_][-a-zA-Z0-9_.-]*)\\s*=\\s*[\"']?$/;\nconst REF_RE = /\\s+ref\\s*=\\s*[\"']?$/;\nconst BOOL_ATTR_RE = /\\s+\\?([a-zA-Z_][-a-zA-Z0-9_]*)\\s*=\\s*[\"']?$/;\nconst ATTR_RE = /\\s+([a-zA-Z_][-a-zA-Z0-9_]*)\\s*=\\s*[\"']?$/;\n\nconst detectSlot = (str: string): DetectedSlot => {\n let m: RegExpExecArray | null;\n\n if ((m = EVENT_RE.exec(str))) {\n const prefix = str.slice(0, -m[0].length);\n const [name, ...modifiers] = m[1].split('.');\n\n if (modifiers.length > 0) throw new OreApiError(ORE_ERRORS.eventModifiersUnsupported(m[1]));\n\n return { kind: SlotKind.EVENT, name, prefix };\n }\n\n if ((m = REF_RE.exec(str))) {\n return { kind: SlotKind.REF, prefix: str.slice(0, -m[0].length) };\n }\n\n if ((m = BOOL_ATTR_RE.exec(str))) {\n return { kind: SlotKind.BOOL_ATTR, name: m[1], prefix: str.slice(0, -m[0].length) };\n }\n\n if ((m = ATTR_RE.exec(str))) {\n return { kind: SlotKind.ATTR, name: m[1], prefix: str.slice(0, -m[0].length) };\n }\n\n const lastOpen = str.lastIndexOf('<');\n const lastClose = str.lastIndexOf('>');\n\n if (lastOpen > lastClose && str[lastOpen + 1] !== '/') {\n throw new OreApiError(ORE_ERRORS.templateInterpolationInTag);\n }\n\n return { kind: SlotKind.NODE, prefix: str };\n};\n\n// ─── Static template cache ────────────────────────────────────────────────────\n\nconst templateCache = new WeakMap<TemplateStringsArray, CompiledStaticTemplate>();\n\n/**\n * Matches a string that ends in an attribute-assignment context, e.g. `...attr=`,\n * `...@click=`, `...?disabled=`. Used together with tag-context\n * tracking (see below) to decide whether a quote immediately before an\n * interpolation is an attribute-value quote (strip it) or a literal text quote\n * (keep it): previously every adjacent quote was stripped, so both\n * `` html`\"${value}\"` `` and prose like `area = \"${area}\"` lost their quotes.\n */\nconst ATTR_VALUE_CONTEXT_RE = /[@?]?[a-zA-Z_][-a-zA-Z0-9_.]*\\s*=\\s*$/;\n\n/**\n * Pre-process template strings to strip surrounding attribute quotes. This lets\n * the main loop operate on clean strings with no per-iteration state flags.\n *\n * Quote stripping requires BOTH an attr-assignment tail AND start-tag context\n * (tracked by replaying the raw strings): `class = \"${c}\"` inside a tag is\n * stripped; `area = \"${a}\"` in prose is not.\n */\nconst normalizeTemplateStrings = (strings: TemplateStringsArray): string[] => {\n const out = Array.from(strings);\n let insideTag = false;\n\n for (let i = 0; i < out.length - 1; i++) {\n const s = out[i];\n const lastChar = s[s.length - 1];\n\n // Tag context at the interpolation boundary is determined by all raw string\n // content up to it: including this string's own text before its final quote\n // (last angle bracket wins; attribute values containing '<'/'>' are outside\n // the supported syntax either way).\n for (const ch of s) {\n if (ch === '<') insideTag = true;\n else if (ch === '>') insideTag = false;\n }\n\n // Strip wrapping attribute quotes: attr=\"${value}\" → attr=${value}\n if ((lastChar === '\"' || lastChar === \"'\") && insideTag && ATTR_VALUE_CONTEXT_RE.test(s.slice(0, -1))) {\n out[i] = s.slice(0, -1);\n\n const next = out[i + 1];\n\n if (next.startsWith(lastChar)) out[i + 1] = next.slice(1);\n }\n }\n\n return out;\n};\n\n/**\n * Attribute names that mark a binding target element, and the comment prefix for\n * node-slot anchors. Namespaced (`data-ore-*` / `ore:N`) so user-authored markup in\n * static template regions can never collide with them: a plain `u` attribute or a\n * numeric comment was previously hijacked as a binding marker and stripped.\n */\nconst ELEMENT_MARKER_ATTR = 'data-ore-b';\nconst COMMENT_MARKER_RE = /^ore:(\\d+)$/;\n\nconst walkNode = (\n node: Node,\n path: number[],\n elementPaths: Map<number, NodePath>,\n commentPaths: Map<number, NodePath>,\n): void => {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const el = node as Element;\n const marker = el.getAttribute(ELEMENT_MARKER_ATTR);\n\n if (marker !== null) {\n elementPaths.set(Number(marker), [...path]);\n el.removeAttribute(ELEMENT_MARKER_ATTR);\n }\n } else if (node.nodeType === Node.COMMENT_NODE) {\n const content = (node as Comment).nodeValue;\n const m = content !== null ? COMMENT_MARKER_RE.exec(content) : null;\n\n if (m) {\n commentPaths.set(Number(m[1]), [...path]);\n }\n }\n\n const children = node.childNodes;\n\n for (let i = 0; i < children.length; i++) walkNode(children[i], [...path, i], elementPaths, commentPaths);\n};\n\nconst buildStaticTemplate = (strings: TemplateStringsArray): CompiledStaticTemplate => {\n const normalized = normalizeTemplateStrings(strings);\n let html = '';\n let activeElementId: number | undefined;\n let elementCounter = 0;\n let commentCounter = 0;\n const slots: SlotMeta[] = [];\n\n for (let i = 0; i < normalized.length - 1; i++) {\n const raw = normalized[i];\n const slot = detectSlot(raw);\n\n if (slot.kind === SlotKind.NODE) {\n html += `${slot.prefix}<!--ore:${commentCounter}-->`;\n slots.push({ commentId: commentCounter, kind: SlotKind.NODE });\n commentCounter++;\n activeElementId = undefined;\n } else {\n const needsNewMarker =\n activeElementId === undefined || slot.prefix.lastIndexOf('<') > slot.prefix.lastIndexOf('>');\n\n if (needsNewMarker) {\n activeElementId = elementCounter++;\n html += `${slot.prefix} ${ELEMENT_MARKER_ATTR}=\"${activeElementId}\"`;\n } else {\n html += slot.prefix;\n }\n\n const mode: 'attr' | 'bool' | undefined =\n slot.kind === SlotKind.BOOL_ATTR ? 'bool' : slot.kind === SlotKind.ATTR ? 'attr' : undefined;\n\n slots.push({ elementId: activeElementId, kind: slot.kind, mode, name: slot.name });\n }\n }\n\n html += normalized[normalized.length - 1] ?? '';\n\n const tpl = document.createElement('template');\n\n tpl.innerHTML = html;\n\n const elementPaths = new Map<number, NodePath>();\n const commentPaths = new Map<number, NodePath>();\n const topChildren = tpl.content.childNodes;\n\n for (let i = 0; i < topChildren.length; i++) walkNode(topChildren[i], [i], elementPaths, commentPaths);\n\n return { commentPaths, element: tpl, elementPaths, slots };\n};\n\nexport const getStaticTemplate = (strings: TemplateStringsArray): CompiledStaticTemplate => {\n let tpl = templateCache.get(strings);\n\n if (!tpl) {\n tpl = buildStaticTemplate(strings);\n templateCache.set(strings, tpl);\n }\n\n return tpl;\n};\n\n// ─── Path navigation (used by instantiator) ───────────────────────────────────\n\nexport const followPath = (root: Node, path: NodePath): Node => {\n let node: Node = root;\n\n for (const i of path) node = node.childNodes[i];\n\n return node;\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instantiator.cjs","names":[],"sources":["../../src/template/instantiator.ts"],"sourcesContent":["/**\n * template/instantiator.ts
|
|
1
|
+
{"version":3,"file":"instantiator.cjs","names":[],"sources":["../../src/template/instantiator.ts"],"sourcesContent":["/**\n * template/instantiator.ts: Template instantiation and the `html` tagged literal.\n *\n * Responsibilities:\n * - Clone a compiled static template and wire up live bindings.\n * - Expose `compileTemplate()` and `html` as the public authoring API.\n */\n\nimport { computed, isReactive, type Readable } from '@vielzeug/ripple';\n\nimport { invariant } from '../errors';\nimport type { Binding, HtmlBindingValue } from './binding-types';\nimport { applyBinding, createAttrBindingFromValue, resolveStaticText } from './bindings';\nimport { followPath, getStaticTemplate, SlotKind } from './compiler';\nimport {\n type CompiledHTMLResult,\n createHtmlResult,\n type HTMLResult,\n isDirectiveResult,\n isHtmlResult,\n type Ref,\n type RefCallback,\n} from './result';\n\n// ─── Template instantiation ──────────────────────────────────────────────────\n\nconst NODE_SLOT_NO_PARENT_MSG = 'html`...`: node-slot comment anchor has no parent node';\n\n/** Normalize a reactive node-slot value to the HtmlBinding signal's array shape. */\nconst toHtmlValues = (raw: unknown): HtmlBindingValue[] =>\n Array.isArray(raw) ? (raw as HtmlBindingValue[]) : [raw as HtmlBindingValue];\n\n/**\n * Static-embed an already-created HTMLResult at a node-slot anchor: move its\n * fragment children into place and chain its apply into the outer apply phase\n * (so embedded reactive wiring starts when the host template mounts, not now).\n */\nconst embedStaticResult = (\n result: CompiledHTMLResult,\n anchor: Comment,\n chainedApplies: Array<(rc: (fn: () => void) => void) => void>,\n): void => {\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n\n while (result.fragment.firstChild) parent.insertBefore(result.fragment.firstChild, anchor);\n\n chainedApplies.push(result.apply.bind(result));\n};\n\n/**\n * Instantiate a compiled template: clone the cached DOM template, navigate\n * to each binding target using pre-recorded paths, and build bindings with\n * direct node references. Returns an HTMLResult ready to mount.\n */\nexport const compileTemplate = (strings: TemplateStringsArray, values: unknown[]): HTMLResult => {\n const compiled = getStaticTemplate(strings);\n const fragment = compiled.element.content.cloneNode(true) as DocumentFragment;\n const bindings: Binding[] = [];\n // For static HTMLResult embeds: chain their apply calls\n const chainedApplies: Array<(rc: (fn: () => void) => void) => void> = [];\n\n // Phase 1: Resolve all binding targets BEFORE any DOM modifications\n type BoundSlot = { comment?: Comment; el?: HTMLElement; slot: (typeof compiled.slots)[number]; value: unknown };\n\n const boundSlots: BoundSlot[] = compiled.slots.map((slot, i) => {\n const value = values[i];\n\n if (slot.kind === SlotKind.NODE) {\n const commentPath = slot.commentId !== undefined ? compiled.commentPaths.get(slot.commentId) : undefined;\n\n invariant(commentPath, `compiled template is missing a comment path for node slot ${slot.commentId}`);\n\n return { comment: followPath(fragment, commentPath) as Comment, slot, value };\n }\n\n const elementPath = slot.elementId !== undefined ? compiled.elementPaths.get(slot.elementId) : undefined;\n\n invariant(elementPath, `compiled template is missing an element path for slot ${slot.elementId}`);\n\n return { el: followPath(fragment, elementPath) as HTMLElement, slot, value };\n });\n\n // Phase 2: Build bindings (may modify DOM for static content).\n for (const { comment, el, slot, value } of boundSlots) {\n if (slot.kind === SlotKind.NODE) {\n const anchor = comment;\n\n invariant(anchor, 'compiled template produced a node slot without a comment anchor');\n\n if (isDirectiveResult(value)) {\n bindings.push({ anchor, directive: value, type: 'directive' });\n continue;\n }\n\n if (isHtmlResult(value)) {\n // Static embed: move fragment children into place, chain apply\n embedStaticResult(value, anchor, chainedApplies);\n anchor.remove();\n continue;\n }\n\n if (typeof value === 'function' || isReactive(value)) {\n // Always use the html binding for reactive values: it handles both text\n // values and HTMLResult values, preventing silent \"[object Object]\"\n // corruption when a signal's runtime type changes from null/string to HTMLResult.\n const sig =\n typeof value === 'function'\n ? computed(() => toHtmlValues((value as () => unknown)()))\n : computed(() => toHtmlValues((value as Readable<unknown>).value));\n\n bindings.push({ anchor, signal: sig, type: 'html' });\n continue;\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n if (isHtmlResult(item)) {\n embedStaticResult(item, anchor, chainedApplies);\n } else {\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n parent.insertBefore(document.createTextNode(resolveStaticText(item)), anchor);\n }\n }\n anchor.remove();\n continue;\n }\n\n // Static primitive: replace with text node, no binding\n anchor.replaceWith(document.createTextNode(resolveStaticText(value)));\n continue;\n }\n\n // Element slot\n invariant(el, 'compiled template produced an element slot without an element');\n\n if (slot.kind === SlotKind.EVENT) {\n const name = slot.name;\n\n invariant(name, 'compiled template produced an event slot without an event name');\n\n if (typeof value === 'function') {\n bindings.push({ el, handler: value as (e: Event) => void, name, type: 'event' });\n } else if (isReactive(value)) {\n const signalValue = value as Readable<unknown>;\n const handler = (e: Event) => {\n const h = signalValue.value;\n\n if (typeof h === 'function') (h as (e: Event) => void)(e);\n };\n\n bindings.push({ el, handler, name, type: 'event' });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.REF) {\n if (value) {\n bindings.push({ el, ref: value as Ref<Element> | RefCallback<Element>, type: 'ref' });\n }\n\n continue;\n }\n\n // attr / boolAttr\n invariant(slot.name, 'compiled template produced an attr slot without an attribute name');\n bindings.push(createAttrBindingFromValue(el, slot.mode ?? 'attr', slot.name, value));\n }\n\n return createHtmlResult(fragment, (registerCleanup) => {\n for (const binding of bindings) applyBinding(binding, registerCleanup);\n for (const chainedApply of chainedApplies) chainedApply(registerCleanup);\n });\n};\n\nexport const html = (strings: TemplateStringsArray, ...values: unknown[]): HTMLResult =>\n compileTemplate(strings, values);\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instantiator.js","names":[],"sources":["../../src/template/instantiator.ts"],"sourcesContent":["/**\n * template/instantiator.ts
|
|
1
|
+
{"version":3,"file":"instantiator.js","names":[],"sources":["../../src/template/instantiator.ts"],"sourcesContent":["/**\n * template/instantiator.ts: Template instantiation and the `html` tagged literal.\n *\n * Responsibilities:\n * - Clone a compiled static template and wire up live bindings.\n * - Expose `compileTemplate()` and `html` as the public authoring API.\n */\n\nimport { computed, isReactive, type Readable } from '@vielzeug/ripple';\n\nimport { invariant } from '../errors';\nimport type { Binding, HtmlBindingValue } from './binding-types';\nimport { applyBinding, createAttrBindingFromValue, resolveStaticText } from './bindings';\nimport { followPath, getStaticTemplate, SlotKind } from './compiler';\nimport {\n type CompiledHTMLResult,\n createHtmlResult,\n type HTMLResult,\n isDirectiveResult,\n isHtmlResult,\n type Ref,\n type RefCallback,\n} from './result';\n\n// ─── Template instantiation ──────────────────────────────────────────────────\n\nconst NODE_SLOT_NO_PARENT_MSG = 'html`...`: node-slot comment anchor has no parent node';\n\n/** Normalize a reactive node-slot value to the HtmlBinding signal's array shape. */\nconst toHtmlValues = (raw: unknown): HtmlBindingValue[] =>\n Array.isArray(raw) ? (raw as HtmlBindingValue[]) : [raw as HtmlBindingValue];\n\n/**\n * Static-embed an already-created HTMLResult at a node-slot anchor: move its\n * fragment children into place and chain its apply into the outer apply phase\n * (so embedded reactive wiring starts when the host template mounts, not now).\n */\nconst embedStaticResult = (\n result: CompiledHTMLResult,\n anchor: Comment,\n chainedApplies: Array<(rc: (fn: () => void) => void) => void>,\n): void => {\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n\n while (result.fragment.firstChild) parent.insertBefore(result.fragment.firstChild, anchor);\n\n chainedApplies.push(result.apply.bind(result));\n};\n\n/**\n * Instantiate a compiled template: clone the cached DOM template, navigate\n * to each binding target using pre-recorded paths, and build bindings with\n * direct node references. Returns an HTMLResult ready to mount.\n */\nexport const compileTemplate = (strings: TemplateStringsArray, values: unknown[]): HTMLResult => {\n const compiled = getStaticTemplate(strings);\n const fragment = compiled.element.content.cloneNode(true) as DocumentFragment;\n const bindings: Binding[] = [];\n // For static HTMLResult embeds: chain their apply calls\n const chainedApplies: Array<(rc: (fn: () => void) => void) => void> = [];\n\n // Phase 1: Resolve all binding targets BEFORE any DOM modifications\n type BoundSlot = { comment?: Comment; el?: HTMLElement; slot: (typeof compiled.slots)[number]; value: unknown };\n\n const boundSlots: BoundSlot[] = compiled.slots.map((slot, i) => {\n const value = values[i];\n\n if (slot.kind === SlotKind.NODE) {\n const commentPath = slot.commentId !== undefined ? compiled.commentPaths.get(slot.commentId) : undefined;\n\n invariant(commentPath, `compiled template is missing a comment path for node slot ${slot.commentId}`);\n\n return { comment: followPath(fragment, commentPath) as Comment, slot, value };\n }\n\n const elementPath = slot.elementId !== undefined ? compiled.elementPaths.get(slot.elementId) : undefined;\n\n invariant(elementPath, `compiled template is missing an element path for slot ${slot.elementId}`);\n\n return { el: followPath(fragment, elementPath) as HTMLElement, slot, value };\n });\n\n // Phase 2: Build bindings (may modify DOM for static content).\n for (const { comment, el, slot, value } of boundSlots) {\n if (slot.kind === SlotKind.NODE) {\n const anchor = comment;\n\n invariant(anchor, 'compiled template produced a node slot without a comment anchor');\n\n if (isDirectiveResult(value)) {\n bindings.push({ anchor, directive: value, type: 'directive' });\n continue;\n }\n\n if (isHtmlResult(value)) {\n // Static embed: move fragment children into place, chain apply\n embedStaticResult(value, anchor, chainedApplies);\n anchor.remove();\n continue;\n }\n\n if (typeof value === 'function' || isReactive(value)) {\n // Always use the html binding for reactive values: it handles both text\n // values and HTMLResult values, preventing silent \"[object Object]\"\n // corruption when a signal's runtime type changes from null/string to HTMLResult.\n const sig =\n typeof value === 'function'\n ? computed(() => toHtmlValues((value as () => unknown)()))\n : computed(() => toHtmlValues((value as Readable<unknown>).value));\n\n bindings.push({ anchor, signal: sig, type: 'html' });\n continue;\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n if (isHtmlResult(item)) {\n embedStaticResult(item, anchor, chainedApplies);\n } else {\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n parent.insertBefore(document.createTextNode(resolveStaticText(item)), anchor);\n }\n }\n anchor.remove();\n continue;\n }\n\n // Static primitive: replace with text node, no binding\n anchor.replaceWith(document.createTextNode(resolveStaticText(value)));\n continue;\n }\n\n // Element slot\n invariant(el, 'compiled template produced an element slot without an element');\n\n if (slot.kind === SlotKind.EVENT) {\n const name = slot.name;\n\n invariant(name, 'compiled template produced an event slot without an event name');\n\n if (typeof value === 'function') {\n bindings.push({ el, handler: value as (e: Event) => void, name, type: 'event' });\n } else if (isReactive(value)) {\n const signalValue = value as Readable<unknown>;\n const handler = (e: Event) => {\n const h = signalValue.value;\n\n if (typeof h === 'function') (h as (e: Event) => void)(e);\n };\n\n bindings.push({ el, handler, name, type: 'event' });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.REF) {\n if (value) {\n bindings.push({ el, ref: value as Ref<Element> | RefCallback<Element>, type: 'ref' });\n }\n\n continue;\n }\n\n // attr / boolAttr\n invariant(slot.name, 'compiled template produced an attr slot without an attribute name');\n bindings.push(createAttrBindingFromValue(el, slot.mode ?? 'attr', slot.name, value));\n }\n\n return createHtmlResult(fragment, (registerCleanup) => {\n for (const binding of bindings) applyBinding(binding, registerCleanup);\n for (const chainedApply of chainedApplies) chainedApply(registerCleanup);\n });\n};\n\nexport const html = (strings: TemplateStringsArray, ...values: unknown[]): HTMLResult =>\n compileTemplate(strings, values);\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"result.cjs","names":[],"sources":["../../src/template/result.ts"],"sourcesContent":["/**\n * template/result.ts
|
|
1
|
+
{"version":3,"file":"result.cjs","names":[],"sources":["../../src/template/result.ts"],"sourcesContent":["/**\n * template/result.ts: Branded result objects produced by the template authoring APIs.\n *\n * Runtime code (factories, brand guards) lives here next to the template engine that\n * consumes it; pure binding-shape types live in `binding-types.ts`. All branding goes\n * through `utils/brand.ts` (`Symbol.for`): see that module for why.\n */\n\nimport { type Signal, signal } from '@vielzeug/ripple';\n\nimport { makeBrand } from '../utils/brand';\n\n// ─── Refs ─────────────────────────────────────────────────────────────────────\n\nexport type Ref<T extends Element> = Signal<T | null>;\n\nexport function ref<T extends Element>(): Ref<T> {\n return signal<T | null>(null);\n}\n\nexport type RefCallback<T extends Element> = (el: T | null) => void;\n\n// ─── Directive result ─────────────────────────────────────────────────────────\n\nexport type DirectiveResult = {\n mount: (anchor: Comment, registerCleanup: (fn: () => void) => void) => void;\n};\n\nconst directiveBrand = makeBrand<DirectiveResult>('ore:directive');\n\n/**\n * Creates a registered DirectiveResult. All directive factories must use this\n * function: only objects created here pass `isDirectiveResult()`.\n */\nexport const createDirectiveResult = (mount: DirectiveResult['mount']): DirectiveResult =>\n directiveBrand.stamp({ mount });\n\nexport const isDirectiveResult = directiveBrand.is;\n\n// ─── HTML result ──────────────────────────────────────────────────────────────\n\n/**\n * The output of an `html` tagged template call.\n *\n * Each `html` call produces an independent fragment: there is no shared mutable\n * state between instances, so the same template can be safely rendered multiple\n * times (e.g. inside `each()`).\n *\n * `mount()` is the entire public surface: insertion and reactive wiring in one\n * step, so the two can never get out of sync (the old public `fragment` + `apply`\n * pair made it possible to insert without wiring, or wire without inserting).\n */\nexport interface HTMLResult {\n /**\n * Insert the template's nodes before `anchor` (or append to `parent` when\n * `anchor` is null) and wire up all reactive effects. Returns the inserted\n * nodes so callers can remove them later.\n */\n mount(parent: ParentNode, anchor: Node | null, registerCleanup: (fn: () => void) => void): Node[];\n}\n\n/**\n * @internal The full result the template engine itself works with. `fragment` and\n * `apply` are the engine's own two-phase insertion protocol (static-embed merging in\n * the instantiator, `insertHtmlValues` in the binding layer): not part of the\n * public API. Consumers only ever see `HTMLResult`.\n */\nexport interface CompiledHTMLResult extends HTMLResult {\n /** Wire up reactive effects to the fragment's nodes. Call after insertion. */\n apply(registerCleanup: (fn: () => void) => void): void;\n /** The DOM fragment ready to insert into the document. Consumed on insertion. */\n readonly fragment: DocumentFragment;\n}\n\nconst htmlResultBrand = makeBrand<CompiledHTMLResult>('ore:html-result');\n\nexport const isHtmlResult = htmlResultBrand.is;\n\nexport function createHtmlResult(\n fragment: DocumentFragment,\n applyFn: (registerCleanup: (fn: () => void) => void) => void,\n): CompiledHTMLResult {\n const mount = (parent: ParentNode, anchor: Node | null, registerCleanup: (fn: () => void) => void): Node[] => {\n const nodes = Array.from(fragment.childNodes);\n\n parent.insertBefore(fragment, anchor);\n applyFn(registerCleanup);\n\n return nodes;\n };\n\n return htmlResultBrand.stamp({ apply: applyFn, fragment, mount });\n}\n"],"mappings":"wEAgBA,SAAgB,GAAiC,CAC/C,OAAA,EAAO,EAAA,OAAA,CAAiB,IAAI,CAC9B,CAUA,IAAM,EAAiB,EAAA,UAA2B,eAAe,EAMpD,EAAyB,GACpC,EAAe,MAAM,CAAE,OAAM,CAAC,EAEnB,EAAoB,EAAe,GAqC1C,EAAkB,EAAA,UAA8B,iBAAiB,EAE1D,EAAe,EAAgB,GAE5C,SAAgB,EACd,EACA,EACoB,CAUpB,OAAO,EAAgB,MAAM,CAAE,MAAO,EAAS,WAAU,OAT1C,EAAoB,EAAqB,IAAsD,CAC5G,IAAM,EAAQ,MAAM,KAAK,EAAS,UAAU,EAK5C,OAHA,EAAO,aAAa,EAAU,CAAM,EACpC,EAAQ,CAAe,EAEhB,CACT,CAE+D,CAAC,CAClE"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* template/result.ts
|
|
2
|
+
* template/result.ts: Branded result objects produced by the template authoring APIs.
|
|
3
3
|
*
|
|
4
4
|
* Runtime code (factories, brand guards) lives here next to the template engine that
|
|
5
5
|
* consumes it; pure binding-shape types live in `binding-types.ts`. All branding goes
|
|
6
|
-
* through `utils/brand.ts` (`Symbol.for`)
|
|
6
|
+
* through `utils/brand.ts` (`Symbol.for`): see that module for why.
|
|
7
7
|
*/
|
|
8
8
|
import { type Signal } from '@vielzeug/ripple';
|
|
9
9
|
export type Ref<T extends Element> = Signal<T | null>;
|
|
@@ -14,14 +14,14 @@ export type DirectiveResult = {
|
|
|
14
14
|
};
|
|
15
15
|
/**
|
|
16
16
|
* Creates a registered DirectiveResult. All directive factories must use this
|
|
17
|
-
* function
|
|
17
|
+
* function: only objects created here pass `isDirectiveResult()`.
|
|
18
18
|
*/
|
|
19
19
|
export declare const createDirectiveResult: (mount: DirectiveResult["mount"]) => DirectiveResult;
|
|
20
20
|
export declare const isDirectiveResult: (value: unknown) => value is DirectiveResult;
|
|
21
21
|
/**
|
|
22
22
|
* The output of an `html` tagged template call.
|
|
23
23
|
*
|
|
24
|
-
* Each `html` call produces an independent fragment
|
|
24
|
+
* Each `html` call produces an independent fragment: there is no shared mutable
|
|
25
25
|
* state between instances, so the same template can be safely rendered multiple
|
|
26
26
|
* times (e.g. inside `each()`).
|
|
27
27
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"result.js","names":[],"sources":["../../src/template/result.ts"],"sourcesContent":["/**\n * template/result.ts
|
|
1
|
+
{"version":3,"file":"result.js","names":[],"sources":["../../src/template/result.ts"],"sourcesContent":["/**\n * template/result.ts: Branded result objects produced by the template authoring APIs.\n *\n * Runtime code (factories, brand guards) lives here next to the template engine that\n * consumes it; pure binding-shape types live in `binding-types.ts`. All branding goes\n * through `utils/brand.ts` (`Symbol.for`): see that module for why.\n */\n\nimport { type Signal, signal } from '@vielzeug/ripple';\n\nimport { makeBrand } from '../utils/brand';\n\n// ─── Refs ─────────────────────────────────────────────────────────────────────\n\nexport type Ref<T extends Element> = Signal<T | null>;\n\nexport function ref<T extends Element>(): Ref<T> {\n return signal<T | null>(null);\n}\n\nexport type RefCallback<T extends Element> = (el: T | null) => void;\n\n// ─── Directive result ─────────────────────────────────────────────────────────\n\nexport type DirectiveResult = {\n mount: (anchor: Comment, registerCleanup: (fn: () => void) => void) => void;\n};\n\nconst directiveBrand = makeBrand<DirectiveResult>('ore:directive');\n\n/**\n * Creates a registered DirectiveResult. All directive factories must use this\n * function: only objects created here pass `isDirectiveResult()`.\n */\nexport const createDirectiveResult = (mount: DirectiveResult['mount']): DirectiveResult =>\n directiveBrand.stamp({ mount });\n\nexport const isDirectiveResult = directiveBrand.is;\n\n// ─── HTML result ──────────────────────────────────────────────────────────────\n\n/**\n * The output of an `html` tagged template call.\n *\n * Each `html` call produces an independent fragment: there is no shared mutable\n * state between instances, so the same template can be safely rendered multiple\n * times (e.g. inside `each()`).\n *\n * `mount()` is the entire public surface: insertion and reactive wiring in one\n * step, so the two can never get out of sync (the old public `fragment` + `apply`\n * pair made it possible to insert without wiring, or wire without inserting).\n */\nexport interface HTMLResult {\n /**\n * Insert the template's nodes before `anchor` (or append to `parent` when\n * `anchor` is null) and wire up all reactive effects. Returns the inserted\n * nodes so callers can remove them later.\n */\n mount(parent: ParentNode, anchor: Node | null, registerCleanup: (fn: () => void) => void): Node[];\n}\n\n/**\n * @internal The full result the template engine itself works with. `fragment` and\n * `apply` are the engine's own two-phase insertion protocol (static-embed merging in\n * the instantiator, `insertHtmlValues` in the binding layer): not part of the\n * public API. Consumers only ever see `HTMLResult`.\n */\nexport interface CompiledHTMLResult extends HTMLResult {\n /** Wire up reactive effects to the fragment's nodes. Call after insertion. */\n apply(registerCleanup: (fn: () => void) => void): void;\n /** The DOM fragment ready to insert into the document. Consumed on insertion. */\n readonly fragment: DocumentFragment;\n}\n\nconst htmlResultBrand = makeBrand<CompiledHTMLResult>('ore:html-result');\n\nexport const isHtmlResult = htmlResultBrand.is;\n\nexport function createHtmlResult(\n fragment: DocumentFragment,\n applyFn: (registerCleanup: (fn: () => void) => void) => void,\n): CompiledHTMLResult {\n const mount = (parent: ParentNode, anchor: Node | null, registerCleanup: (fn: () => void) => void): Node[] => {\n const nodes = Array.from(fragment.childNodes);\n\n parent.insertBefore(fragment, anchor);\n applyFn(registerCleanup);\n\n return nodes;\n };\n\n return htmlResultBrand.stamp({ apply: applyFn, fragment, mount });\n}\n"],"mappings":"wFAgBA,SAAgB,GAAiC,CAC/C,OAAO,EAAiB,IAAI,CAC9B,CAUA,IAAM,EAAiB,EAA2B,eAAe,EAMpD,EAAyB,GACpC,EAAe,MAAM,CAAE,OAAM,CAAC,EAEnB,EAAoB,EAAe,GAqC1C,EAAkB,EAA8B,iBAAiB,EAE1D,EAAe,EAAgB,GAE5C,SAAgB,EACd,EACA,EACoB,CAUpB,OAAO,EAAgB,MAAM,CAAE,MAAO,EAAS,WAAU,OAT1C,EAAoB,EAAqB,IAAsD,CAC5G,IAAM,EAAQ,MAAM,KAAK,EAAS,UAAU,EAK5C,OAHA,EAAO,aAAa,EAAU,CAAM,EACpC,EAAQ,CAAe,EAEhB,CACT,CAE+D,CAAC,CAClE"}
|
package/dist/testing/dom.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dom.cjs","names":[],"sources":["../../src/testing/dom.ts"],"sourcesContent":["/**\n * DOM traversal helpers for tests running under jsdom, which only implements the *light* DOM\n * tree
|
|
1
|
+
{"version":3,"file":"dom.cjs","names":[],"sources":["../../src/testing/dom.ts"],"sourcesContent":["/**\n * DOM traversal helpers for tests running under jsdom, which only implements the *light* DOM\n * tree: `Element.children`/`querySelectorAll()` never cross a `<slot>` into its assigned\n * elements. Real browsers use the *flat tree* (light DOM with each `<slot>` transparently\n * replaced by what's actually assigned to it) for this: it's what \"form owner\" resolution,\n * `:has()`, and plenty of other DOM algorithms are specified against.\n */\n\n/**\n * Walks the flat tree starting at `root`, calling `visit` for every element encountered :\n * expanding each `<slot>` via `assignedElements()` instead of descending into its (always\n * empty, in jsdom) literal children.\n *\n * @example\n * ```ts\n * // Find every form-associated element slotted (possibly through nested slots) under `form`,\n * // something `form.querySelectorAll('[name]')` can't do once a shadow boundary is involved.\n * const named: HTMLElement[] = [];\n * walkFlatTree(form, (el) => { if (el.hasAttribute('name')) named.push(el); });\n * ```\n */\nexport const walkFlatTree = (root: Element, visit: (element: HTMLElement) => void): void => {\n const children = root instanceof HTMLSlotElement ? root.assignedElements({ flatten: true }) : root.children;\n\n for (const child of Array.from(children)) {\n if (!(child instanceof HTMLSlotElement)) visit(child as HTMLElement);\n\n walkFlatTree(child, visit);\n }\n};\n"],"mappings":"AAqBA,IAAa,GAAgB,EAAe,IAAgD,CAC1F,IAAM,EAAW,aAAgB,gBAAkB,EAAK,iBAAiB,CAAE,QAAS,EAAK,CAAC,EAAI,EAAK,SAEnG,IAAK,IAAM,KAAS,MAAM,KAAK,CAAQ,EAC/B,aAAiB,iBAAkB,EAAM,CAAoB,EAEnE,EAAa,EAAO,CAAK,CAE7B"}
|
package/dist/testing/dom.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* DOM traversal helpers for tests running under jsdom, which only implements the *light* DOM
|
|
3
|
-
* tree
|
|
3
|
+
* tree: `Element.children`/`querySelectorAll()` never cross a `<slot>` into its assigned
|
|
4
4
|
* elements. Real browsers use the *flat tree* (light DOM with each `<slot>` transparently
|
|
5
|
-
* replaced by what's actually assigned to it) for this
|
|
5
|
+
* replaced by what's actually assigned to it) for this: it's what "form owner" resolution,
|
|
6
6
|
* `:has()`, and plenty of other DOM algorithms are specified against.
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
|
-
* Walks the flat tree starting at `root`, calling `visit` for every element encountered
|
|
9
|
+
* Walks the flat tree starting at `root`, calling `visit` for every element encountered :
|
|
10
10
|
* expanding each `<slot>` via `assignedElements()` instead of descending into its (always
|
|
11
11
|
* empty, in jsdom) literal children.
|
|
12
12
|
*
|
package/dist/testing/dom.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dom.js","names":[],"sources":["../../src/testing/dom.ts"],"sourcesContent":["/**\n * DOM traversal helpers for tests running under jsdom, which only implements the *light* DOM\n * tree
|
|
1
|
+
{"version":3,"file":"dom.js","names":[],"sources":["../../src/testing/dom.ts"],"sourcesContent":["/**\n * DOM traversal helpers for tests running under jsdom, which only implements the *light* DOM\n * tree: `Element.children`/`querySelectorAll()` never cross a `<slot>` into its assigned\n * elements. Real browsers use the *flat tree* (light DOM with each `<slot>` transparently\n * replaced by what's actually assigned to it) for this: it's what \"form owner\" resolution,\n * `:has()`, and plenty of other DOM algorithms are specified against.\n */\n\n/**\n * Walks the flat tree starting at `root`, calling `visit` for every element encountered :\n * expanding each `<slot>` via `assignedElements()` instead of descending into its (always\n * empty, in jsdom) literal children.\n *\n * @example\n * ```ts\n * // Find every form-associated element slotted (possibly through nested slots) under `form`,\n * // something `form.querySelectorAll('[name]')` can't do once a shadow boundary is involved.\n * const named: HTMLElement[] = [];\n * walkFlatTree(form, (el) => { if (el.hasAttribute('name')) named.push(el); });\n * ```\n */\nexport const walkFlatTree = (root: Element, visit: (element: HTMLElement) => void): void => {\n const children = root instanceof HTMLSlotElement ? root.assignedElements({ flatten: true }) : root.children;\n\n for (const child of Array.from(children)) {\n if (!(child instanceof HTMLSlotElement)) visit(child as HTMLElement);\n\n walkFlatTree(child, visit);\n }\n};\n"],"mappings":"AAqBA,IAAa,GAAgB,EAAe,IAAgD,CAC1F,IAAM,EAAW,aAAgB,gBAAkB,EAAK,iBAAiB,CAAE,QAAS,EAAK,CAAC,EAAI,EAAK,SAEnG,IAAK,IAAM,KAAS,MAAM,KAAK,CAAQ,EAC/B,aAAiB,iBAAkB,EAAM,CAAoB,EAEnE,EAAa,EAAO,CAAK,CAE7B"}
|
package/dist/testing/flush.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../errors.cjs"),t=require("../runtime.cjs");var n=class extends e.OreError{},r=1e3;async function i(e={}){let{logger:i}=e;i?.(`[flush] draining pending component work (scheduled mount callbacks)`);let a=0;for(;t.hasPendingWork();){if(++a>r)throw new n(`flush(): pending component work did not settle after ${r} microtask turns
|
|
1
|
+
const e=require("../errors.cjs"),t=require("../runtime.cjs");var n=class extends e.OreError{},r=1e3;async function i(e={}){let{logger:i}=e;i?.(`[flush] draining pending component work (scheduled mount callbacks)`);let a=0;for(;t.hasPendingWork();){if(++a>r)throw new n(`flush(): pending component work did not settle after ${r} microtask turns: check for a component whose onMounted callback keeps re-registering itself.`);i?.(`[flush] pending work remains, turn ${a}`),await Promise.resolve()}i?.(`[flush] draining requestAnimationFrame`),await new Promise(e=>typeof requestAnimationFrame<`u`?requestAnimationFrame(()=>e()):e()),i?.(`[flush] final microtask pass`),await Promise.resolve(),i?.(`[flush] complete`)}exports.OreTimeoutError=n,exports.flush=i;
|
|
2
2
|
//# sourceMappingURL=flush.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flush.cjs","names":[],"sources":["../../src/testing/flush.ts"],"sourcesContent":["/**\n * Reactive update flushing utilities for test environments.\n */\n\nimport { OreError } from '../errors';\nimport { hasPendingWork } from '../runtime';\n\n/** Thrown by `flush()` when pending component work doesn't settle within the timeout. */\nexport class OreTimeoutError extends OreError {}\n\n// Defensive cap against a genuine bug (e.g. an onMounted callback that always registers\n// another onMounted, looping forever)
|
|
1
|
+
{"version":3,"file":"flush.cjs","names":[],"sources":["../../src/testing/flush.ts"],"sourcesContent":["/**\n * Reactive update flushing utilities for test environments.\n */\n\nimport { OreError } from '../errors';\nimport { hasPendingWork } from '../runtime';\n\n/** Thrown by `flush()` when pending component work doesn't settle within the timeout. */\nexport class OreTimeoutError extends OreError {}\n\n// Defensive cap against a genuine bug (e.g. an onMounted callback that always registers\n// another onMounted, looping forever): not a \"how much is normally enough\" guess like the\n// old fixed turn count was. Microtask polls are cheap, so this is generous.\nconst MAX_FLUSH_ITERATIONS = 1000;\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/**\n * Options for flushing pending reactive updates.\n */\nexport interface FlushOptions {\n /**\n * Logger function called with each diagnostic message during the flush.\n * Pass `console.debug` or any `(msg: string) => void` to enable output.\n * Omit (or pass `undefined`) to disable all logging.\n */\n logger?: (msg: string) => void;\n}\n\n// ─── Core ────────────────────────────────────────────────────────────────────\n\n/**\n * Flush pending reactive updates: waits for every tracked in-flight component\n * operation (scheduled `onMounted` microtasks: including ones registered by other\n * `onMounted` callbacks) to settle, then yields one animation frame and one final\n * microtask pass for rAF-scheduled work.\n *\n * This is deterministic, not a fixed number of guessed turns: `@vielzeug/ripple`'s\n * reactive graph itself settles synchronously on every signal write, so the only real\n * asynchrony to wait for is ore's own component scheduling (see `runtime.ts`'s\n * `hasPendingWork()`). There is no \"deep\" variant to reach for anymore: `flush()` waits\n * for exactly as much work as is actually pending, however deep that turns out to be.\n *\n * @example\n * ```ts\n * // Standard flush: waits for whatever is actually pending, shallow or deep\n * await flush();\n *\n * // Debug timing issues\n * await flush({ logger: console.debug });\n * ```\n *\n * @throws `OreTimeoutError` if pending work never settles within {@link MAX_FLUSH_ITERATIONS}\n * microtask turns: almost always a sign of a genuine bug (e.g. an `onMounted` callback that\n * keeps re-registering itself), not a case for raising a limit.\n */\nexport async function flush(options: FlushOptions = {}): Promise<void> {\n const { logger } = options;\n\n logger?.('[flush] draining pending component work (scheduled mount callbacks)');\n\n let turns = 0;\n\n while (hasPendingWork()) {\n if (++turns > MAX_FLUSH_ITERATIONS) {\n throw new OreTimeoutError(\n `flush(): pending component work did not settle after ${MAX_FLUSH_ITERATIONS} microtask turns: ` +\n `check for a component whose onMounted callback keeps re-registering itself.`,\n );\n }\n\n logger?.(`[flush] pending work remains, turn ${turns}`);\n await Promise.resolve();\n }\n\n logger?.('[flush] draining requestAnimationFrame');\n\n await new Promise<void>((resolve) =>\n typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame(() => resolve()) : resolve(),\n );\n\n logger?.('[flush] final microtask pass');\n\n await Promise.resolve();\n\n logger?.('[flush] complete');\n}\n"],"mappings":"6DAQA,IAAa,EAAb,cAAqC,EAAA,QAAS,CAAC,EAKzC,EAAuB,IA2C7B,eAAsB,EAAM,EAAwB,CAAC,EAAkB,CACrE,GAAM,CAAE,UAAW,EAEnB,IAAS,qEAAqE,EAE9E,IAAI,EAAQ,EAEZ,KAAO,EAAA,eAAe,GAAG,CACvB,GAAI,EAAE,EAAQ,EACZ,MAAM,IAAI,EACR,wDAAwD,EAAqB,8FAE/E,EAGF,IAAS,sCAAsC,GAAO,EACtD,MAAM,QAAQ,QAAQ,CACxB,CAEA,IAAS,wCAAwC,EAEjD,MAAM,IAAI,QAAe,GACvB,OAAO,sBAA0B,IAAc,0BAA4B,EAAQ,CAAC,EAAI,EAAQ,CAClG,EAEA,IAAS,8BAA8B,EAEvC,MAAM,QAAQ,QAAQ,EAEtB,IAAS,kBAAkB,CAC7B"}
|
package/dist/testing/flush.d.ts
CHANGED
|
@@ -18,11 +18,11 @@ export interface FlushOptions {
|
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* Flush pending reactive updates: waits for every tracked in-flight component
|
|
21
|
-
* operation (scheduled `onMounted` microtasks
|
|
21
|
+
* operation (scheduled `onMounted` microtasks: including ones registered by other
|
|
22
22
|
* `onMounted` callbacks) to settle, then yields one animation frame and one final
|
|
23
23
|
* microtask pass for rAF-scheduled work.
|
|
24
24
|
*
|
|
25
|
-
* This is deterministic, not a fixed number of guessed turns
|
|
25
|
+
* This is deterministic, not a fixed number of guessed turns: `@vielzeug/ripple`'s
|
|
26
26
|
* reactive graph itself settles synchronously on every signal write, so the only real
|
|
27
27
|
* asynchrony to wait for is ore's own component scheduling (see `runtime.ts`'s
|
|
28
28
|
* `hasPendingWork()`). There is no "deep" variant to reach for anymore: `flush()` waits
|
|
@@ -30,7 +30,7 @@ export interface FlushOptions {
|
|
|
30
30
|
*
|
|
31
31
|
* @example
|
|
32
32
|
* ```ts
|
|
33
|
-
* // Standard flush
|
|
33
|
+
* // Standard flush: waits for whatever is actually pending, shallow or deep
|
|
34
34
|
* await flush();
|
|
35
35
|
*
|
|
36
36
|
* // Debug timing issues
|
|
@@ -38,7 +38,7 @@ export interface FlushOptions {
|
|
|
38
38
|
* ```
|
|
39
39
|
*
|
|
40
40
|
* @throws `OreTimeoutError` if pending work never settles within {@link MAX_FLUSH_ITERATIONS}
|
|
41
|
-
* microtask turns
|
|
41
|
+
* microtask turns: almost always a sign of a genuine bug (e.g. an `onMounted` callback that
|
|
42
42
|
* keeps re-registering itself), not a case for raising a limit.
|
|
43
43
|
*/
|
|
44
44
|
export declare function flush(options?: FlushOptions): Promise<void>;
|
package/dist/testing/flush.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{OreError as e}from"../errors.js";import{hasPendingWork as t}from"../runtime.js";var n=class extends e{},r=1e3;async function i(e={}){let{logger:i}=e;i?.(`[flush] draining pending component work (scheduled mount callbacks)`);let a=0;for(;t();){if(++a>r)throw new n(`flush(): pending component work did not settle after ${r} microtask turns
|
|
1
|
+
import{OreError as e}from"../errors.js";import{hasPendingWork as t}from"../runtime.js";var n=class extends e{},r=1e3;async function i(e={}){let{logger:i}=e;i?.(`[flush] draining pending component work (scheduled mount callbacks)`);let a=0;for(;t();){if(++a>r)throw new n(`flush(): pending component work did not settle after ${r} microtask turns: check for a component whose onMounted callback keeps re-registering itself.`);i?.(`[flush] pending work remains, turn ${a}`),await Promise.resolve()}i?.(`[flush] draining requestAnimationFrame`),await new Promise(e=>typeof requestAnimationFrame<`u`?requestAnimationFrame(()=>e()):e()),i?.(`[flush] final microtask pass`),await Promise.resolve(),i?.(`[flush] complete`)}export{n as OreTimeoutError,i as flush};
|
|
2
2
|
//# sourceMappingURL=flush.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flush.js","names":[],"sources":["../../src/testing/flush.ts"],"sourcesContent":["/**\n * Reactive update flushing utilities for test environments.\n */\n\nimport { OreError } from '../errors';\nimport { hasPendingWork } from '../runtime';\n\n/** Thrown by `flush()` when pending component work doesn't settle within the timeout. */\nexport class OreTimeoutError extends OreError {}\n\n// Defensive cap against a genuine bug (e.g. an onMounted callback that always registers\n// another onMounted, looping forever)
|
|
1
|
+
{"version":3,"file":"flush.js","names":[],"sources":["../../src/testing/flush.ts"],"sourcesContent":["/**\n * Reactive update flushing utilities for test environments.\n */\n\nimport { OreError } from '../errors';\nimport { hasPendingWork } from '../runtime';\n\n/** Thrown by `flush()` when pending component work doesn't settle within the timeout. */\nexport class OreTimeoutError extends OreError {}\n\n// Defensive cap against a genuine bug (e.g. an onMounted callback that always registers\n// another onMounted, looping forever): not a \"how much is normally enough\" guess like the\n// old fixed turn count was. Microtask polls are cheap, so this is generous.\nconst MAX_FLUSH_ITERATIONS = 1000;\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/**\n * Options for flushing pending reactive updates.\n */\nexport interface FlushOptions {\n /**\n * Logger function called with each diagnostic message during the flush.\n * Pass `console.debug` or any `(msg: string) => void` to enable output.\n * Omit (or pass `undefined`) to disable all logging.\n */\n logger?: (msg: string) => void;\n}\n\n// ─── Core ────────────────────────────────────────────────────────────────────\n\n/**\n * Flush pending reactive updates: waits for every tracked in-flight component\n * operation (scheduled `onMounted` microtasks: including ones registered by other\n * `onMounted` callbacks) to settle, then yields one animation frame and one final\n * microtask pass for rAF-scheduled work.\n *\n * This is deterministic, not a fixed number of guessed turns: `@vielzeug/ripple`'s\n * reactive graph itself settles synchronously on every signal write, so the only real\n * asynchrony to wait for is ore's own component scheduling (see `runtime.ts`'s\n * `hasPendingWork()`). There is no \"deep\" variant to reach for anymore: `flush()` waits\n * for exactly as much work as is actually pending, however deep that turns out to be.\n *\n * @example\n * ```ts\n * // Standard flush: waits for whatever is actually pending, shallow or deep\n * await flush();\n *\n * // Debug timing issues\n * await flush({ logger: console.debug });\n * ```\n *\n * @throws `OreTimeoutError` if pending work never settles within {@link MAX_FLUSH_ITERATIONS}\n * microtask turns: almost always a sign of a genuine bug (e.g. an `onMounted` callback that\n * keeps re-registering itself), not a case for raising a limit.\n */\nexport async function flush(options: FlushOptions = {}): Promise<void> {\n const { logger } = options;\n\n logger?.('[flush] draining pending component work (scheduled mount callbacks)');\n\n let turns = 0;\n\n while (hasPendingWork()) {\n if (++turns > MAX_FLUSH_ITERATIONS) {\n throw new OreTimeoutError(\n `flush(): pending component work did not settle after ${MAX_FLUSH_ITERATIONS} microtask turns: ` +\n `check for a component whose onMounted callback keeps re-registering itself.`,\n );\n }\n\n logger?.(`[flush] pending work remains, turn ${turns}`);\n await Promise.resolve();\n }\n\n logger?.('[flush] draining requestAnimationFrame');\n\n await new Promise<void>((resolve) =>\n typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame(() => resolve()) : resolve(),\n );\n\n logger?.('[flush] final microtask pass');\n\n await Promise.resolve();\n\n logger?.('[flush] complete');\n}\n"],"mappings":"uFAQA,IAAa,EAAb,cAAqC,CAAS,CAAC,EAKzC,EAAuB,IA2C7B,eAAsB,EAAM,EAAwB,CAAC,EAAkB,CACrE,GAAM,CAAE,UAAW,EAEnB,IAAS,qEAAqE,EAE9E,IAAI,EAAQ,EAEZ,KAAO,EAAe,GAAG,CACvB,GAAI,EAAE,EAAQ,EACZ,MAAM,IAAI,EACR,wDAAwD,EAAqB,8FAE/E,EAGF,IAAS,sCAAsC,GAAO,EACtD,MAAM,QAAQ,QAAQ,CACxB,CAEA,IAAS,wCAAwC,EAEjD,MAAM,IAAI,QAAe,GACvB,OAAO,sBAA0B,IAAc,0BAA4B,EAAQ,CAAC,EAAI,EAAQ,CAClG,EAEA,IAAS,8BAA8B,EAEvC,MAAM,QAAQ,QAAQ,EAEtB,IAAS,kBAAkB,CAC7B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-internals-polyfill.cjs","names":[],"sources":["../../src/testing/form-internals-polyfill.ts"],"sourcesContent":["import { walkFlatTree } from './dom';\n\n/**\n * jsdom implements none of the `ElementInternals` form-association API — not `setFormValue`,\n * not `setValidity`, not the `checkValidity`/`reportValidity`/`validity`/`validationMessage`\n * mixin real browsers put on every `formAssociated: true` element, not even `FormData`\n * collecting a form-associated element's set value, or `<form>.reset()` invoking\n * `formResetCallback()`. Every one of those gaps is exercised by `useField()` (`@vielzeug/ore`),\n * so any package testing a form-associated component needs all of them, not just one.\n *\n * Opt-in via `install(afterEach, { formInternals: true })` (or call this directly) —\n * the patches are global monkey-patches, so suites without form-associated components\n * shouldn't pay for them. Returns an `uninstall()` that restores every patched global.\n *\n * Deliberately lives here rather than in each consuming package's own `vitest.setup.ts`: the\n * gap is in `ore`'s own form-association feature, not in any one consumer, so the fix belongs\n * with the feature. `walkFlatTree` handles form-associated custom elements across shadow\n * boundaries, so consumer packages do not need package-local copies of this polyfill.\n */\nexport const installFormInternalsPolyfill = (): (() => void) => {\n // Checked *before* consuming the flag below — an environment without `ElementInternals`\n // installs nothing and should stay eligible to install for real later (e.g. a differently\n // configured environment in the same process), not get marked \"already installed\" for having\n // done nothing.\n if (typeof ElementInternals === 'undefined') return () => {};\n\n const INSTALLED_FLAG = Symbol.for('vielzeug.ore.testing.formInternalsPolyfillInstalled');\n const flagHost = globalThis as Record<symbol, boolean | undefined>;\n\n if (flagHost[INSTALLED_FLAG]) return () => {};\n\n flagHost[INSTALLED_FLAG] = true;\n\n // Every patch pushes its inverse here, so uninstall() restores the environment exactly.\n const restorers: Array<() => void> = [];\n\n const proto = ElementInternals.prototype as unknown as Record<string, unknown>;\n const validityState = new WeakMap<ElementInternals, { flags: ValidityStateFlags; message: string }>();\n const formValueByHost = new WeakMap<HTMLElement, File | FormData | string | null>();\n const hostByInternals = new WeakMap<ElementInternals, HTMLElement>();\n\n const patchProto = (name: string, descriptor: PropertyDescriptor): void => {\n if (name in proto) return;\n\n Object.defineProperty(proto, name, descriptor);\n restorers.push(() => delete proto[name]);\n };\n\n const isValid = (internals: ElementInternals): boolean => {\n const state = validityState.get(internals);\n\n return !state || !Object.values(state.flags).some(Boolean);\n };\n\n patchProto('setFormValue', {\n configurable: true,\n value: function (this: ElementInternals, value: File | FormData | string | null) {\n const host = hostByInternals.get(this);\n\n if (host) formValueByHost.set(host, value);\n },\n writable: true,\n });\n\n patchProto('setValidity', {\n configurable: true,\n // Matches the real platform contract: throws if any flag is true and message is empty.\n // `useField()` itself already guards against triggering this — see forms/field.ts — but\n // the polyfill enforcing it too means a test would still catch a *different* caller doing\n // the same thing wrong, instead of that bug only surfacing in a real browser.\n value: function (this: ElementInternals, flags: ValidityStateFlags = {}, message = '') {\n if (Object.values(flags).some(Boolean) && !message) {\n throw new TypeError(\n \"Failed to execute 'setValidity' on 'ElementInternals': The second argument must not be empty if \" +\n 'one or more flags in the first argument are true.',\n );\n }\n\n validityState.set(this, { flags, message });\n },\n writable: true,\n });\n\n patchProto('checkValidity', {\n configurable: true,\n value: function (this: ElementInternals) {\n return isValid(this);\n },\n writable: true,\n });\n\n patchProto('reportValidity', {\n configurable: true,\n value: function (this: ElementInternals) {\n return isValid(this);\n },\n writable: true,\n });\n\n patchProto('validationMessage', {\n configurable: true,\n get(this: ElementInternals) {\n return isValid(this) ? '' : (validityState.get(this)?.message ?? '');\n },\n });\n\n // A ValidityState-shaped view of the flags passed to setValidity — every known\n // flag defaults to false, `valid` reflects whether any flag is set.\n patchProto('validity', {\n configurable: true,\n get(this: ElementInternals): ValidityState {\n const state = validityState.get(this);\n\n return {\n badInput: false,\n customError: false,\n patternMismatch: false,\n rangeOverflow: false,\n rangeUnderflow: false,\n stepMismatch: false,\n tooLong: false,\n tooShort: false,\n typeMismatch: false,\n valid: isValid(this),\n valueMissing: false,\n ...(state?.flags ?? {}),\n } as ValidityState;\n },\n });\n\n patchProto('states', {\n configurable: true,\n get: function (this: ElementInternals & { _states?: Set<string> }) {\n this._states ??= new Set();\n\n return this._states;\n },\n });\n\n // Real browsers mix `checkValidity`/`reportValidity`/`validity`/`validationMessage` onto any\n // `formAssociated: true` custom element itself (delegating to its internals) — not just onto\n // `ElementInternals`. jsdom does neither; mirror it here so a component's own\n // `checkValidity()`/`reportValidity()`, and a test asserting against the element directly,\n // behave the same as they would in a real browser.\n const originalAttachInternals = HTMLElement.prototype.attachInternals;\n\n HTMLElement.prototype.attachInternals = function (this: HTMLElement, ...args: []): ElementInternals {\n const internals = originalAttachInternals.apply(this, args);\n\n hostByInternals.set(internals, this);\n\n Object.defineProperties(this, {\n checkValidity: { configurable: true, value: () => internals.checkValidity() },\n reportValidity: { configurable: true, value: () => internals.reportValidity() },\n validationMessage: { configurable: true, get: () => internals.validationMessage },\n validity: { configurable: true, get: () => internals.validity },\n });\n\n return internals;\n };\n restorers.push(() => {\n HTMLElement.prototype.attachInternals = originalAttachInternals;\n });\n\n const appendFormValue = (formData: FormData, name: string, value: File | FormData | string): void => {\n if (value instanceof FormData) {\n for (const [entryName, entryValue] of value.entries()) formData.append(entryName, entryValue);\n\n return;\n }\n\n formData.append(name, value);\n };\n\n // jsdom's `FormData` constructor never collects a form-associated custom element's set form\n // value — real browsers walk the flat tree for every form-associated element with a `name`.\n const NativeFormData = globalThis.FormData;\n\n globalThis.FormData = class FormDataWithFormAssociatedElements extends NativeFormData {\n constructor(form?: HTMLFormElement, submitter?: HTMLElement | null) {\n super(form as HTMLFormElement | undefined, submitter as HTMLElement | null | undefined);\n\n if (!(form instanceof HTMLFormElement)) return;\n\n walkFlatTree(form, (element) => {\n const name = element.getAttribute('name');\n const value = formValueByHost.get(element);\n\n if (!name || value == null || element.hasAttribute('disabled')) return;\n\n appendFormValue(this, name, value);\n });\n }\n };\n restorers.push(() => {\n globalThis.FormData = NativeFormData;\n });\n\n // jsdom never invokes the native `formResetCallback` lifecycle method on form-associated\n // custom elements when their form resets — patch `reset()` to do it (via the same flat-tree\n // walk, since a reset field can be nested behind a shadow boundary too), so `onFormReset()`\n // (see `@vielzeug/ore`'s runtime.ts) is testable against a real `<form>.reset()` call instead\n // of only by calling `element.formResetCallback()` directly.\n const originalReset = HTMLFormElement.prototype.reset;\n\n HTMLFormElement.prototype.reset = function (this: HTMLFormElement, ...args: []) {\n originalReset.apply(this, args);\n\n walkFlatTree(this, (element) => {\n (element as Partial<{ formResetCallback: () => void }>).formResetCallback?.();\n });\n };\n restorers.push(() => {\n HTMLFormElement.prototype.reset = originalReset;\n });\n\n return () => {\n for (const restore of restorers) restore();\n\n restorers.length = 0;\n flagHost[INSTALLED_FLAG] = false;\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"form-internals-polyfill.cjs","names":[],"sources":["../../src/testing/form-internals-polyfill.ts"],"sourcesContent":["import { walkFlatTree } from './dom';\n\n/**\n * jsdom implements none of the `ElementInternals` form-association API: not `setFormValue`,\n * not `setValidity`, not the `checkValidity`/`reportValidity`/`validity`/`validationMessage`\n * mixin real browsers put on every `formAssociated: true` element, not even `FormData`\n * collecting a form-associated element's set value, or `<form>.reset()` invoking\n * `formResetCallback()`. Every one of those gaps is exercised by `useField()` (`@vielzeug/ore`),\n * so any package testing a form-associated component needs all of them, not just one.\n *\n * Opt-in via `install(afterEach, { formInternals: true })` (or call this directly) :\n * the patches are global monkey-patches, so suites without form-associated components\n * shouldn't pay for them. Returns an `uninstall()` that restores every patched global.\n *\n * Deliberately lives here rather than in each consuming package's own `vitest.setup.ts`: the\n * gap is in `ore`'s own form-association feature, not in any one consumer, so the fix belongs\n * with the feature. `walkFlatTree` handles form-associated custom elements across shadow\n * boundaries, so consumer packages do not need package-local copies of this polyfill.\n */\nexport const installFormInternalsPolyfill = (): (() => void) => {\n // Checked *before* consuming the flag below: an environment without `ElementInternals`\n // installs nothing and should stay eligible to install for real later (e.g. a differently\n // configured environment in the same process), not get marked \"already installed\" for having\n // done nothing.\n if (typeof ElementInternals === 'undefined') return () => {};\n\n const INSTALLED_FLAG = Symbol.for('vielzeug.ore.testing.formInternalsPolyfillInstalled');\n const flagHost = globalThis as Record<symbol, boolean | undefined>;\n\n if (flagHost[INSTALLED_FLAG]) return () => {};\n\n flagHost[INSTALLED_FLAG] = true;\n\n // Every patch pushes its inverse here, so uninstall() restores the environment exactly.\n const restorers: Array<() => void> = [];\n\n const proto = ElementInternals.prototype as unknown as Record<string, unknown>;\n const validityState = new WeakMap<ElementInternals, { flags: ValidityStateFlags; message: string }>();\n const formValueByHost = new WeakMap<HTMLElement, File | FormData | string | null>();\n const hostByInternals = new WeakMap<ElementInternals, HTMLElement>();\n\n const patchProto = (name: string, descriptor: PropertyDescriptor): void => {\n if (name in proto) return;\n\n Object.defineProperty(proto, name, descriptor);\n restorers.push(() => delete proto[name]);\n };\n\n const isValid = (internals: ElementInternals): boolean => {\n const state = validityState.get(internals);\n\n return !state || !Object.values(state.flags).some(Boolean);\n };\n\n patchProto('setFormValue', {\n configurable: true,\n value: function (this: ElementInternals, value: File | FormData | string | null) {\n const host = hostByInternals.get(this);\n\n if (host) formValueByHost.set(host, value);\n },\n writable: true,\n });\n\n patchProto('setValidity', {\n configurable: true,\n // Matches the real platform contract: throws if any flag is true and message is empty.\n // `useField()` itself already guards against triggering this: see forms/field.ts, but\n // the polyfill enforcing it too means a test would still catch a *different* caller doing\n // the same thing wrong, instead of that bug only surfacing in a real browser.\n value: function (this: ElementInternals, flags: ValidityStateFlags = {}, message = '') {\n if (Object.values(flags).some(Boolean) && !message) {\n throw new TypeError(\n \"Failed to execute 'setValidity' on 'ElementInternals': The second argument must not be empty if \" +\n 'one or more flags in the first argument are true.',\n );\n }\n\n validityState.set(this, { flags, message });\n },\n writable: true,\n });\n\n patchProto('checkValidity', {\n configurable: true,\n value: function (this: ElementInternals) {\n return isValid(this);\n },\n writable: true,\n });\n\n patchProto('reportValidity', {\n configurable: true,\n value: function (this: ElementInternals) {\n return isValid(this);\n },\n writable: true,\n });\n\n patchProto('validationMessage', {\n configurable: true,\n get(this: ElementInternals) {\n return isValid(this) ? '' : (validityState.get(this)?.message ?? '');\n },\n });\n\n // A ValidityState-shaped view of the flags passed to setValidity: every known\n // flag defaults to false, `valid` reflects whether any flag is set.\n patchProto('validity', {\n configurable: true,\n get(this: ElementInternals): ValidityState {\n const state = validityState.get(this);\n\n return {\n badInput: false,\n customError: false,\n patternMismatch: false,\n rangeOverflow: false,\n rangeUnderflow: false,\n stepMismatch: false,\n tooLong: false,\n tooShort: false,\n typeMismatch: false,\n valid: isValid(this),\n valueMissing: false,\n ...(state?.flags ?? {}),\n } as ValidityState;\n },\n });\n\n patchProto('states', {\n configurable: true,\n get: function (this: ElementInternals & { _states?: Set<string> }) {\n this._states ??= new Set();\n\n return this._states;\n },\n });\n\n // Real browsers mix `checkValidity`/`reportValidity`/`validity`/`validationMessage` onto any\n // `formAssociated: true` custom element itself (delegating to its internals): not just onto\n // `ElementInternals`. jsdom does neither; mirror it here so a component's own\n // `checkValidity()`/`reportValidity()`, and a test asserting against the element directly,\n // behave the same as they would in a real browser.\n const originalAttachInternals = HTMLElement.prototype.attachInternals;\n\n HTMLElement.prototype.attachInternals = function (this: HTMLElement, ...args: []): ElementInternals {\n const internals = originalAttachInternals.apply(this, args);\n\n hostByInternals.set(internals, this);\n\n Object.defineProperties(this, {\n checkValidity: { configurable: true, value: () => internals.checkValidity() },\n reportValidity: { configurable: true, value: () => internals.reportValidity() },\n validationMessage: { configurable: true, get: () => internals.validationMessage },\n validity: { configurable: true, get: () => internals.validity },\n });\n\n return internals;\n };\n restorers.push(() => {\n HTMLElement.prototype.attachInternals = originalAttachInternals;\n });\n\n const appendFormValue = (formData: FormData, name: string, value: File | FormData | string): void => {\n if (value instanceof FormData) {\n for (const [entryName, entryValue] of value.entries()) formData.append(entryName, entryValue);\n\n return;\n }\n\n formData.append(name, value);\n };\n\n // jsdom's `FormData` constructor never collects a form-associated custom element's set form\n // value: real browsers walk the flat tree for every form-associated element with a `name`.\n const NativeFormData = globalThis.FormData;\n\n globalThis.FormData = class FormDataWithFormAssociatedElements extends NativeFormData {\n constructor(form?: HTMLFormElement, submitter?: HTMLElement | null) {\n super(form as HTMLFormElement | undefined, submitter as HTMLElement | null | undefined);\n\n if (!(form instanceof HTMLFormElement)) return;\n\n walkFlatTree(form, (element) => {\n const name = element.getAttribute('name');\n const value = formValueByHost.get(element);\n\n if (!name || value == null || element.hasAttribute('disabled')) return;\n\n appendFormValue(this, name, value);\n });\n }\n };\n restorers.push(() => {\n globalThis.FormData = NativeFormData;\n });\n\n // jsdom never invokes the native `formResetCallback` lifecycle method on form-associated\n // custom elements when their form resets: patch `reset()` to do it (via the same flat-tree\n // walk, since a reset field can be nested behind a shadow boundary too), so `onFormReset()`\n // (see `@vielzeug/ore`'s runtime.ts) is testable against a real `<form>.reset()` call instead\n // of only by calling `element.formResetCallback()` directly.\n const originalReset = HTMLFormElement.prototype.reset;\n\n HTMLFormElement.prototype.reset = function (this: HTMLFormElement, ...args: []) {\n originalReset.apply(this, args);\n\n walkFlatTree(this, (element) => {\n (element as Partial<{ formResetCallback: () => void }>).formResetCallback?.();\n });\n };\n restorers.push(() => {\n HTMLFormElement.prototype.reset = originalReset;\n });\n\n return () => {\n for (const restore of restorers) restore();\n\n restorers.length = 0;\n flagHost[INSTALLED_FLAG] = false;\n };\n};\n"],"mappings":"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"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* jsdom implements none of the `ElementInternals` form-association API
|
|
2
|
+
* jsdom implements none of the `ElementInternals` form-association API: not `setFormValue`,
|
|
3
3
|
* not `setValidity`, not the `checkValidity`/`reportValidity`/`validity`/`validationMessage`
|
|
4
4
|
* mixin real browsers put on every `formAssociated: true` element, not even `FormData`
|
|
5
5
|
* collecting a form-associated element's set value, or `<form>.reset()` invoking
|
|
6
6
|
* `formResetCallback()`. Every one of those gaps is exercised by `useField()` (`@vielzeug/ore`),
|
|
7
7
|
* so any package testing a form-associated component needs all of them, not just one.
|
|
8
8
|
*
|
|
9
|
-
* Opt-in via `install(afterEach, { formInternals: true })` (or call this directly)
|
|
9
|
+
* Opt-in via `install(afterEach, { formInternals: true })` (or call this directly) :
|
|
10
10
|
* the patches are global monkey-patches, so suites without form-associated components
|
|
11
11
|
* shouldn't pay for them. Returns an `uninstall()` that restores every patched global.
|
|
12
12
|
*
|