@vielzeug/ore 26.10.14 → 26.10.16
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/dist/_dev.cjs.map +1 -1
- package/dist/_dev.js.map +1 -1
- package/dist/base-element.cjs.map +1 -1
- package/dist/base-element.js.map +1 -1
- package/dist/context.cjs.map +1 -1
- package/dist/context.js +1 -1
- package/dist/context.js.map +1 -1
- package/dist/define.cjs.map +1 -1
- package/dist/define.js.map +1 -1
- package/dist/directives/classMap.cjs.map +1 -1
- package/dist/directives/classMap.js.map +1 -1
- package/dist/directives/each.cjs.map +1 -1
- package/dist/directives/each.js +1 -1
- package/dist/directives/each.js.map +1 -1
- package/dist/directives/live.cjs.map +1 -1
- package/dist/directives/live.js.map +1 -1
- package/dist/directives/model.cjs.map +1 -1
- package/dist/directives/model.js.map +1 -1
- package/dist/directives/styleMap.cjs.map +1 -1
- package/dist/directives/styleMap.js.map +1 -1
- package/dist/directives/unsafe-html.cjs.map +1 -1
- package/dist/directives/unsafe-html.js.map +1 -1
- package/dist/directives/when.cjs.map +1 -1
- package/dist/directives/when.js.map +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/forms/field.cjs.map +1 -1
- package/dist/forms/field.js +1 -1
- package/dist/forms/field.js.map +1 -1
- package/dist/host-bind.cjs.map +1 -1
- package/dist/host-bind.js +1 -1
- package/dist/host-bind.js.map +1 -1
- package/dist/ore.cjs +1 -1
- package/dist/ore.cjs.map +1 -1
- package/dist/ore.iife.js +1 -1
- package/dist/ore.iife.js.map +1 -1
- package/dist/ore.js +1180 -2
- package/dist/ore.js.map +1 -1
- package/dist/props.cjs.map +1 -1
- package/dist/props.js +1 -1
- package/dist/props.js.map +1 -1
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js.map +1 -1
- package/dist/slots.cjs.map +1 -1
- package/dist/slots.js +1 -1
- package/dist/slots.js.map +1 -1
- package/dist/template/bindings.cjs +1 -1
- package/dist/template/bindings.cjs.map +1 -1
- package/dist/template/bindings.js +1 -1
- package/dist/template/bindings.js.map +1 -1
- package/dist/template/compiler.cjs.map +1 -1
- package/dist/template/compiler.js +1 -1
- package/dist/template/compiler.js.map +1 -1
- package/dist/template/instantiator.cjs +1 -1
- package/dist/template/instantiator.cjs.map +1 -1
- package/dist/template/instantiator.js +1 -1
- package/dist/template/instantiator.js.map +1 -1
- package/dist/template/result.cjs.map +1 -1
- package/dist/template/result.js.map +1 -1
- package/dist/testing/flush.cjs.map +1 -1
- package/dist/testing/flush.js.map +1 -1
- package/dist/testing/mount.cjs.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.js +1 -1
- package/dist/testing/render-hook.js.map +1 -1
- package/dist/testing/reset.cjs.map +1 -1
- 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/aria.cjs.map +1 -1
- package/dist/utils/aria.js.map +1 -1
- package/dist/utils/brand.cjs.map +1 -1
- package/dist/utils/brand.js.map +1 -1
- package/dist/utils/css.cjs.map +1 -1
- package/dist/utils/css.js +1 -1
- package/dist/utils/css.js.map +1 -1
- package/dist/utils/dom.cjs +1 -1
- package/dist/utils/dom.cjs.map +1 -1
- package/dist/utils/dom.js +1 -1
- package/dist/utils/dom.js.map +1 -1
- package/dist/utils/emit.cjs.map +1 -1
- package/dist/utils/emit.js.map +1 -1
- package/dist/utils/id.cjs.map +1 -1
- package/dist/utils/id.js.map +1 -1
- package/package.json +10 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindings.js","names":[],"sources":["../../src/template/bindings.ts"],"sourcesContent":["/**\n * template/bindings.ts: Runtime binding appliers.\n *\n * Responsibilities:\n * - Apply each Binding variant to the live DOM (attr, event, html, model,\n * prop, ref, directive).\n * - Manage reactive effects and cleanup registration.\n * - Expose `applyBinding()` as the single dispatch entry point.\n * - Own the signal-to-form-control write path (`syncFormControl`) for regular\n * attribute bindings, and the two-way `model()` write-back loop.\n */\n\nimport { computed, isReactive, type Readable, effect as rawEffect, type Signal, untrack } from '@vielzeug/ripple';\n\nimport { isLiveBinding } from '../directives/live';\nimport { invariant } from '../errors';\nimport { getPropMeta, type PropMeta } from '../props';\nimport { createReplaceableSlot, isStructuredValue, listen, setAttr } from '../utils/dom';\nimport type {\n AttrBinding,\n Binding,\n DirectiveBinding,\n EventBinding,\n HtmlBinding,\n HtmlBindingValue,\n ModelBinding,\n PropBinding,\n RefBinding,\n} from './binding-types';\nimport { isHtmlResult } from './result';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type RegisterCleanup = (fn: () => void) => void;\n\n// ─── Signal helpers ───────────────────────────────────────────────────────────\n\nconst signalEffect = (\n signal: Readable<unknown>,\n update: (v: unknown) => void,\n registerCleanup: RegisterCleanup,\n): void => {\n const sub = rawEffect(() => {\n update(signal.value);\n return undefined;\n });\n\n registerCleanup(() => sub.dispose());\n};\n\n// ─── Form control value sync ──────────────────────────────────────────────────\n\nconst isNativeFormInput = (el: HTMLElement): el is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement =>\n el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement;\n\nconst isCheckableInput = (el: HTMLElement): el is HTMLInputElement =>\n el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio');\n\ntype LiveWriteState = { last: unknown };\n\n/**\n * The single write path from a reactive value to a form control's `value`/`checked`\n * property for regular attribute bindings.\n *\n * Live-write: when the binding was created from `live(source)`, a write is skipped\n * if the DOM value has diverged from this binding's last write (in-progress user\n * input): unless the incoming value already matches the DOM (write would be a no-op).\n */\nexport const syncFormControl = (\n el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n value: unknown,\n isLive?: boolean,\n state: LiveWriteState = { last: undefined },\n): void => {\n const checkable = isCheckableInput(el);\n const next: boolean | string = checkable ? Boolean(value) : value == null ? '' : String(value);\n const current: boolean | string = checkable ? (el as HTMLInputElement).checked : el.value;\n\n if (isLive && state.last !== undefined && !Object.is(current, state.last) && !Object.is(current, next)) return;\n\n if (checkable) (el as HTMLInputElement).checked = next as boolean;\n else el.value = next as string;\n\n if (isLive) state.last = next;\n};\n\n// ─── Attributes ───────────────────────────────────────────────────────────────\n\nconst syncRegisteredProp = (el: HTMLElement, meta: PropMeta, binding: AttrBinding, value: unknown): void => {\n const parsed = isStructuredValue(value)\n ? value\n : meta.parse(\n binding.mode === 'bool' ? (value ? '' : null) : value == null || value === false ? null : String(value),\n );\n\n if (\n !Object.is(\n untrack(() => meta.signal.value),\n parsed,\n )\n ) {\n meta.signal.value = parsed;\n }\n\n if (!meta.reflect) {\n if (isStructuredValue(value)) return;\n\n if (binding.mode === 'bool') el.toggleAttribute(binding.name, Boolean(value));\n else setAttr(el, binding.name, value);\n }\n};\n\nexport const applyAttrBinding = (binding: AttrBinding, registerCleanup: RegisterCleanup): void => {\n const { el, mode, name } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const update = (value: unknown): void => {\n // Resolved per update, not at instantiation: a nested custom element only\n // registers its props when it is connected, which happens when this result\n // mounts: later than binding construction. By update time the registry entry\n // exists, so a structured value (array/object/callback) reaches a registered\n // prop through its signal instead of an attribute round-trip that could not\n // represent it. Unregistered targets get no such pass: structured data to\n // them is an explicit `.prop=` binding.\n const propMeta = getPropMeta(el, name);\n\n if (propMeta) {\n syncRegisteredProp(el, propMeta, binding, value);\n\n return;\n }\n\n if ((name === 'value' && isNativeFormInput(el)) || (name === 'checked' && el instanceof HTMLInputElement)) {\n syncFormControl(el as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement, value, binding.live, liveState);\n\n return;\n }\n\n // `indeterminate` is a property-only concept on checkboxes (no attribute exists):\n // bind it like any other element property so reactive indeterminate state works\n // through the same `indeterminate=\"${sig}\"` syntax as `checked`.\n if (name === 'indeterminate' && el instanceof HTMLInputElement) {\n el.indeterminate = Boolean(value);\n\n return;\n }\n\n if (mode === 'bool') el.toggleAttribute(name, Boolean(value));\n else setAttr(el, name, value);\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Events ───────────────────────────────────────────────────────────────────\n\nconst applyEventBinding = (binding: EventBinding, registerCleanup: RegisterCleanup): void => {\n const { modifiers } = binding;\n\n const handler =\n modifiers?.stop || modifiers?.prevent\n ? (e: Event): void => {\n if (modifiers.stop) e.stopPropagation();\n if (modifiers.prevent) e.preventDefault();\n binding.handler(e);\n }\n : binding.handler;\n\n const options =\n modifiers?.once || modifiers?.capture ? { capture: modifiers.capture, once: modifiers.once } : undefined;\n\n registerCleanup(listen(binding.el, binding.name, handler, options));\n};\n\n// ─── Explicit property bindings ───────────────────────────────────────────────\n\n/**\n * `.prop=${value}` binding: always writes the element property (never an\n * attribute). This is the explicit channel for structured values (arrays,\n * objects, callbacks): the old implicit \"value happens to be an object, so set\n * a property\" fallback in the attr path is gone, so intent lives in the markup.\n * Prototype-polluting names are rejected at compile time (see compiler.ts\n * PROP_RE), not silently dropped here.\n */\nconst applyPropBinding = (binding: PropBinding, registerCleanup: RegisterCleanup): void => {\n const update = (value: unknown): void => {\n (binding.el as unknown as Record<string, unknown>)[binding.name] = value;\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Two-way model bindings ──────────────────────────────────────────────────\n\n/**\n * `model()` binding: signal → control property (through the same live-aware\n * `syncFormControl` write path as `live()`, so an in-progress user edit is never\n * clobbered) and control event → signal (the write-back that makes it two-way).\n */\nconst applyModelBinding = (binding: ModelBinding, registerCleanup: RegisterCleanup): void => {\n const { el, get, signal } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const write = (value: unknown): void => {\n if (isNativeFormInput(el)) {\n syncFormControl(el, value, true, liveState);\n } else {\n // Non-native target: plain property write (custom controls expose `value`).\n (el as unknown as { value: unknown }).value = value;\n }\n };\n\n const sub = rawEffect(() => {\n write(signal.value);\n return undefined;\n });\n\n const onEvent: EventListener = () => {\n const next = get(el);\n\n if (\n !Object.is(\n untrack(() => signal.value),\n next,\n )\n ) {\n (signal as Signal<unknown>).value = next;\n }\n };\n\n registerCleanup(() => sub.dispose());\n registerCleanup(listen(el, binding.event, onEvent));\n};\n\n// ─── Refs ─────────────────────────────────────────────────────────────────────\n\nconst applyRefBinding = (binding: RefBinding, registerCleanup: RegisterCleanup): void => {\n const { el, ref } = binding;\n\n if (typeof ref === 'function') {\n ref(el as never);\n registerCleanup(() => ref(null as never));\n\n return;\n }\n\n ref.value = el as never;\n registerCleanup(() => {\n ref.value = null as never;\n });\n};\n\n// ─── HTML (reactive) ─────────────────────────────────────────────────────────\n\nconst insertHtmlValues = (\n values: HtmlBindingValue[],\n insertBefore: ChildNode,\n registerCleanup: RegisterCleanup,\n): Node[] => {\n const nodes: Node[] = [];\n const parent = insertBefore.parentNode;\n\n invariant(parent, 'html binding anchor has no parent node');\n\n for (const v of values) {\n if (isHtmlResult(v)) {\n const captured = Array.from(v.fragment.childNodes);\n\n parent.insertBefore(v.fragment, insertBefore);\n v.apply(registerCleanup);\n nodes.push(...captured);\n } else if (v != null && v !== false) {\n const text = document.createTextNode(String(v));\n\n parent.insertBefore(text, insertBefore);\n nodes.push(text);\n }\n }\n\n return nodes;\n};\n\nexport const applyHtmlBinding = (binding: HtmlBinding, registerCleanup: RegisterCleanup): void => {\n const { anchor, signal } = binding;\n\n const slot = createReplaceableSlot();\n\n const stop = rawEffect(() => {\n const raw = signal.value;\n\n slot.clear();\n\n if (raw == null || raw.length === 0) return;\n\n untrack(() => {\n slot.setNodes(insertHtmlValues(raw, anchor, slot.registerCleanup));\n });\n });\n\n registerCleanup(() => {\n stop.dispose();\n slot.clear();\n });\n};\n\n// ─── Directives ───────────────────────────────────────────────────────────────\n\nconst applyDirectiveBinding = (binding: DirectiveBinding, registerCleanup: RegisterCleanup): void => {\n binding.directive.mount(binding.anchor, registerCleanup);\n};\n\n// ─── Binding dispatch ─────────────────────────────────────────────────────────\n\nexport const applyBinding = (binding: Binding, registerCleanup: RegisterCleanup): void => {\n switch (binding.type) {\n case 'attr':\n applyAttrBinding(binding, registerCleanup);\n break;\n case 'directive':\n applyDirectiveBinding(binding, registerCleanup);\n break;\n case 'event':\n applyEventBinding(binding, registerCleanup);\n break;\n case 'html':\n applyHtmlBinding(binding, registerCleanup);\n break;\n case 'model':\n applyModelBinding(binding, registerCleanup);\n break;\n case 'prop':\n applyPropBinding(binding, registerCleanup);\n break;\n case 'ref':\n applyRefBinding(binding, registerCleanup);\n break;\n }\n};\n\n// ─── Attr binding factory (used by instantiator) ──────────────────────────────\n\nexport const createAttrBindingFromValue = (\n el: HTMLElement,\n mode: 'attr' | 'bool',\n name: string,\n value: unknown,\n): AttrBinding => {\n if (isLiveBinding(value)) {\n return { el, live: true, mode, name, signal: value.source, type: 'attr' };\n }\n\n if (typeof value === 'function') {\n return { el, mode, name, signal: computed(value as () => unknown), type: 'attr' };\n }\n\n if (isReactive(value)) {\n return { el, mode, name, signal: value as Readable<unknown>, type: 'attr' };\n }\n\n return { el, mode, name, type: 'attr', value };\n};\n\nexport const resolveStaticText = (value: unknown): string => {\n if (value == null) return '';\n\n return String(value);\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bindings.js","names":["rawEffect","signalEffect","sub","effect","update","signal","registerCleanup","isNativeFormInput","el","isCheckableInput","syncFormControl","checkable","next","value","current","state","isLive","syncRegisteredProp","parsed","isStructuredValue","meta","binding","untrack","setAttr","applyAttrBinding","liveState","propMeta","getPropMeta","name","mode","applyEventBinding","handler","modifiers","e","options","listen","applyPropBinding","applyModelBinding","write","get","applyRefBinding","ref","insertHtmlValues","nodes","parent","insertBefore","invariant","v","values","isHtmlResult","captured","text","applyHtmlBinding","slot","createReplaceableSlot","stop","raw","anchor","applyDirectiveBinding","applyBinding","createAttrBindingFromValue","isLiveBinding","computed","isReactive","resolveStaticText"],"sources":["../../src/template/bindings.ts"],"sourcesContent":["/**\n * template/bindings.ts: Runtime binding appliers.\n *\n * Responsibilities:\n * - Apply each Binding variant to the live DOM (attr, event, html, model,\n * prop, ref, directive).\n * - Manage reactive effects and cleanup registration.\n * - Expose `applyBinding()` as the single dispatch entry point.\n * - Own the signal-to-form-control write path (`syncFormControl`) for regular\n * attribute bindings, and the two-way `model()` write-back loop.\n */\n\nimport { computed, isReactive, type Readable, effect as rawEffect, type Signal, untrack } from '@vielzeug/ripple';\n\nimport { isLiveBinding } from '../directives/live';\nimport { invariant } from '../errors';\nimport { getPropMeta, type PropMeta } from '../props';\nimport { createReplaceableSlot, isStructuredValue, listen, setAttr } from '../utils/dom';\nimport type {\n AttrBinding,\n Binding,\n DirectiveBinding,\n EventBinding,\n HtmlBinding,\n HtmlBindingValue,\n ModelBinding,\n PropBinding,\n RefBinding,\n} from './binding-types';\nimport { isHtmlResult } from './result';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type RegisterCleanup = (fn: () => void) => void;\n\n// ─── Signal helpers ───────────────────────────────────────────────────────────\n\nconst signalEffect = (\n signal: Readable<unknown>,\n update: (v: unknown) => void,\n registerCleanup: RegisterCleanup,\n): void => {\n const sub = rawEffect(() => {\n update(signal.value);\n return undefined;\n });\n\n registerCleanup(() => sub.dispose());\n};\n\n// ─── Form control value sync ──────────────────────────────────────────────────\n\nconst isNativeFormInput = (el: HTMLElement): el is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement =>\n el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement;\n\nconst isCheckableInput = (el: HTMLElement): el is HTMLInputElement =>\n el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio');\n\ntype LiveWriteState = { last: unknown };\n\n/**\n * The single write path from a reactive value to a form control's `value`/`checked`\n * property for regular attribute bindings.\n *\n * Live-write: when the binding was created from `live(source)`, a write is skipped\n * if the DOM value has diverged from this binding's last write (in-progress user\n * input): unless the incoming value already matches the DOM (write would be a no-op).\n */\nexport const syncFormControl = (\n el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n value: unknown,\n isLive?: boolean,\n state: LiveWriteState = { last: undefined },\n): void => {\n const checkable = isCheckableInput(el);\n const next: boolean | string = checkable ? Boolean(value) : value == null ? '' : String(value);\n const current: boolean | string = checkable ? (el as HTMLInputElement).checked : el.value;\n\n if (isLive && state.last !== undefined && !Object.is(current, state.last) && !Object.is(current, next)) return;\n\n if (checkable) (el as HTMLInputElement).checked = next as boolean;\n else el.value = next as string;\n\n if (isLive) state.last = next;\n};\n\n// ─── Attributes ───────────────────────────────────────────────────────────────\n\nconst syncRegisteredProp = (el: HTMLElement, meta: PropMeta, binding: AttrBinding, value: unknown): void => {\n const parsed = isStructuredValue(value)\n ? value\n : meta.parse(\n binding.mode === 'bool' ? (value ? '' : null) : value == null || value === false ? null : String(value),\n );\n\n if (\n !Object.is(\n untrack(() => meta.signal.value),\n parsed,\n )\n ) {\n meta.signal.value = parsed;\n }\n\n if (!meta.reflect) {\n if (isStructuredValue(value)) return;\n\n if (binding.mode === 'bool') el.toggleAttribute(binding.name, Boolean(value));\n else setAttr(el, binding.name, value);\n }\n};\n\nexport const applyAttrBinding = (binding: AttrBinding, registerCleanup: RegisterCleanup): void => {\n const { el, mode, name } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const update = (value: unknown): void => {\n // Resolved per update, not at instantiation: a nested custom element only\n // registers its props when it is connected, which happens when this result\n // mounts: later than binding construction. By update time the registry entry\n // exists, so a structured value (array/object/callback) reaches a registered\n // prop through its signal instead of an attribute round-trip that could not\n // represent it. Unregistered targets get no such pass: structured data to\n // them is an explicit `.prop=` binding.\n const propMeta = getPropMeta(el, name);\n\n if (propMeta) {\n syncRegisteredProp(el, propMeta, binding, value);\n\n return;\n }\n\n if ((name === 'value' && isNativeFormInput(el)) || (name === 'checked' && el instanceof HTMLInputElement)) {\n syncFormControl(el as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement, value, binding.live, liveState);\n\n return;\n }\n\n // `indeterminate` is a property-only concept on checkboxes (no attribute exists):\n // bind it like any other element property so reactive indeterminate state works\n // through the same `indeterminate=\"${sig}\"` syntax as `checked`.\n if (name === 'indeterminate' && el instanceof HTMLInputElement) {\n el.indeterminate = Boolean(value);\n\n return;\n }\n\n if (mode === 'bool') el.toggleAttribute(name, Boolean(value));\n else setAttr(el, name, value);\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Events ───────────────────────────────────────────────────────────────────\n\nconst applyEventBinding = (binding: EventBinding, registerCleanup: RegisterCleanup): void => {\n const { modifiers } = binding;\n\n const handler =\n modifiers?.stop || modifiers?.prevent\n ? (e: Event): void => {\n if (modifiers.stop) e.stopPropagation();\n if (modifiers.prevent) e.preventDefault();\n binding.handler(e);\n }\n : binding.handler;\n\n const options =\n modifiers?.once || modifiers?.capture ? { capture: modifiers.capture, once: modifiers.once } : undefined;\n\n registerCleanup(listen(binding.el, binding.name, handler, options));\n};\n\n// ─── Explicit property bindings ───────────────────────────────────────────────\n\n/**\n * `.prop=${value}` binding: always writes the element property (never an\n * attribute). This is the explicit channel for structured values (arrays,\n * objects, callbacks): the old implicit \"value happens to be an object, so set\n * a property\" fallback in the attr path is gone, so intent lives in the markup.\n * Prototype-polluting names are rejected at compile time (see compiler.ts\n * PROP_RE), not silently dropped here.\n */\nconst applyPropBinding = (binding: PropBinding, registerCleanup: RegisterCleanup): void => {\n const update = (value: unknown): void => {\n (binding.el as unknown as Record<string, unknown>)[binding.name] = value;\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Two-way model bindings ──────────────────────────────────────────────────\n\n/**\n * `model()` binding: signal → control property (through the same live-aware\n * `syncFormControl` write path as `live()`, so an in-progress user edit is never\n * clobbered) and control event → signal (the write-back that makes it two-way).\n */\nconst applyModelBinding = (binding: ModelBinding, registerCleanup: RegisterCleanup): void => {\n const { el, get, signal } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const write = (value: unknown): void => {\n if (isNativeFormInput(el)) {\n syncFormControl(el, value, true, liveState);\n } else {\n // Non-native target: plain property write (custom controls expose `value`).\n (el as unknown as { value: unknown }).value = value;\n }\n };\n\n const sub = rawEffect(() => {\n write(signal.value);\n return undefined;\n });\n\n const onEvent: EventListener = () => {\n const next = get(el);\n\n if (\n !Object.is(\n untrack(() => signal.value),\n next,\n )\n ) {\n (signal as Signal<unknown>).value = next;\n }\n };\n\n registerCleanup(() => sub.dispose());\n registerCleanup(listen(el, binding.event, onEvent));\n};\n\n// ─── Refs ─────────────────────────────────────────────────────────────────────\n\nconst applyRefBinding = (binding: RefBinding, registerCleanup: RegisterCleanup): void => {\n const { el, ref } = binding;\n\n if (typeof ref === 'function') {\n ref(el as never);\n registerCleanup(() => ref(null as never));\n\n return;\n }\n\n ref.value = el as never;\n registerCleanup(() => {\n ref.value = null as never;\n });\n};\n\n// ─── HTML (reactive) ─────────────────────────────────────────────────────────\n\nconst insertHtmlValues = (\n values: HtmlBindingValue[],\n insertBefore: ChildNode,\n registerCleanup: RegisterCleanup,\n): Node[] => {\n const nodes: Node[] = [];\n const parent = insertBefore.parentNode;\n\n invariant(parent, 'html binding anchor has no parent node');\n\n for (const v of values) {\n if (isHtmlResult(v)) {\n const captured = Array.from(v.fragment.childNodes);\n\n parent.insertBefore(v.fragment, insertBefore);\n v.apply(registerCleanup);\n nodes.push(...captured);\n } else if (v != null && v !== false) {\n const text = document.createTextNode(String(v));\n\n parent.insertBefore(text, insertBefore);\n nodes.push(text);\n }\n }\n\n return nodes;\n};\n\nexport const applyHtmlBinding = (binding: HtmlBinding, registerCleanup: RegisterCleanup): void => {\n const { anchor, signal } = binding;\n\n const slot = createReplaceableSlot();\n\n const stop = rawEffect(() => {\n const raw = signal.value;\n\n slot.clear();\n\n if (raw == null || raw.length === 0) return;\n\n untrack(() => {\n slot.setNodes(insertHtmlValues(raw, anchor, slot.registerCleanup));\n });\n });\n\n registerCleanup(() => {\n stop.dispose();\n slot.clear();\n });\n};\n\n// ─── Directives ───────────────────────────────────────────────────────────────\n\nconst applyDirectiveBinding = (binding: DirectiveBinding, registerCleanup: RegisterCleanup): void => {\n binding.directive.mount(binding.anchor, registerCleanup);\n};\n\n// ─── Binding dispatch ─────────────────────────────────────────────────────────\n\nexport const applyBinding = (binding: Binding, registerCleanup: RegisterCleanup): void => {\n switch (binding.type) {\n case 'attr':\n applyAttrBinding(binding, registerCleanup);\n break;\n case 'directive':\n applyDirectiveBinding(binding, registerCleanup);\n break;\n case 'event':\n applyEventBinding(binding, registerCleanup);\n break;\n case 'html':\n applyHtmlBinding(binding, registerCleanup);\n break;\n case 'model':\n applyModelBinding(binding, registerCleanup);\n break;\n case 'prop':\n applyPropBinding(binding, registerCleanup);\n break;\n case 'ref':\n applyRefBinding(binding, registerCleanup);\n break;\n }\n};\n\n// ─── Attr binding factory (used by instantiator) ──────────────────────────────\n\nexport const createAttrBindingFromValue = (\n el: HTMLElement,\n mode: 'attr' | 'bool',\n name: string,\n value: unknown,\n): AttrBinding => {\n if (isLiveBinding(value)) {\n return { el, live: true, mode, name, signal: value.source, type: 'attr' };\n }\n\n if (typeof value === 'function') {\n return { el, mode, name, signal: computed(value as () => unknown), type: 'attr' };\n }\n\n if (isReactive(value)) {\n return { el, mode, name, signal: value as Readable<unknown>, type: 'attr' };\n }\n\n return { el, mode, name, type: 'attr', value };\n};\n\nexport const resolveStaticText = (value: unknown): string => {\n if (value == null) return '';\n\n return String(value);\n};\n"],"mappings":"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"}
|
|
@@ -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`\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 PROP: 'prop',\n REF: 'ref',\n} as const;\n\nexport type DetectedSlotKind = (typeof SlotKind)[keyof typeof SlotKind];\n\n/**\n * Event modifiers accepted after `@event.` in a template: `@click.stop.prevent`.\n * `stop`/`prevent` wrap the handler (stopPropagation/preventDefault); `once`/`capture`\n * map to the native addEventListener options of the same name.\n */\nexport type EventModifiers = {\n capture?: boolean;\n once?: boolean;\n prevent?: boolean;\n stop?: boolean;\n};\n\nconst EVENT_MODIFIERS: EventModifiers = { capture: true, once: true, prevent: true, stop: true };\n\nconst parseEventModifiers = (name: string, modifiers: string[]): EventModifiers | undefined => {\n if (modifiers.length === 0) return undefined;\n\n const parsed: EventModifiers = {};\n\n for (const modifier of modifiers) {\n if (!(modifier in EVENT_MODIFIERS)) throw new OreApiError(ORE_ERRORS.eventModifierUnknown(name, modifier));\n\n parsed[modifier as keyof EventModifiers] = true;\n }\n\n return parsed;\n};\n\ntype DetectedSlot = {\n eventModifiers?: EventModifiers;\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 eventModifiers?: EventModifiers;\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 PROP_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 return { eventModifiers: parseEventModifiers(name, modifiers), 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 = PROP_RE.exec(str))) {\n const name = m[1];\n\n // Prototype-polluting property names are rejected at compile time: `.prop=`\n // writes the element property directly, so these names must never be writable.\n if (name === '__proto__' || name === 'constructor' || name === 'prototype') {\n throw new OreApiError(ORE_ERRORS.propBindingForbiddenName(name));\n }\n\n return { kind: SlotKind.PROP, name, 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({\n elementId: activeElementId,\n eventModifiers: slot.eventModifiers,\n kind: slot.kind,\n mode,\n name: slot.name,\n });\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":["OreApiError","ORE_ERRORS","require_errors","SlotKind","EVENT_MODIFIERS","parseEventModifiers","modifiers","parsed","modifier","name","EVENT_RE","REF_RE","BOOL_ATTR_RE","PROP_RE","ATTR_RE","detectSlot","m","str","prefix","lastOpen","templateCache","ATTR_VALUE_CONTEXT_RE","normalizeTemplateStrings","out","strings","insideTag","i","s","lastChar","ch","next","ELEMENT_MARKER_ATTR","COMMENT_MARKER_RE","walkNode","node","el","marker","elementPaths","path","content","commentPaths","children","buildStaticTemplate","normalized","html","activeElementId","elementCounter","commentCounter","slots","raw","slot","mode","tpl","topChildren","getStaticTemplate","followPath","root"],"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 PROP: 'prop',\n REF: 'ref',\n} as const;\n\nexport type DetectedSlotKind = (typeof SlotKind)[keyof typeof SlotKind];\n\n/**\n * Event modifiers accepted after `@event.` in a template: `@click.stop.prevent`.\n * `stop`/`prevent` wrap the handler (stopPropagation/preventDefault); `once`/`capture`\n * map to the native addEventListener options of the same name.\n */\nexport type EventModifiers = {\n capture?: boolean;\n once?: boolean;\n prevent?: boolean;\n stop?: boolean;\n};\n\nconst EVENT_MODIFIERS: EventModifiers = { capture: true, once: true, prevent: true, stop: true };\n\nconst parseEventModifiers = (name: string, modifiers: string[]): EventModifiers | undefined => {\n if (modifiers.length === 0) return undefined;\n\n const parsed: EventModifiers = {};\n\n for (const modifier of modifiers) {\n if (!(modifier in EVENT_MODIFIERS)) throw new OreApiError(ORE_ERRORS.eventModifierUnknown(name, modifier));\n\n parsed[modifier as keyof EventModifiers] = true;\n }\n\n return parsed;\n};\n\ntype DetectedSlot = {\n eventModifiers?: EventModifiers;\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 eventModifiers?: EventModifiers;\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 PROP_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 return { eventModifiers: parseEventModifiers(name, modifiers), 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 = PROP_RE.exec(str))) {\n const name = m[1];\n\n // Prototype-polluting property names are rejected at compile time: `.prop=`\n // writes the element property directly, so these names must never be writable.\n if (name === '__proto__' || name === 'constructor' || name === 'prototype') {\n throw new OreApiError(ORE_ERRORS.propBindingForbiddenName(name));\n }\n\n return { kind: SlotKind.PROP, name, 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({\n elementId: activeElementId,\n eventModifiers: slot.eventModifiers,\n kind: slot.kind,\n mode,\n name: slot.name,\n });\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":"A,iCAkBA,IAAaG,EAAW,CACtB,KAAM,OACN,UAAW,WACX,MAAO,QACP,KAAM,OACN,KAAM,OACN,IAAK,KACP,EAgBMC,EAAkC,CAAE,QAAS,GAAM,KAAM,GAAM,QAAS,GAAM,KAAM,EAAK,EAEzFC,GAAuB,EAAc,IAAoD,CAC7F,GAAIC,EAAU,SAAW,EAAG,OAE5B,IAAMC,EAAyB,CAAC,EAEhC,IAAK,IAAMC,KAAYF,EAAW,CAChC,GAAI,EAAEE,KAAYJ,GAAkB,MAAM,IAAIJ,EAAAA,YAAYC,EAAAA,WAAW,qBAAqBQ,EAAMD,CAAQ,CAAC,EAEzG,EAAOA,GAAoC,EAC7C,CAEA,OAAOD,CACT,EA+BMG,EAAW,+CACXC,EAAS,sBACTC,EAAe,8CACfC,EAAU,8CACVC,EAAU,4CAEVC,EAAc,GAA8B,CAChD,IAAIC,EAEJ,GAAK,EAAIN,EAAS,KAAKO,CAAG,EAAI,CAC5B,IAAMC,EAASD,EAAI,MAAM,EAAG,CAACD,EAAE,EAAE,CAAC,MAAM,EAClC,CAACP,EAAM,GAAGH,GAAaU,EAAE,EAAE,CAAC,MAAM,GAAG,EAE3C,MAAO,CAAE,eAAgBX,EAAoBI,EAAMH,CAAS,EAAG,KAAMH,EAAS,MAAO,OAAM,QAAO,CACpG,CAEA,GAAK,EAAIQ,EAAO,KAAKM,CAAG,EACtB,MAAO,CAAE,KAAMd,EAAS,IAAK,OAAQc,EAAI,MAAM,EAAG,CAACD,EAAE,EAAE,CAAC,MAAM,CAAE,EAGlE,GAAK,EAAIJ,EAAa,KAAKK,CAAG,EAC5B,MAAO,CAAE,KAAMd,EAAS,UAAW,KAAMa,EAAE,GAAI,OAAQC,EAAI,MAAM,EAAG,CAACD,EAAE,EAAE,CAAC,MAAM,CAAE,EAGpF,GAAK,EAAIH,EAAQ,KAAKI,CAAG,EAAI,CAC3B,IAAMR,EAAOO,EAAE,GAIf,GAAIP,IAAS,aAAeA,IAAS,eAAiBA,IAAS,YAC7D,MAAM,IAAIT,EAAAA,YAAYC,EAAAA,WAAW,yBAAyBQ,CAAI,CAAC,EAGjE,MAAO,CAAE,KAAMN,EAAS,KAAM,OAAM,OAAQc,EAAI,MAAM,EAAG,CAACD,EAAE,EAAE,CAAC,MAAM,CAAE,CACzE,CAEA,GAAK,EAAIF,EAAQ,KAAKG,CAAG,EACvB,MAAO,CAAE,KAAMd,EAAS,KAAM,KAAMa,EAAE,GAAI,OAAQC,EAAI,MAAM,EAAG,CAACD,EAAE,EAAE,CAAC,MAAM,CAAE,EAG/E,IAAMG,EAAWF,EAAI,YAAY,GAAG,EAGpC,GAAIE,EAFcF,EAAI,YAAY,GAEnB,GAAaA,EAAIE,EAAW,KAAO,IAChD,MAAM,IAAInB,EAAAA,YAAYC,EAAAA,WAAW,0BAA0B,EAG7D,MAAO,CAAE,KAAME,EAAS,KAAM,OAAQc,CAAI,CAC5C,EAIMG,EAAgB,IAAI,QAUpBC,EAAwB,wCAUxBC,EAA4B,GAA4C,CAC5E,IAAMC,EAAM,MAAM,KAAKC,CAAO,EAC1BC,EAAY,GAEhB,IAAK,IAAIC,EAAI,EAAGA,EAAIH,EAAI,OAAS,EAAG,IAAK,CACvC,IAAMI,EAAIJ,EAAIG,GACRE,EAAWD,EAAEA,EAAE,OAAS,GAM9B,IAAK,IAAME,KAAMF,EACXE,IAAO,IAAK,EAAY,GACnBA,IAAO,MAAK,EAAY,IAInC,IAAKD,IAAa,KAAOA,IAAa,MAAQH,GAAaJ,EAAsB,KAAKM,EAAE,MAAM,EAAG,EAAE,CAAC,EAAG,CACrG,EAAID,GAAKC,EAAE,MAAM,EAAG,EAAE,EAEtB,IAAMG,EAAOP,EAAIG,EAAI,GAEjBI,EAAK,WAAWF,CAAQ,IAAG,EAAIF,EAAI,GAAKI,EAAK,MAAM,CAAC,EAC1D,CACF,CAEA,OAAOP,CACT,EAQMQ,EAAsB,aACtBC,EAAoB,cAEpBC,GACJ,EACA,EACA,EACA,IACS,CACT,GAAIC,EAAK,WAAa,KAAK,aAAc,CACvC,IAAMC,EAAKD,EACLE,EAASD,EAAG,aAAaJ,CAAmB,EAE9CK,IAAW,OACbC,EAAa,IAAI,OAAOD,CAAM,EAAG,CAAC,GAAGE,CAAI,CAAC,EAC1CH,EAAG,gBAAgBJ,CAAmB,EAE1C,MAAO,GAAIG,EAAK,WAAa,KAAK,aAAc,CAC9C,IAAMK,EAAWL,EAAiB,UAC5BlB,EAAIuB,IAAY,KAAyC,KAAlCP,EAAkB,KAAKO,CAAO,EAEvDvB,GACFwB,EAAa,IAAI,OAAOxB,EAAE,EAAE,EAAG,CAAC,GAAGsB,CAAI,CAAC,CAE5C,CAEA,IAAMG,EAAWP,EAAK,WAEtB,IAAK,IAAIR,EAAI,EAAGA,EAAIe,EAAS,OAAQ,IAAKR,EAASQ,EAASf,GAAI,CAAC,GAAGY,EAAMZ,CAAC,EAAGW,EAAcG,CAAY,CAC1G,EAEME,EAAuB,GAA0D,CACrF,IAAMC,EAAarB,EAAyBE,CAAO,EAC/CoB,EAAO,GACPC,EACAC,EAAiB,EACjBC,EAAiB,EACfC,EAAoB,CAAC,EAE3B,IAAK,IAAItB,EAAI,EAAGA,EAAIiB,EAAW,OAAS,EAAG,IAAK,CAC9C,IAAMM,EAAMN,EAAWjB,GACjBwB,EAAOnC,EAAWkC,CAAG,EAE3B,GAAIC,EAAK,OAAS/C,EAAS,KACzB,GAAQ,GAAG+C,EAAK,OAAO,UAAUH,EAAe,KAChDC,EAAM,KAAK,CAAE,UAAWD,EAAgB,KAAM5C,EAAS,IAAK,CAAC,EAC7D,IACA,EAAkB,IAAA,OACb,CAEH0C,IAAoB,IAAA,IAAaK,EAAK,OAAO,YAAY,GAAG,EAAIA,EAAK,OAAO,YAAY,GAAG,GAG3F,EAAkB,IAClB,GAAQ,GAAGA,EAAK,OAAO,GAAGnB,EAAoB,IAAIc,EAAgB,IAElE,GAAQK,EAAK,OAGf,IAAMC,EACJD,EAAK,OAAS/C,EAAS,UAAY,OAAS+C,EAAK,OAAS/C,EAAS,KAAO,OAAS,IAAA,GAErF6C,EAAM,KAAK,CACT,UAAWH,EACX,eAAgBK,EAAK,eACrB,KAAMA,EAAK,KACX,OACA,KAAMA,EAAK,IACb,CAAC,CACH,CACF,CAEA,GAAQP,EAAWA,EAAW,OAAS,IAAM,GAE7C,IAAMS,EAAM,SAAS,cAAc,UAAU,EAE7C,EAAI,UAAYR,EAEhB,IAAMP,EAAe,IAAI,IACnBG,EAAe,IAAI,IACnBa,EAAcD,EAAI,QAAQ,WAEhC,IAAK,IAAI1B,EAAI,EAAGA,EAAI2B,EAAY,OAAQ,IAAKpB,EAASoB,EAAY3B,GAAI,CAACA,CAAC,EAAGW,EAAcG,CAAY,EAErG,MAAO,CAAE,eAAc,QAASY,EAAK,eAAc,OAAM,CAC3D,EAEaE,EAAqB,GAA0D,CAC1F,IAAIF,EAAMhC,EAAc,IAAII,CAAO,EAOnC,OALK4B,IACH,EAAMV,EAAoBlB,CAAO,EACjCJ,EAAc,IAAII,EAAS4B,CAAG,GAGzBA,CACT,EAIaG,GAAc,EAAY,IAAyB,CAC9D,IAAIrB,EAAasB,EAEjB,IAAK,IAAM9B,KAAKY,EAAM,EAAOJ,EAAK,WAAWR,GAE7C,OAAOQ,CACT,E"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{ORE_ERRORS as e,OreApiError as t}from"../errors.js";var n={ATTR:`attr`,BOOL_ATTR:`boolAttr`,EVENT:`event`,NODE:`node`,PROP:`prop`,REF:`ref`},r={capture:!0,once:!0,prevent:!0,stop:!0},i=(n,i)=>{if(i.length===0)return;let a={};for(let o of i){if(!(o in r))throw new t(e.eventModifierUnknown(n,o));a[o]=!0}return a},a=/\s+@([a-zA-Z_][-a-zA-Z0-9_.-]*)\s*=\s*["']?$/,o=/\s+ref\s*=\s*["']?$/,s=/\s+\?([a-zA-Z_][-a-zA-Z0-9_]*)\s*=\s*["']?$/,c=/\s+\.([a-zA-Z_][-a-zA-Z0-9_]*)\s*=\s*["']?$/,l=/\s+([a-zA-Z_][-a-zA-Z0-9_]*)\s*=\s*["']?$/,u=r=>{let u;if(u=a.exec(r)){let e=r.slice(0,-u[0].length),[t,...a]=u[1].split(`.`);return{eventModifiers:i(t,a),kind:n.EVENT,name:t,prefix:e}}if(u=o.exec(r))return{kind:n.REF,prefix:r.slice(0,-u[0].length)};if(u=s.exec(r))return{kind:n.BOOL_ATTR,name:u[1],prefix:r.slice(0,-u[0].length)};if(u=c.exec(r)){let i=u[1];if(i===`__proto__`||i===`constructor`||i===`prototype`)throw new t(e.propBindingForbiddenName(i));return{kind:n.PROP,name:i,prefix:r.slice(0,-u[0].length)}}if(u=l.exec(r))return{kind:n.ATTR,name:u[1],prefix:r.slice(0,-u[0].length)};let d=r.lastIndexOf(`<`);if(d>r.lastIndexOf(`>`)&&r[d+1]!==`/`)throw new t(e.templateInterpolationInTag);return{kind:n.NODE,prefix:r}},d
|
|
1
|
+
import{ORE_ERRORS as e,OreApiError as t}from"../errors.js";var n={ATTR:`attr`,BOOL_ATTR:`boolAttr`,EVENT:`event`,NODE:`node`,PROP:`prop`,REF:`ref`},r={capture:!0,once:!0,prevent:!0,stop:!0},i=(n,i)=>{if(i.length===0)return;let a={};for(let o of i){if(!(o in r))throw new t(e.eventModifierUnknown(n,o));a[o]=!0}return a},a=/\s+@([a-zA-Z_][-a-zA-Z0-9_.-]*)\s*=\s*["']?$/,o=/\s+ref\s*=\s*["']?$/,s=/\s+\?([a-zA-Z_][-a-zA-Z0-9_]*)\s*=\s*["']?$/,c=/\s+\.([a-zA-Z_][-a-zA-Z0-9_]*)\s*=\s*["']?$/,l=/\s+([a-zA-Z_][-a-zA-Z0-9_]*)\s*=\s*["']?$/,u=r=>{let u;if(u=a.exec(r)){let e=r.slice(0,-u[0].length),[t,...a]=u[1].split(`.`);return{eventModifiers:i(t,a),kind:n.EVENT,name:t,prefix:e}}if(u=o.exec(r))return{kind:n.REF,prefix:r.slice(0,-u[0].length)};if(u=s.exec(r))return{kind:n.BOOL_ATTR,name:u[1],prefix:r.slice(0,-u[0].length)};if(u=c.exec(r)){let i=u[1];if(i===`__proto__`||i===`constructor`||i===`prototype`)throw new t(e.propBindingForbiddenName(i));return{kind:n.PROP,name:i,prefix:r.slice(0,-u[0].length)}}if(u=l.exec(r))return{kind:n.ATTR,name:u[1],prefix:r.slice(0,-u[0].length)};let d=r.lastIndexOf(`<`);if(d>r.lastIndexOf(`>`)&&r[d+1]!==`/`)throw new t(e.templateInterpolationInTag);return{kind:n.NODE,prefix:r}},d=/* @__PURE__ */ new WeakMap,f=/[@?]?[a-zA-Z_][-a-zA-Z0-9_.]*\s*=\s*$/,p=e=>{let t=Array.from(e),n=!1;for(let e=0;e<t.length-1;e++){let r=t[e],i=r[r.length-1];for(let e of r)e===`<`?n=!0:e===`>`&&(n=!1);if((i===`"`||i===`'`)&&n&&f.test(r.slice(0,-1))){t[e]=r.slice(0,-1);let n=t[e+1];n.startsWith(i)&&(t[e+1]=n.slice(1))}}return t},m=`data-ore-b`,h=/^ore:(\d+)$/,g=(e,t,n,r)=>{if(e.nodeType===Node.ELEMENT_NODE){let r=e,i=r.getAttribute(m);i!==null&&(n.set(Number(i),[...t]),r.removeAttribute(m))}else if(e.nodeType===Node.COMMENT_NODE){let n=e.nodeValue,i=n===null?null:h.exec(n);i&&r.set(Number(i[1]),[...t])}let i=e.childNodes;for(let e=0;e<i.length;e++)g(i[e],[...t,e],n,r)},_=e=>{let t=p(e),r=``,i,a=0,o=0,s=[];for(let e=0;e<t.length-1;e++){let c=t[e],l=u(c);if(l.kind===n.NODE)r+=`${l.prefix}<!--ore:${o}-->`,s.push({commentId:o,kind:n.NODE}),o++,i=void 0;else{i===void 0||l.prefix.lastIndexOf(`<`)>l.prefix.lastIndexOf(`>`)?(i=a++,r+=`${l.prefix} ${m}="${i}"`):r+=l.prefix;let e=l.kind===n.BOOL_ATTR?`bool`:l.kind===n.ATTR?`attr`:void 0;s.push({elementId:i,eventModifiers:l.eventModifiers,kind:l.kind,mode:e,name:l.name})}}r+=t[t.length-1]??``;let c=document.createElement(`template`);c.innerHTML=r;let l=/* @__PURE__ */ new Map,d=/* @__PURE__ */ new Map,f=c.content.childNodes;for(let e=0;e<f.length;e++)g(f[e],[e],l,d);return{commentPaths:d,element:c,elementPaths:l,slots:s}},v=e=>{let t=d.get(e);return t||(t=_(e),d.set(e,t)),t},y=(e,t)=>{let n=e;for(let e of t)n=n.childNodes[e];return n};export{n as SlotKind,y as followPath,v as getStaticTemplate};
|
|
2
2
|
//# sourceMappingURL=compiler.js.map
|
|
@@ -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`\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 PROP: 'prop',\n REF: 'ref',\n} as const;\n\nexport type DetectedSlotKind = (typeof SlotKind)[keyof typeof SlotKind];\n\n/**\n * Event modifiers accepted after `@event.` in a template: `@click.stop.prevent`.\n * `stop`/`prevent` wrap the handler (stopPropagation/preventDefault); `once`/`capture`\n * map to the native addEventListener options of the same name.\n */\nexport type EventModifiers = {\n capture?: boolean;\n once?: boolean;\n prevent?: boolean;\n stop?: boolean;\n};\n\nconst EVENT_MODIFIERS: EventModifiers = { capture: true, once: true, prevent: true, stop: true };\n\nconst parseEventModifiers = (name: string, modifiers: string[]): EventModifiers | undefined => {\n if (modifiers.length === 0) return undefined;\n\n const parsed: EventModifiers = {};\n\n for (const modifier of modifiers) {\n if (!(modifier in EVENT_MODIFIERS)) throw new OreApiError(ORE_ERRORS.eventModifierUnknown(name, modifier));\n\n parsed[modifier as keyof EventModifiers] = true;\n }\n\n return parsed;\n};\n\ntype DetectedSlot = {\n eventModifiers?: EventModifiers;\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 eventModifiers?: EventModifiers;\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 PROP_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 return { eventModifiers: parseEventModifiers(name, modifiers), 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 = PROP_RE.exec(str))) {\n const name = m[1];\n\n // Prototype-polluting property names are rejected at compile time: `.prop=`\n // writes the element property directly, so these names must never be writable.\n if (name === '__proto__' || name === 'constructor' || name === 'prototype') {\n throw new OreApiError(ORE_ERRORS.propBindingForbiddenName(name));\n }\n\n return { kind: SlotKind.PROP, name, 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({\n elementId: activeElementId,\n eventModifiers: slot.eventModifiers,\n kind: slot.kind,\n mode,\n name: slot.name,\n });\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":["SlotKind","EVENT_MODIFIERS","parseEventModifiers","modifiers","parsed","modifier","OreApiError","ORE_ERRORS","name","EVENT_RE","REF_RE","BOOL_ATTR_RE","PROP_RE","ATTR_RE","detectSlot","m","str","prefix","lastOpen","templateCache","ATTR_VALUE_CONTEXT_RE","normalizeTemplateStrings","out","strings","insideTag","i","s","lastChar","ch","next","ELEMENT_MARKER_ATTR","COMMENT_MARKER_RE","walkNode","node","el","marker","elementPaths","path","content","commentPaths","children","buildStaticTemplate","normalized","html","activeElementId","elementCounter","commentCounter","slots","raw","slot","mode","tpl","topChildren","getStaticTemplate","followPath","root"],"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 PROP: 'prop',\n REF: 'ref',\n} as const;\n\nexport type DetectedSlotKind = (typeof SlotKind)[keyof typeof SlotKind];\n\n/**\n * Event modifiers accepted after `@event.` in a template: `@click.stop.prevent`.\n * `stop`/`prevent` wrap the handler (stopPropagation/preventDefault); `once`/`capture`\n * map to the native addEventListener options of the same name.\n */\nexport type EventModifiers = {\n capture?: boolean;\n once?: boolean;\n prevent?: boolean;\n stop?: boolean;\n};\n\nconst EVENT_MODIFIERS: EventModifiers = { capture: true, once: true, prevent: true, stop: true };\n\nconst parseEventModifiers = (name: string, modifiers: string[]): EventModifiers | undefined => {\n if (modifiers.length === 0) return undefined;\n\n const parsed: EventModifiers = {};\n\n for (const modifier of modifiers) {\n if (!(modifier in EVENT_MODIFIERS)) throw new OreApiError(ORE_ERRORS.eventModifierUnknown(name, modifier));\n\n parsed[modifier as keyof EventModifiers] = true;\n }\n\n return parsed;\n};\n\ntype DetectedSlot = {\n eventModifiers?: EventModifiers;\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 eventModifiers?: EventModifiers;\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 PROP_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 return { eventModifiers: parseEventModifiers(name, modifiers), 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 = PROP_RE.exec(str))) {\n const name = m[1];\n\n // Prototype-polluting property names are rejected at compile time: `.prop=`\n // writes the element property directly, so these names must never be writable.\n if (name === '__proto__' || name === 'constructor' || name === 'prototype') {\n throw new OreApiError(ORE_ERRORS.propBindingForbiddenName(name));\n }\n\n return { kind: SlotKind.PROP, name, 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({\n elementId: activeElementId,\n eventModifiers: slot.eventModifiers,\n kind: slot.kind,\n mode,\n name: slot.name,\n });\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":"A,2DAkBA,IAAaA,EAAW,CACtB,KAAM,OACN,UAAW,WACX,MAAO,QACP,KAAM,OACN,KAAM,OACN,IAAK,KACP,EAgBMC,EAAkC,CAAE,QAAS,GAAM,KAAM,GAAM,QAAS,GAAM,KAAM,EAAK,EAEzFC,GAAuB,EAAc,IAAoD,CAC7F,GAAIC,EAAU,SAAW,EAAG,OAE5B,IAAMC,EAAyB,CAAC,EAEhC,IAAK,IAAMC,KAAYF,EAAW,CAChC,GAAI,EAAEE,KAAYJ,GAAkB,MAAM,IAAIK,EAAYC,EAAW,qBAAqBC,EAAMH,CAAQ,CAAC,EAEzG,EAAOA,GAAoC,EAC7C,CAEA,OAAOD,CACT,EA+BMK,EAAW,+CACXC,EAAS,sBACTC,EAAe,8CACfC,EAAU,8CACVC,EAAU,4CAEVC,EAAc,GAA8B,CAChD,IAAIC,EAEJ,GAAK,EAAIN,EAAS,KAAKO,CAAG,EAAI,CAC5B,IAAMC,EAASD,EAAI,MAAM,EAAG,CAACD,EAAE,EAAE,CAAC,MAAM,EAClC,CAACP,EAAM,GAAGL,GAAaY,EAAE,EAAE,CAAC,MAAM,GAAG,EAE3C,MAAO,CAAE,eAAgBb,EAAoBM,EAAML,CAAS,EAAG,KAAMH,EAAS,MAAO,OAAM,QAAO,CACpG,CAEA,GAAK,EAAIU,EAAO,KAAKM,CAAG,EACtB,MAAO,CAAE,KAAMhB,EAAS,IAAK,OAAQgB,EAAI,MAAM,EAAG,CAACD,EAAE,EAAE,CAAC,MAAM,CAAE,EAGlE,GAAK,EAAIJ,EAAa,KAAKK,CAAG,EAC5B,MAAO,CAAE,KAAMhB,EAAS,UAAW,KAAMe,EAAE,GAAI,OAAQC,EAAI,MAAM,EAAG,CAACD,EAAE,EAAE,CAAC,MAAM,CAAE,EAGpF,GAAK,EAAIH,EAAQ,KAAKI,CAAG,EAAI,CAC3B,IAAMR,EAAOO,EAAE,GAIf,GAAIP,IAAS,aAAeA,IAAS,eAAiBA,IAAS,YAC7D,MAAM,IAAIF,EAAYC,EAAW,yBAAyBC,CAAI,CAAC,EAGjE,MAAO,CAAE,KAAMR,EAAS,KAAM,OAAM,OAAQgB,EAAI,MAAM,EAAG,CAACD,EAAE,EAAE,CAAC,MAAM,CAAE,CACzE,CAEA,GAAK,EAAIF,EAAQ,KAAKG,CAAG,EACvB,MAAO,CAAE,KAAMhB,EAAS,KAAM,KAAMe,EAAE,GAAI,OAAQC,EAAI,MAAM,EAAG,CAACD,EAAE,EAAE,CAAC,MAAM,CAAE,EAG/E,IAAMG,EAAWF,EAAI,YAAY,GAAG,EAGpC,GAAIE,EAFcF,EAAI,YAAY,GAEnB,GAAaA,EAAIE,EAAW,KAAO,IAChD,MAAM,IAAIZ,EAAYC,EAAW,0BAA0B,EAG7D,MAAO,CAAE,KAAMP,EAAS,KAAM,OAAQgB,CAAI,CAC5C,EAIMG,kBAAgB,IAAI,QAUpBC,EAAwB,wCAUxBC,EAA4B,GAA4C,CAC5E,IAAMC,EAAM,MAAM,KAAKC,CAAO,EAC1BC,EAAY,GAEhB,IAAK,IAAIC,EAAI,EAAGA,EAAIH,EAAI,OAAS,EAAG,IAAK,CACvC,IAAMI,EAAIJ,EAAIG,GACRE,EAAWD,EAAEA,EAAE,OAAS,GAM9B,IAAK,IAAME,KAAMF,EACXE,IAAO,IAAK,EAAY,GACnBA,IAAO,MAAK,EAAY,IAInC,IAAKD,IAAa,KAAOA,IAAa,MAAQH,GAAaJ,EAAsB,KAAKM,EAAE,MAAM,EAAG,EAAE,CAAC,EAAG,CACrG,EAAID,GAAKC,EAAE,MAAM,EAAG,EAAE,EAEtB,IAAMG,EAAOP,EAAIG,EAAI,GAEjBI,EAAK,WAAWF,CAAQ,IAAG,EAAIF,EAAI,GAAKI,EAAK,MAAM,CAAC,EAC1D,CACF,CAEA,OAAOP,CACT,EAQMQ,EAAsB,aACtBC,EAAoB,cAEpBC,GACJ,EACA,EACA,EACA,IACS,CACT,GAAIC,EAAK,WAAa,KAAK,aAAc,CACvC,IAAMC,EAAKD,EACLE,EAASD,EAAG,aAAaJ,CAAmB,EAE9CK,IAAW,OACbC,EAAa,IAAI,OAAOD,CAAM,EAAG,CAAC,GAAGE,CAAI,CAAC,EAC1CH,EAAG,gBAAgBJ,CAAmB,EAE1C,MAAO,GAAIG,EAAK,WAAa,KAAK,aAAc,CAC9C,IAAMK,EAAWL,EAAiB,UAC5BlB,EAAIuB,IAAY,KAAyC,KAAlCP,EAAkB,KAAKO,CAAO,EAEvDvB,GACFwB,EAAa,IAAI,OAAOxB,EAAE,EAAE,EAAG,CAAC,GAAGsB,CAAI,CAAC,CAE5C,CAEA,IAAMG,EAAWP,EAAK,WAEtB,IAAK,IAAIR,EAAI,EAAGA,EAAIe,EAAS,OAAQ,IAAKR,EAASQ,EAASf,GAAI,CAAC,GAAGY,EAAMZ,CAAC,EAAGW,EAAcG,CAAY,CAC1G,EAEME,EAAuB,GAA0D,CACrF,IAAMC,EAAarB,EAAyBE,CAAO,EAC/CoB,EAAO,GACPC,EACAC,EAAiB,EACjBC,EAAiB,EACfC,EAAoB,CAAC,EAE3B,IAAK,IAAItB,EAAI,EAAGA,EAAIiB,EAAW,OAAS,EAAG,IAAK,CAC9C,IAAMM,EAAMN,EAAWjB,GACjBwB,EAAOnC,EAAWkC,CAAG,EAE3B,GAAIC,EAAK,OAASjD,EAAS,KACzB,GAAQ,GAAGiD,EAAK,OAAO,UAAUH,EAAe,KAChDC,EAAM,KAAK,CAAE,UAAWD,EAAgB,KAAM9C,EAAS,IAAK,CAAC,EAC7D,IACA,EAAkB,IAAA,OACb,CAEH4C,IAAoB,IAAA,IAAaK,EAAK,OAAO,YAAY,GAAG,EAAIA,EAAK,OAAO,YAAY,GAAG,GAG3F,EAAkB,IAClB,GAAQ,GAAGA,EAAK,OAAO,GAAGnB,EAAoB,IAAIc,EAAgB,IAElE,GAAQK,EAAK,OAGf,IAAMC,EACJD,EAAK,OAASjD,EAAS,UAAY,OAASiD,EAAK,OAASjD,EAAS,KAAO,OAAS,IAAA,GAErF+C,EAAM,KAAK,CACT,UAAWH,EACX,eAAgBK,EAAK,eACrB,KAAMA,EAAK,KACX,OACA,KAAMA,EAAK,IACb,CAAC,CACH,CACF,CAEA,GAAQP,EAAWA,EAAW,OAAS,IAAM,GAE7C,IAAMS,EAAM,SAAS,cAAc,UAAU,EAE7C,EAAI,UAAYR,EAEhB,IAAMP,kBAAe,IAAI,IACnBG,kBAAe,IAAI,IACnBa,EAAcD,EAAI,QAAQ,WAEhC,IAAK,IAAI1B,EAAI,EAAGA,EAAI2B,EAAY,OAAQ,IAAKpB,EAASoB,EAAY3B,GAAI,CAACA,CAAC,EAAGW,EAAcG,CAAY,EAErG,MAAO,CAAE,eAAc,QAASY,EAAK,eAAc,OAAM,CAC3D,EAEaE,EAAqB,GAA0D,CAC1F,IAAIF,EAAMhC,EAAc,IAAII,CAAO,EAOnC,OALK4B,IACH,EAAMV,EAAoBlB,CAAO,EACjCJ,EAAc,IAAII,EAAS4B,CAAG,GAGzBA,CACT,EAIaG,GAAc,EAAY,IAAyB,CAC9D,IAAIrB,EAAasB,EAEjB,IAAK,IAAM9B,KAAKY,EAAM,EAAOJ,EAAK,WAAWR,GAE7C,OAAOQ,CACT,E"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../errors.cjs"),t=require("./result.cjs"),n=require("../directives/model.cjs"),r=require("./bindings.cjs"),i=require("./compiler.cjs");let a=require("@vielzeug/ripple");var o="html`...`: node-slot comment anchor has no parent node",s=e=>Array.isArray(e)?e:[e],c=(t,n,r)=>{let i=n.parentNode;for(e.invariant(i,o);t.fragment.firstChild;)i.insertBefore(t.fragment.firstChild,n);r.push(t.apply.bind(t))},l=(l,u)=>{let d=i.getStaticTemplate(l),f=d.element.content.cloneNode(!0),p=[],m=[],h=d.slots.map((t,n)=>{let r=u[n];if(t.kind===i.SlotKind.NODE){let n=t.commentId===void 0?void 0:d.commentPaths.get(t.commentId);return e.invariant(n,`compiled template is missing a comment path for node slot ${t.commentId}`),{comment:i.followPath(f,n),slot:t,value:r}}let a=t.elementId===void 0?void 0:d.elementPaths.get(t.elementId);return e.invariant(a,`compiled template is missing an element path for slot ${t.elementId}`),{el:i.followPath(f,a),slot:t,value:r}});for(let{comment:l,el:u,slot:d,value:f}of h)
|
|
1
|
+
const e=require("../errors.cjs"),t=require("./result.cjs"),n=require("../directives/model.cjs"),r=require("./bindings.cjs"),i=require("./compiler.cjs");let a=require("@vielzeug/ripple");var o="html`...`: node-slot comment anchor has no parent node",s=e=>Array.isArray(e)?e:[e],c=(t,n,r)=>{let i=n.parentNode;for(e.invariant(i,o);t.fragment.firstChild;)i.insertBefore(t.fragment.firstChild,n);r.push(t.apply.bind(t))},l=(l,u)=>{let d=i.getStaticTemplate(l),f=d.element.content.cloneNode(!0),p=[],m=[],h=d.slots.map((t,n)=>{let r=u[n];if(t.kind===i.SlotKind.NODE){let n=t.commentId===void 0?void 0:d.commentPaths.get(t.commentId);return e.invariant(n,`compiled template is missing a comment path for node slot ${t.commentId}`),{comment:i.followPath(f,n),slot:t,value:r}}let a=t.elementId===void 0?void 0:d.elementPaths.get(t.elementId);return e.invariant(a,`compiled template is missing an element path for slot ${t.elementId}`),{el:i.followPath(f,a),slot:t,value:r}});for(let{comment:l,el:u,slot:d,value:f}of h)if(d.kind===i.SlotKind.NODE){let n=l;if(e.invariant(n,`compiled template produced a node slot without a comment anchor`),t.isDirectiveResult(f)){p.push({anchor:n,directive:f,type:`directive`});continue}if(t.isHtmlResult(f)){c(f,n,m),n.remove();continue}if(typeof f==`function`||(0,a.isReactive)(f)){let e=typeof f==`function`?(0,a.computed)(()=>s(f())):(0,a.computed)(()=>s(f.value));p.push({anchor:n,signal:e,type:`html`});continue}if(Array.isArray(f)){for(let i of f)if(t.isHtmlResult(i))c(i,n,m);else{let t=n.parentNode;e.invariant(t,o),t.insertBefore(document.createTextNode(r.resolveStaticText(i)),n)}n.remove();continue}n.replaceWith(document.createTextNode(r.resolveStaticText(f)))}else if(e.invariant(u,`compiled template produced an element slot without an element`),d.kind===i.SlotKind.EVENT){let t=d.name;if(e.invariant(t,`compiled template produced an event slot without an event name`),typeof f==`function`)p.push({el:u,handler:f,modifiers:d.eventModifiers,name:t,type:`event`});else if((0,a.isReactive)(f)){let e=f;p.push({el:u,handler:t=>{let n=e.value;typeof n==`function`&&n(t)},modifiers:d.eventModifiers,name:t,type:`event`})}}else if(d.kind===i.SlotKind.PROP){let t=d.name;e.invariant(t,`compiled template produced a prop slot without a property name`),typeof f==`function`?p.push({el:u,name:t,signal:(0,a.computed)(f),type:`prop`}):(0,a.isReactive)(f)?p.push({el:u,name:t,signal:f,type:`prop`}):p.push({el:u,name:t,type:`prop`,value:f})}else if(d.kind===i.SlotKind.REF)f&&p.push({el:u,ref:f,type:`ref`});else if(e.invariant(d.name,`compiled template produced an attr slot without an attribute name`),n.isModelSource(f)){if(d.mode!==`attr`)throw new e.OreApiError(e.ORE_ERRORS.modelRequiresValueBinding(d.name));p.push({el:u,event:f.event??n.modelDefaultEvent(u),get:f.get??n.modelDefaultGet,signal:f.source,type:`model`})}else p.push(r.createAttrBindingFromValue(u,d.mode??`attr`,d.name,f));return t.createHtmlResult(f,e=>{for(let t of p)r.applyBinding(t,e);for(let t of m)t(e)})},u=(e,...t)=>l(e,t);exports.compileTemplate=l,exports.html=u;
|
|
2
2
|
//# sourceMappingURL=instantiator.cjs.map
|
|
@@ -1 +1 @@
|
|
|
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, type Signal } from '@vielzeug/ripple';\n\nimport { isModelSource, modelDefaultEvent, modelDefaultGet } from '../directives/model';\nimport { invariant, ORE_ERRORS, OreApiError } 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({\n el,\n handler: value as (e: Event) => void,\n modifiers: slot.eventModifiers,\n name,\n type: 'event',\n });\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, modifiers: slot.eventModifiers, name, type: 'event' });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.PROP) {\n const name = slot.name;\n\n invariant(name, 'compiled template produced a prop slot without a property name');\n\n if (typeof value === 'function') {\n bindings.push({ el, name, signal: computed(value as () => unknown), type: 'prop' });\n } else if (isReactive(value)) {\n bindings.push({ el, name, signal: value as Readable<unknown>, type: 'prop' });\n } else {\n bindings.push({ el, name, type: 'prop', value });\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\n if (isModelSource(value)) {\n if (slot.mode !== 'attr') throw new OreApiError(ORE_ERRORS.modelRequiresValueBinding(slot.name));\n\n bindings.push({\n el,\n event: value.event ?? modelDefaultEvent(el),\n get: (value.get ?? modelDefaultGet) as (el: HTMLElement) => unknown,\n signal: value.source as Signal<unknown>,\n type: 'model',\n });\n continue;\n }\n\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
|
+
{"version":3,"file":"instantiator.cjs","names":["getStaticTemplate","SlotKind","followPath","isDirectiveResult","isHtmlResult","isReactive","computed","resolveStaticText","isModelSource","OreApiError","ORE_ERRORS","modelDefaultEvent","modelDefaultGet","createAttrBindingFromValue","createHtmlResult","require_errors","require_result","require_model","require_bindings","require_compiler","_vielzeug_ripple","NODE_SLOT_NO_PARENT_MSG","toHtmlValues","raw","embedStaticResult","parent","anchor","result","chainedApplies","compileTemplate","compiled","strings","fragment","bindings","boundSlots","value","values","i","slot","commentPath","elementPath","comment","sig","item","el","name","signalValue","h","e","binding","registerCleanup","chainedApply","html"],"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, type Signal } from '@vielzeug/ripple';\n\nimport { isModelSource, modelDefaultEvent, modelDefaultGet } from '../directives/model';\nimport { invariant, ORE_ERRORS, OreApiError } 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({\n el,\n handler: value as (e: Event) => void,\n modifiers: slot.eventModifiers,\n name,\n type: 'event',\n });\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, modifiers: slot.eventModifiers, name, type: 'event' });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.PROP) {\n const name = slot.name;\n\n invariant(name, 'compiled template produced a prop slot without a property name');\n\n if (typeof value === 'function') {\n bindings.push({ el, name, signal: computed(value as () => unknown), type: 'prop' });\n } else if (isReactive(value)) {\n bindings.push({ el, name, signal: value as Readable<unknown>, type: 'prop' });\n } else {\n bindings.push({ el, name, type: 'prop', value });\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\n if (isModelSource(value)) {\n if (slot.mode !== 'attr') throw new OreApiError(ORE_ERRORS.modelRequiresValueBinding(slot.name));\n\n bindings.push({\n el,\n event: value.event ?? modelDefaultEvent(el),\n get: (value.get ?? modelDefaultGet) as (el: HTMLElement) => unknown,\n signal: value.source as Signal<unknown>,\n type: 'model',\n });\n continue;\n }\n\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,2 +1,2 @@
|
|
|
1
|
-
import{ORE_ERRORS as e,OreApiError as t,invariant as n}from"../errors.js";import{createHtmlResult as r,isDirectiveResult as i,isHtmlResult as a}from"./result.js";import{isModelSource as o,modelDefaultEvent as s,modelDefaultGet as c}from"../directives/model.js";import{applyBinding as l,createAttrBindingFromValue as u,resolveStaticText as d}from"./bindings.js";import{SlotKind as f,followPath as p,getStaticTemplate as m}from"./compiler.js";import{computed as h,isReactive as g}from"@vielzeug/ripple";var _="html`...`: node-slot comment anchor has no parent node",v=e=>Array.isArray(e)?e:[e],y=(e,t,r)=>{let i=t.parentNode;for(n(i,_);e.fragment.firstChild;)i.insertBefore(e.fragment.firstChild,t);r.push(e.apply.bind(e))},b=(b,x)=>{let S=m(b),C=S.element.content.cloneNode(!0),w=[],T=[],E=S.slots.map((e,t)=>{let r=x[t];if(e.kind===f.NODE){let t=e.commentId===void 0?void 0:S.commentPaths.get(e.commentId);return n(t,`compiled template is missing a comment path for node slot ${e.commentId}`),{comment:p(C,t),slot:e,value:r}}let i=e.elementId===void 0?void 0:S.elementPaths.get(e.elementId);return n(i,`compiled template is missing an element path for slot ${e.elementId}`),{el:p(C,i),slot:e,value:r}});for(let{comment:r,el:l,slot:p,value:m}of E)
|
|
1
|
+
import{ORE_ERRORS as e,OreApiError as t,invariant as n}from"../errors.js";import{createHtmlResult as r,isDirectiveResult as i,isHtmlResult as a}from"./result.js";import{isModelSource as o,modelDefaultEvent as s,modelDefaultGet as c}from"../directives/model.js";import{applyBinding as l,createAttrBindingFromValue as u,resolveStaticText as d}from"./bindings.js";import{SlotKind as f,followPath as p,getStaticTemplate as m}from"./compiler.js";import{computed as h,isReactive as g}from"@vielzeug/ripple";var _="html`...`: node-slot comment anchor has no parent node",v=e=>Array.isArray(e)?e:[e],y=(e,t,r)=>{let i=t.parentNode;for(n(i,_);e.fragment.firstChild;)i.insertBefore(e.fragment.firstChild,t);r.push(e.apply.bind(e))},b=(b,x)=>{let S=m(b),C=S.element.content.cloneNode(!0),w=[],T=[],E=S.slots.map((e,t)=>{let r=x[t];if(e.kind===f.NODE){let t=e.commentId===void 0?void 0:S.commentPaths.get(e.commentId);return n(t,`compiled template is missing a comment path for node slot ${e.commentId}`),{comment:p(C,t),slot:e,value:r}}let i=e.elementId===void 0?void 0:S.elementPaths.get(e.elementId);return n(i,`compiled template is missing an element path for slot ${e.elementId}`),{el:p(C,i),slot:e,value:r}});for(let{comment:r,el:l,slot:p,value:m}of E)if(p.kind===f.NODE){let e=r;if(n(e,`compiled template produced a node slot without a comment anchor`),i(m)){w.push({anchor:e,directive:m,type:`directive`});continue}if(a(m)){y(m,e,T),e.remove();continue}if(typeof m==`function`||g(m)){let t=h(typeof m==`function`?()=>v(m()):()=>v(m.value));w.push({anchor:e,signal:t,type:`html`});continue}if(Array.isArray(m)){for(let t of m)if(a(t))y(t,e,T);else{let r=e.parentNode;n(r,_),r.insertBefore(document.createTextNode(d(t)),e)}e.remove();continue}e.replaceWith(document.createTextNode(d(m)))}else if(n(l,`compiled template produced an element slot without an element`),p.kind===f.EVENT){let e=p.name;if(n(e,`compiled template produced an event slot without an event name`),typeof m==`function`)w.push({el:l,handler:m,modifiers:p.eventModifiers,name:e,type:`event`});else if(g(m)){let t=m;w.push({el:l,handler:e=>{let n=t.value;typeof n==`function`&&n(e)},modifiers:p.eventModifiers,name:e,type:`event`})}}else if(p.kind===f.PROP){let e=p.name;n(e,`compiled template produced a prop slot without a property name`),typeof m==`function`?w.push({el:l,name:e,signal:h(m),type:`prop`}):g(m)?w.push({el:l,name:e,signal:m,type:`prop`}):w.push({el:l,name:e,type:`prop`,value:m})}else if(p.kind===f.REF)m&&w.push({el:l,ref:m,type:`ref`});else if(n(p.name,`compiled template produced an attr slot without an attribute name`),o(m)){if(p.mode!==`attr`)throw new t(e.modelRequiresValueBinding(p.name));w.push({el:l,event:m.event??s(l),get:m.get??c,signal:m.source,type:`model`})}else w.push(u(l,p.mode??`attr`,p.name,m));return r(C,e=>{for(let t of w)l(t,e);for(let t of T)t(e)})},x=(e,...t)=>b(e,t);export{b as compileTemplate,x as html};
|
|
2
2
|
//# sourceMappingURL=instantiator.js.map
|
|
@@ -1 +1 @@
|
|
|
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, type Signal } from '@vielzeug/ripple';\n\nimport { isModelSource, modelDefaultEvent, modelDefaultGet } from '../directives/model';\nimport { invariant, ORE_ERRORS, OreApiError } 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({\n el,\n handler: value as (e: Event) => void,\n modifiers: slot.eventModifiers,\n name,\n type: 'event',\n });\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, modifiers: slot.eventModifiers, name, type: 'event' });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.PROP) {\n const name = slot.name;\n\n invariant(name, 'compiled template produced a prop slot without a property name');\n\n if (typeof value === 'function') {\n bindings.push({ el, name, signal: computed(value as () => unknown), type: 'prop' });\n } else if (isReactive(value)) {\n bindings.push({ el, name, signal: value as Readable<unknown>, type: 'prop' });\n } else {\n bindings.push({ el, name, type: 'prop', value });\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\n if (isModelSource(value)) {\n if (slot.mode !== 'attr') throw new OreApiError(ORE_ERRORS.modelRequiresValueBinding(slot.name));\n\n bindings.push({\n el,\n event: value.event ?? modelDefaultEvent(el),\n get: (value.get ?? modelDefaultGet) as (el: HTMLElement) => unknown,\n signal: value.source as Signal<unknown>,\n type: 'model',\n });\n continue;\n }\n\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
|
+
{"version":3,"file":"instantiator.js","names":["NODE_SLOT_NO_PARENT_MSG","toHtmlValues","raw","embedStaticResult","parent","anchor","invariant","result","chainedApplies","compileTemplate","compiled","getStaticTemplate","strings","fragment","bindings","boundSlots","value","values","i","slot","SlotKind","commentPath","followPath","elementPath","comment","isDirectiveResult","isHtmlResult","isReactive","sig","computed","item","resolveStaticText","el","name","signalValue","h","isModelSource","OreApiError","ORE_ERRORS","modelDefaultEvent","modelDefaultGet","createAttrBindingFromValue","createHtmlResult","binding","applyBinding","registerCleanup","chainedApply","html"],"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, type Signal } from '@vielzeug/ripple';\n\nimport { isModelSource, modelDefaultEvent, modelDefaultGet } from '../directives/model';\nimport { invariant, ORE_ERRORS, OreApiError } 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({\n el,\n handler: value as (e: Event) => void,\n modifiers: slot.eventModifiers,\n name,\n type: 'event',\n });\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, modifiers: slot.eventModifiers, name, type: 'event' });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.PROP) {\n const name = slot.name;\n\n invariant(name, 'compiled template produced a prop slot without a property name');\n\n if (typeof value === 'function') {\n bindings.push({ el, name, signal: computed(value as () => unknown), type: 'prop' });\n } else if (isReactive(value)) {\n bindings.push({ el, name, signal: value as Readable<unknown>, type: 'prop' });\n } else {\n bindings.push({ el, name, type: 'prop', value });\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\n if (isModelSource(value)) {\n if (slot.mode !== 'attr') throw new OreApiError(ORE_ERRORS.modelRequiresValueBinding(slot.name));\n\n bindings.push({\n el,\n event: value.event ?? modelDefaultEvent(el),\n get: (value.get ?? modelDefaultGet) as (el: HTMLElement) => unknown,\n signal: value.source as Signal<unknown>,\n type: 'model',\n });\n continue;\n }\n\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: 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,
|
|
1
|
+
{"version":3,"file":"result.cjs","names":["signal","makeBrand","require_brand","_vielzeug_ripple","ref","directiveBrand","createDirectiveResult","isDirectiveResult","htmlResultBrand","isHtmlResult","createHtmlResult","applyFn","nodes","fragment","parent","anchor","registerCleanup"],"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":"A,wEAgBA,SAAgBI,GAAiC,CAC/C,OAAA,EAAOJ,EAAAA,OAAAA,CAAiB,IAAI,CAC9B,CAUA,IAAMK,EAAiBJ,EAAAA,UAA2B,eAAe,EAMpDK,EAAyB,GACpCD,EAAe,MAAM,CAAE,OAAM,CAAC,EAEnBE,EAAoBF,EAAe,GAqC1CG,EAAkBP,EAAAA,UAA8B,iBAAiB,EAE1DQ,EAAeD,EAAgB,GAE5C,SAAgBE,EACd,EACA,EACoB,CAUpB,OAAOF,EAAgB,MAAM,CAAE,MAAOG,EAAS,WAAU,OAT1C,EAAoB,EAAqB,IAAsD,CAC5G,IAAMC,EAAQ,MAAM,KAAKC,EAAS,UAAU,EAK5C,OAHAC,EAAO,aAAaD,EAAUE,CAAM,EACpCJ,EAAQK,CAAe,EAEhBJ,CACT,CAE+D,CAAC,CAClE,C"}
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
1
|
+
{"version":3,"file":"result.js","names":["ref","signal","directiveBrand","makeBrand","createDirectiveResult","isDirectiveResult","htmlResultBrand","isHtmlResult","createHtmlResult","applyFn","nodes","fragment","parent","anchor","registerCleanup"],"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":"A,wFAgBA,SAAgBA,GAAiC,CAC/C,OAAOC,EAAiB,IAAI,CAC9B,CAUA,IAAMC,EAAiBC,EAA2B,eAAe,EAMpDC,EAAyB,GACpCF,EAAe,MAAM,CAAE,OAAM,CAAC,EAEnBG,EAAoBH,EAAe,GAqC1CI,EAAkBH,EAA8B,iBAAiB,EAE1DI,EAAeD,EAAgB,GAE5C,SAAgBE,EACd,EACA,EACoB,CAUpB,OAAOF,EAAgB,MAAM,CAAE,MAAOG,EAAS,WAAU,OAT1C,EAAoB,EAAqB,IAAsD,CAC5G,IAAMC,EAAQ,MAAM,KAAKC,EAAS,UAAU,EAK5C,OAHAC,EAAO,aAAaD,EAAUE,CAAM,EACpCJ,EAAQK,CAAe,EAEhBJ,CACT,CAE+D,CAAC,CAClE,C"}
|
|
@@ -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): 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,
|
|
1
|
+
{"version":3,"file":"flush.cjs","names":["OreError","hasPendingWork","require_errors","require_runtime","OreTimeoutError","MAX_FLUSH_ITERATIONS","flush","options","logger","turns","resolve"],"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":"A,6DAQA,IAAaI,EAAb,cAAqCJ,EAAAA,QAAS,CAAC,EAKzCK,EAAuB,IA2C7B,eAAsBC,EAAM,EAAwB,CAAC,EAAkB,CACrE,GAAM,CAAE,UAAWC,EAEnBC,IAAS,qEAAqE,EAE9E,IAAIC,EAAQ,EAEZ,KAAOR,EAAAA,eAAe,GAAG,CACvB,GAAI,EAAEQ,EAAQJ,EACZ,MAAM,IAAID,EACR,wDAAwDC,EAAqB,8FAE/E,EAGFG,IAAS,sCAAsCC,GAAO,EACtD,MAAM,QAAQ,QAAQ,CACxB,CAEAD,IAAS,wCAAwC,EAEjD,MAAM,IAAI,QAAe,GACvB,OAAO,sBAA0B,IAAc,0BAA4BE,EAAQ,CAAC,EAAIA,EAAQ,CAClG,EAEAF,IAAS,8BAA8B,EAEvC,MAAM,QAAQ,QAAQ,EAEtBA,IAAS,kBAAkB,CAC7B,C"}
|
|
@@ -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): 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,
|
|
1
|
+
{"version":3,"file":"flush.js","names":["OreTimeoutError","OreError","MAX_FLUSH_ITERATIONS","flush","options","logger","turns","hasPendingWork","resolve"],"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":"A,uFAQA,IAAaA,EAAb,cAAqCC,CAAS,CAAC,EAKzCC,EAAuB,IA2C7B,eAAsBC,EAAM,EAAwB,CAAC,EAAkB,CACrE,GAAM,CAAE,UAAWC,EAEnBC,IAAS,qEAAqE,EAE9E,IAAIC,EAAQ,EAEZ,KAAOC,EAAe,GAAG,CACvB,GAAI,EAAED,EAAQJ,EACZ,MAAM,IAAIF,EACR,wDAAwDE,EAAqB,8FAE/E,EAGFG,IAAS,sCAAsCC,GAAO,EACtD,MAAM,QAAQ,QAAQ,CACxB,CAEAD,IAAS,wCAAwC,EAEjD,MAAM,IAAI,QAAe,GACvB,OAAO,sBAA0B,IAAc,0BAA4BG,EAAQ,CAAC,EAAIA,EAAQ,CAClG,EAEAH,IAAS,8BAA8B,EAEvC,MAAM,QAAQ,QAAQ,EAEtBA,IAAS,kBAAkB,CAC7B,C"}
|