{"version":3,"file":"instantiator.cjs","names":[],"sources":["../../src/template/instantiator.ts"],"sourcesContent":["/**\n * template/instantiator.ts — Template instantiation and the `html` tagged literal.\n *\n * Responsibilities:\n * - Clone a compiled static template and wire up live bindings.\n * - Expose `compileTemplate()` and `html` as the public authoring API.\n */\n\nimport { computed, isReactive, type Readable } from '@vielzeug/ripple';\n\nimport { invariant } from '../errors';\nimport type { Binding, HtmlBindingValue } from './binding-types';\nimport { applyBinding, createAttrBindingFromValue, resolveStaticText } from './bindings';\nimport { followPath, getStaticTemplate, SlotKind } from './compiler';\nimport {\n  type CompiledHTMLResult,\n  createHtmlResult,\n  type HTMLResult,\n  isDirectiveResult,\n  isHtmlResult,\n  type Ref,\n  type RefCallback,\n} from './result';\n\n// ─── Template instantiation ──────────────────────────────────────────────────\n\nconst NODE_SLOT_NO_PARENT_MSG = 'html`...`: node-slot comment anchor has no parent node';\n\n/** Normalize a reactive node-slot value to the HtmlBinding signal's array shape. */\nconst toHtmlValues = (raw: unknown): HtmlBindingValue[] =>\n  Array.isArray(raw) ? (raw as HtmlBindingValue[]) : [raw as HtmlBindingValue];\n\n/**\n * Static-embed an already-created HTMLResult at a node-slot anchor: move its\n * fragment children into place and chain its apply into the outer apply phase\n * (so embedded reactive wiring starts when the host template mounts, not now).\n */\nconst embedStaticResult = (\n  result: CompiledHTMLResult,\n  anchor: Comment,\n  chainedApplies: Array<(rc: (fn: () => void) => void) => void>,\n): void => {\n  const parent = anchor.parentNode;\n\n  invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n\n  while (result.fragment.firstChild) parent.insertBefore(result.fragment.firstChild, anchor);\n\n  chainedApplies.push(result.apply.bind(result));\n};\n\n/**\n * Instantiate a compiled template: clone the cached DOM template, navigate\n * to each binding target using pre-recorded paths, and build bindings with\n * direct node references. Returns an HTMLResult ready to mount.\n */\nexport const compileTemplate = (strings: TemplateStringsArray, values: unknown[]): HTMLResult => {\n  const compiled = getStaticTemplate(strings);\n  const fragment = compiled.element.content.cloneNode(true) as DocumentFragment;\n  const bindings: Binding[] = [];\n  // For static HTMLResult embeds: chain their apply calls\n  const chainedApplies: Array<(rc: (fn: () => void) => void) => void> = [];\n\n  // Phase 1: Resolve all binding targets BEFORE any DOM modifications\n  type BoundSlot = { comment?: Comment; el?: HTMLElement; slot: (typeof compiled.slots)[number]; value: unknown };\n\n  const boundSlots: BoundSlot[] = compiled.slots.map((slot, i) => {\n    const value = values[i];\n\n    if (slot.kind === SlotKind.NODE) {\n      const commentPath = slot.commentId !== undefined ? compiled.commentPaths.get(slot.commentId) : undefined;\n\n      invariant(commentPath, `compiled template is missing a comment path for node slot ${slot.commentId}`);\n\n      return { comment: followPath(fragment, commentPath) as Comment, slot, value };\n    }\n\n    const elementPath = slot.elementId !== undefined ? compiled.elementPaths.get(slot.elementId) : undefined;\n\n    invariant(elementPath, `compiled template is missing an element path for slot ${slot.elementId}`);\n\n    return { el: followPath(fragment, elementPath) as HTMLElement, slot, value };\n  });\n\n  // Phase 2: Build bindings (may modify DOM for static content).\n  for (const { comment, el, slot, value } of boundSlots) {\n    if (slot.kind === SlotKind.NODE) {\n      const anchor = comment;\n\n      invariant(anchor, 'compiled template produced a node slot without a comment anchor');\n\n      if (isDirectiveResult(value)) {\n        bindings.push({ anchor, directive: value, type: 'directive' });\n        continue;\n      }\n\n      if (isHtmlResult(value)) {\n        // Static embed: move fragment children into place, chain apply\n        embedStaticResult(value, anchor, chainedApplies);\n        anchor.remove();\n        continue;\n      }\n\n      if (typeof value === 'function' || isReactive(value)) {\n        // Always use the html binding for reactive values — it handles both text\n        // values and HTMLResult values, preventing silent \"[object Object]\"\n        // corruption when a signal's runtime type changes from null/string to HTMLResult.\n        const sig =\n          typeof value === 'function'\n            ? computed(() => toHtmlValues((value as () => unknown)()))\n            : computed(() => toHtmlValues((value as Readable<unknown>).value));\n\n        bindings.push({ anchor, signal: sig, type: 'html' });\n        continue;\n      }\n\n      if (Array.isArray(value)) {\n        for (const item of value) {\n          if (isHtmlResult(item)) {\n            embedStaticResult(item, anchor, chainedApplies);\n          } else {\n            const parent = anchor.parentNode;\n\n            invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n            parent.insertBefore(document.createTextNode(resolveStaticText(item)), anchor);\n          }\n        }\n        anchor.remove();\n        continue;\n      }\n\n      // Static primitive: replace with text node, no binding\n      anchor.replaceWith(document.createTextNode(resolveStaticText(value)));\n      continue;\n    }\n\n    // Element slot\n    invariant(el, 'compiled template produced an element slot without an element');\n\n    if (slot.kind === SlotKind.EVENT) {\n      const name = slot.name;\n\n      invariant(name, 'compiled template produced an event slot without an event name');\n\n      if (typeof value === 'function') {\n        bindings.push({ el, handler: value as (e: Event) => void, name, type: 'event' });\n      } else if (isReactive(value)) {\n        const signalValue = value as Readable<unknown>;\n        const handler = (e: Event) => {\n          const h = signalValue.value;\n\n          if (typeof h === 'function') (h as (e: Event) => void)(e);\n        };\n\n        bindings.push({ el, handler, name, type: 'event' });\n      }\n\n      continue;\n    }\n\n    if (slot.kind === SlotKind.REF) {\n      if (value) {\n        bindings.push({ el, ref: value as Ref<Element> | RefCallback<Element>, type: 'ref' });\n      }\n\n      continue;\n    }\n\n    // attr / boolAttr\n    invariant(slot.name, 'compiled template produced an attr slot without an attribute name');\n    bindings.push(createAttrBindingFromValue(el, slot.mode ?? 'attr', slot.name, value));\n  }\n\n  return createHtmlResult(fragment, (registerCleanup) => {\n    for (const binding of bindings) applyBinding(binding, registerCleanup);\n    for (const chainedApply of chainedApplies) chainedApply(registerCleanup);\n  });\n};\n\nexport const html = (strings: TemplateStringsArray, ...values: unknown[]): HTMLResult =>\n  compileTemplate(strings, values);\n"],"mappings":"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"}