{
  "version": 3,
  "sources": ["../src/render-to-string.ts"],
  "sourcesContent": ["import * as parse5 from 'parse5'\nimport type { Tonic, TonicTemplate } from './index.js'\n\n// Set up minimal globals needed for Tonic SSR in Node.js\n// Only set these up if we're in a Node.js environment (no window)\nif (typeof window === 'undefined') {\n    // @ts-expect-error its ok\n    (global as any).window = {\n        HTMLElement: class HTMLElement {\n            children:any[] = []\n            childNodes:any[] = []\n            attributes:any[] = []\n\n            getRootNode () {\n                return this\n            }\n\n            addEventListener () {}\n            dispatchEvent () {}\n        },\n        customElements: {\n            define: () => {},\n            get: () => null\n        },\n        CustomEvent: class CustomEvent {}\n    }\n}\n\n/**\n * Render a Tonic component instance to an HTML string.\n *\n * This function takes a Tonic component instance, calls its render method,\n * and recursively processes any nested Tonic components to produce a complete\n * HTML string suitable for server-side rendering.\n *\n * @param component - A Tonic component instance to render\n * @returns A promise that resolves to an HTML string\n *\n * @example\n * ```ts\n * import { render } from '@substrate-system/tonic/render-to-string'\n * import { MyComponent } from './components'\n *\n * const component = new MyComponent()\n * const html = await render(component)\n * console.log(html) // <div>...</div>\n * ```\n */\nexport async function render (\n    component:InstanceType<typeof Tonic>\n):Promise<string> {\n    // Get the registry of all registered Tonic components\n    // @ts-expect-error _reg is private but we need it for SSR\n    const registry = component.constructor._reg || {}\n\n    // Initialize props with defaults if not already set\n    if (!component.props || Object.keys(component.props).length === 0) {\n        component.props = component.defaults?.() || {}\n    } else {\n        // Merge defaults with existing props\n        component.props = Object.assign(component.defaults?.() || {}, component.props)\n    }\n\n    // Call the component's render method to get the template\n    const template:TonicTemplate|Promise<TonicTemplate> = component.render()\n    const resolvedTemplate = await Promise.resolve(template)\n\n    // Extract the raw HTML string from the TonicTemplate\n    const htmlString = resolvedTemplate.rawText\n\n    // Parse the HTML string into a parse5 document fragment\n    const fragment = parse5.parseFragment(htmlString)\n\n    // Recursively visit and render nested Tonic components\n    await visitNode(fragment, registry)\n\n    // Serialize the document fragment back to an HTML string\n    return parse5.serialize(fragment)\n}\n\nfunction escapeAttr (s:string):string {\n    return s\n        .replace(/&/g, '&amp;')\n        .replace(/\"/g, '&quot;')\n        .replace(/'/g, '&#x27;')\n        .replace(/</g, '&lt;')\n        .replace(/>/g, '&gt;')\n}\n\nfunction getTagName (name:string):string {\n    return name.match(/[A-Z][a-z0-9]*/g)!\n        .join('-').toLowerCase()\n}\n\n/**\n * Encode component props as HTML attribute string.\n *\n * Simple types (string, number, boolean, null) are encoded\n * using Tonic's type marker conventions. Complex types\n * (objects, arrays, functions) are skipped -- put those in\n * the `state` JSON instead.\n */\nfunction propsToAttrs (\n    props:Record<string, any>,\n    id?:string\n):string {\n    const parts:string[] = []\n\n    if (id) parts.push(`id=\"${escapeAttr(id)}\"`)\n\n    for (const [key, value] of Object.entries(props)) {\n        const attr = key\n            .replace(/([a-z])([A-Z])/g, '$1-$2')\n            .toLowerCase()\n\n        if (value === null) {\n            parts.push(`${attr}=\"null__null\"`)\n        } else if (typeof value === 'boolean') {\n            parts.push(`${attr}=\"${value}__boolean\"`)\n        } else if (typeof value === 'number') {\n            parts.push(`${attr}=\"${value}__float\"`)\n        } else if (typeof value === 'string') {\n            parts.push(`${attr}=\"${escapeAttr(value)}\"`)\n        }\n        // Complex types (objects, arrays, functions) are\n        // skipped -- they belong in the state JSON.\n    }\n\n    return parts.length ? ' ' + parts.join(' ') : ''\n}\n\n/**\n * Generate a `<script>` tag containing serialized hydration\n * state.\n *\n * Embed this in your HTML page so the client-side `hydrate`\n * function can read it. The state object keys should be\n * component `id` attributes, mapping to the props for that\n * component.\n *\n * @example\n * ```ts\n * const script = getHydrationScript({\n *     app: { title: 'Hello', items: [1, 2, 3] }\n * })\n * // <script type=\"application/json\" data-tonic-ssr>\n * //   {\"app\":{\"title\":\"Hello\",\"items\":[1,2,3]}}\n * // </script>\n * ```\n */\nexport function getHydrationScript (\n    state:Record<string, any>\n):string {\n    const json = JSON.stringify(state)\n    return '<script type=\"application/json\" ' +\n        'data-tonic-ssr>' + json + '</script>'\n}\n\n/**\n * Wrap rendered component content in its custom element tag,\n * with props encoded as attributes.\n *\n * Use this to build a hydratable HTML page from\n * server-rendered content.\n *\n * @param component  The component instance that was rendered\n * @param content    The HTML string from `render(component)`\n * @param opts.id    Element `id` attribute (required for\n *                   state transfer via hydration)\n * @param opts.tagName  Override the tag name (defaults to\n *                      the class name converted to kebab-case)\n * @param opts.state    Hydration state -- if provided, a\n *                      `<script data-tonic-ssr>` tag is\n *                      appended with the serialized JSON\n *\n * @example\n * ```ts\n * import { render, toHtml } from\n *     '@substrate-system/tonic/render-to-string'\n *\n * const app = new MyApp()\n * app.props = { title: 'Hello', items: [1, 2, 3] }\n * const content = await render(app)\n *\n * const html = toHtml(app, content, {\n *     id: 'app',\n *     state: {\n *         app: { title: 'Hello', items: [1, 2, 3] }\n *     }\n * })\n * ```\n */\nexport function toHtml (\n    component:InstanceType<typeof Tonic>,\n    content:string,\n    opts?:{\n        id?:string;\n        tagName?:string;\n        state?:Record<string, any>;\n    }\n):string {\n    const tag = opts?.tagName ||\n        getTagName(component.constructor.name)\n\n    const attrs = propsToAttrs(component.props, opts?.id)\n\n    let html = `<${tag}${attrs}>${content}</${tag}>`\n\n    if (opts?.state) {\n        html += '\\n' + getHydrationScript(opts.state)\n    }\n\n    return html\n}\n\n/**\n * Recursively visit nodes in the parse5 AST\n * and render any Tonic components.\n */\nasync function visitNode (node:any, registry:Record<string, any>):Promise<void> {\n    // Check if this node is a registered Tonic component\n    if (node.tagName && registry[node.tagName]) {\n        const ComponentClass = registry[node.tagName]\n\n        // Create an instance of the component\n        const instance = new ComponentClass()\n\n        // Set up the component's attributes/props from the node\n        if (node.attrs && node.attrs.length > 0) {\n            const props = {}\n            for (const attr of node.attrs) {\n                // Convert kebab-case attribute names to camelCase prop names\n                const propName = attr.name.replace(/-(.)/g, (_, char) => char.toUpperCase())\n                props[propName] = attr.value\n            }\n            instance.props = Object.assign(instance.defaults?.() || {}, props)\n        } else {\n            instance.props = instance.defaults?.() || {}\n        }\n\n        // Pass existing child content so this.children works.\n        // Must use defineProperty because in browsers\n        // Element.children is a read-only getter.\n        if (node.childNodes && node.childNodes.length > 0) {\n            const childHtml = parse5.serialize(\n                { childNodes: node.childNodes } as any\n            )\n            if (childHtml.trim()) {\n                const arr = [{\n                    isTonicTemplate: true,\n                    unsafe: false,\n                    rawText: childHtml,\n                    toString () { return childHtml },\n                    valueOf () { return childHtml },\n                }]\n                Object.defineProperty(instance, 'children', {\n                    get () { return arr },\n                    configurable: true,\n                })\n            }\n        }\n\n        // Render the component\n        const template = await Promise.resolve(instance.render())\n        const childHtml = template.rawText\n\n        // Parse the rendered HTML\n        const childFragment = parse5.parseFragment(childHtml)\n\n        // Recursively visit children of the rendered component\n        for (const child of childFragment.childNodes) {\n            if ('childNodes' in child && child.childNodes) {\n                await visitNode(child, registry)\n            }\n        }\n\n        // Replace the component tag with its rendered content\n        node.childNodes = childFragment.childNodes\n    }\n\n    // Visit all child nodes\n    if ('childNodes' in node && node.childNodes) {\n        for (const child of node.childNodes) {\n            await visitNode(child, registry)\n        }\n    }\n}\n"],
  "mappings": "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",
  "names": ["childHtml"]
}
