// `unsafeHtml` — render a raw HTML string as live DOM nodes inline between anchor // comments (an escape hatch for pre-rendered markup: markdown, syntax highlighting). // Reactive on the bound string; the parsed nodes carry NO bindings. The caller owns // trust/sanitization. import { requireCtx, mountable, type Mountable, type Producer } from './build-context.js' import { removeBetween, parseFragment } from './dom-region.js' /** * Render a raw HTML string as live DOM nodes, inline between anchor comments (no * wrapper element). Reactive: when the bound string changes, the previously * inserted fragment is removed and the new HTML parsed in. The parsed nodes carry * NO reactive bindings — `unsafeHtml` is an escape hatch for pre-rendered markup * (markdown, syntax highlighting). The caller is responsible for trust/sanitization. */ export function signalUnsafeHtml( produce: Producer, deps: readonly string[], componentRooted?: boolean, ): Mountable { return mountable(() => buildSignalUnsafeHtml(produce, deps, componentRooted)) } function buildSignalUnsafeHtml( produce: Producer, deps: readonly string[], componentRooted?: boolean, ): Node { const c = requireCtx() const doc = c.doc const start = doc.createComment('unsafe-html') const end = doc.createComment('/unsafe-html') const frag = doc.createDocumentFragment() frag.appendChild(start) frag.appendChild(end) c.specs.push({ deps, produce, componentRooted, commit: (value) => { const parent = end.parentNode if (!parent) return removeBetween(start, end) const html = value == null ? '' : String(value) if (html === '') return const parsed = parseFragment(doc, html) // Snapshot childNodes before insertion (insertBefore drains the fragment). for (const n of Array.from(parsed.childNodes)) parent.insertBefore(n, end) }, }) // On host dispose, clear the inserted region (mirrors signalShow) so an enclosing // arm's teardown doesn't orphan these nodes between now-removed anchors. c.teardowns.push(() => removeBetween(start, end)) return frag }