{
  "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": ["render_to_string_exports", "__export", "getHydrationScript", "render", "toHtml", "__toCommonJS", "parse5", "__name", "render", "component", "registry", "template", "htmlString", "fragment", "visitNode", "escapeAttr", "s", "getTagName", "name", "propsToAttrs", "props", "id", "parts", "key", "value", "attr", "getHydrationScript", "state", "toHtml", "content", "opts", "tag", "attrs", "html", "node", "ComponentClass", "instance", "propName", "_", "char", "childHtml", "arr", "childFragment", "child"]
}
