semajsx 0.7.0 → 0.9.0

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.
Files changed (47) hide show
  1. package/dist/{client-BrupjhG0.mjs → client-CEJQ4fit.mjs} +3 -3
  2. package/dist/{client-BrupjhG0.mjs.map → client-CEJQ4fit.mjs.map} +1 -1
  3. package/dist/{document-DsiJO2jG.mjs → document-Cbz4084O.mjs} +2 -2
  4. package/dist/{document-XKyAs62C.mjs → document-Cfdhi7vG.mjs} +2 -2
  5. package/dist/{document-XKyAs62C.mjs.map → document-Cfdhi7vG.mjs.map} +1 -1
  6. package/dist/dom/index.mjs +2 -2
  7. package/dist/dom/jsx-dev-runtime.mjs +1 -1
  8. package/dist/dom/jsx-runtime.mjs +1 -1
  9. package/dist/index.d.mts +11 -0
  10. package/dist/index.d.mts.map +1 -1
  11. package/dist/index.mjs +1 -1
  12. package/dist/{jsx-runtime-Dc77fsnM.d.mts → jsx-runtime-tdaY-P9K.d.mts} +2 -2
  13. package/dist/{jsx-runtime-Dc77fsnM.d.mts.map → jsx-runtime-tdaY-P9K.d.mts.map} +1 -1
  14. package/dist/{lucide-Ddt_N9dJ.mjs → lucide-DWk3itzO.mjs} +3 -3
  15. package/dist/{lucide-Ddt_N9dJ.mjs.map → lucide-DWk3itzO.mjs.map} +1 -1
  16. package/dist/{resource-pm7qP-jV.mjs → resource-BU0Po0ez.mjs} +2 -2
  17. package/dist/{resource-pm7qP-jV.mjs.map → resource-BU0Po0ez.mjs.map} +1 -1
  18. package/dist/{src-Cv4rRVzv.mjs → src--YS4EvMz.mjs} +9 -6
  19. package/dist/src--YS4EvMz.mjs.map +1 -0
  20. package/dist/{src-CXY-7FC3.mjs → src-77V1Plyd.mjs} +665 -129
  21. package/dist/src-77V1Plyd.mjs.map +1 -0
  22. package/dist/{src-SqJ6k7Xv.mjs → src-BTG08Qnh.mjs} +4 -4
  23. package/dist/{src-SqJ6k7Xv.mjs.map → src-BTG08Qnh.mjs.map} +1 -1
  24. package/dist/{src-C_aFsFJ3.mjs → src-Cm12Y2XV.mjs} +2 -2
  25. package/dist/{src-C_aFsFJ3.mjs.map → src-Cm12Y2XV.mjs.map} +1 -1
  26. package/dist/{src-CAyv9Uf9.mjs → src-Mucdq4zw.mjs} +6 -6
  27. package/dist/{src-CAyv9Uf9.mjs.map → src-Mucdq4zw.mjs.map} +1 -1
  28. package/dist/ssg/index.mjs +6 -6
  29. package/dist/ssg/plugins/docs-theme.d.mts +8 -0
  30. package/dist/ssg/plugins/docs-theme.d.mts.map +1 -1
  31. package/dist/ssg/plugins/docs-theme.mjs +1039 -222
  32. package/dist/ssg/plugins/docs-theme.mjs.map +1 -1
  33. package/dist/ssg/plugins/lucide.mjs +3 -3
  34. package/dist/ssr/client.mjs +4 -4
  35. package/dist/ssr/index.mjs +5 -5
  36. package/dist/terminal/index.d.mts +248 -4
  37. package/dist/terminal/index.d.mts.map +1 -1
  38. package/dist/terminal/index.mjs +3 -3
  39. package/dist/terminal/jsx-dev-runtime.d.mts +2 -2
  40. package/dist/terminal/jsx-dev-runtime.mjs +1 -1
  41. package/dist/terminal/jsx-runtime.d.mts +2 -2
  42. package/dist/terminal/jsx-runtime.mjs +1 -1
  43. package/dist/{types-Bj5q5x2Q.d.mts → types-Bm8rZGKW.d.mts} +2 -2
  44. package/dist/{types-Bj5q5x2Q.d.mts.map → types-Bm8rZGKW.d.mts.map} +1 -1
  45. package/package.json +1 -1
  46. package/dist/src-CXY-7FC3.mjs.map +0 -1
  47. package/dist/src-Cv4rRVzv.mjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import { t as isSignal } from "./utils-BrGmTgfG.mjs";
2
- import { h, v as Fragment } from "./src-Cv4rRVzv.mjs";
3
- import { d as setProperty, u as render } from "./src-C_aFsFJ3.mjs";
2
+ import { h, v as Fragment } from "./src--YS4EvMz.mjs";
3
+ import { d as setProperty, u as render } from "./src-Cm12Y2XV.mjs";
4
4
 
5
5
  //#region ../ssr/src/client/hydrate.ts
6
6
  /**
@@ -737,4 +737,4 @@ function clientResource() {
737
737
 
738
738
  //#endregion
739
739
  export { resolveCSS as a, getIslandInfo as c, hydrateAllIslands as d, hydrateIsland as f, markIslandHydrated as h, resolveAsset as i, hasIslands as l, hydrateIslands as m, getManifest as n, setManifest as o, hydrateIslandById as p, loadStylesheet as r, getIslandIds as s, clientResource as t, hydrate as u };
740
- //# sourceMappingURL=client-BrupjhG0.mjs.map
740
+ //# sourceMappingURL=client-CEJQ4fit.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"client-BrupjhG0.mjs","names":[],"sources":["../../ssr/src/client/hydrate.ts","../../ssr/src/client/client-resource.ts"],"sourcesContent":["/**\n * Client-side hydration for SSR\n * This module runs in the browser and hydrates server-rendered content\n */\n\nimport type { VNode } from \"@semajsx/core\";\nimport { Fragment, h } from \"@semajsx/core\";\nimport { setProperty, render } from \"@semajsx/dom\";\nimport { isSignal } from \"@semajsx/signal\";\n\n/**\n * Island info collected from the DOM\n */\ninterface IslandInfo {\n id: string;\n props: Record<string, any>;\n /** Element with data-island-id (single element islands) */\n element?: HTMLElement;\n /** Start comment node (fragment islands) */\n startComment?: Comment;\n /** End comment node (fragment islands) */\n endComment?: Comment;\n}\n\n/**\n * Type guard for async iterators\n */\nfunction isAsyncIterator(value: unknown): value is AsyncIterableIterator<unknown> {\n if (!value || typeof value !== \"object\") return false;\n const obj = value as Record<string | symbol, unknown>;\n return (\n typeof obj[Symbol.asyncIterator] === \"function\" ||\n (typeof obj.next === \"function\" && typeof obj.return === \"function\")\n );\n}\n\n/**\n * Hydrate a server-rendered DOM tree with client-side interactivity\n * Unlike render(), this preserves existing DOM and only attaches event listeners\n *\n * @param vnode - The VNode to hydrate\n * @param container - The DOM container with server-rendered content\n * @returns The hydrated root node\n *\n * @example\n * ```tsx\n * const vnode = <Counter initial={5} />\n * const container = document.querySelector('[data-island-id=\"island-0\"]')\n * hydrate(vnode, container)\n * ```\n */\nexport function hydrate(vnode: VNode, container: Element): Node | null {\n // Standard hydration: hydrate container's first child\n const nodeToHydrate = container.firstChild;\n\n if (!nodeToHydrate) {\n console.warn(\"[Hydrate] Container is empty, falling back to render\");\n const rendered = renderNode(vnode, container);\n if (rendered) {\n container.appendChild(rendered);\n }\n return rendered;\n }\n\n // Hydrate the VNode tree onto the existing DOM\n try {\n hydrateNode(vnode, nodeToHydrate, container);\n return nodeToHydrate;\n } catch (error) {\n console.error(\"[Hydrate] Error during hydration:\", error);\n // Fall back to client-side rendering if hydration fails\n console.warn(\"[Hydrate] Falling back to client-side rendering\");\n container.innerHTML = \"\";\n return renderNode(vnode, container);\n }\n}\n\n/**\n * Hydrate a VNode onto an existing DOM node\n */\nfunction hydrateNode(vnode: VNode | any, domNode: Node, parentElement: Element): void {\n // Handle null/undefined\n if (vnode == null) {\n return;\n }\n\n // Handle signals - unwrap and subscribe\n if (isSignal(vnode)) {\n // For signal VNodes, we need to hydrate the current value\n // and set up reactivity to update when signal changes\n hydrateSignalNode(vnode, domNode, parentElement);\n return;\n }\n\n // Handle primitives (text nodes)\n if (typeof vnode === \"string\" || typeof vnode === \"number\") {\n if (domNode.nodeType === Node.TEXT_NODE) {\n // Text node already exists, verify content matches\n const expectedText = String(vnode);\n if (domNode.textContent !== expectedText) {\n console.warn(\"[Hydrate] Text mismatch, updating:\", domNode.textContent, \"->\", expectedText);\n domNode.textContent = expectedText;\n }\n }\n return;\n }\n\n // Handle arrays\n if (Array.isArray(vnode)) {\n let currentDomNode: Node | null = domNode;\n for (const child of vnode) {\n if (currentDomNode) {\n hydrateNode(child, currentDomNode, parentElement);\n currentDomNode = currentDomNode.nextSibling;\n }\n }\n return;\n }\n\n // Must be a VNode object\n if (typeof vnode !== \"object\" || !(\"type\" in vnode)) {\n return;\n }\n\n const vnodeTyped = vnode as VNode;\n\n // Handle signal nodes - special reactive nodes\n if (vnodeTyped.type === \"#signal\") {\n const signal = vnodeTyped.props?.signal;\n if (signal && isSignal(signal)) {\n hydrateSignalNode(signal, domNode, parentElement);\n }\n return;\n }\n\n // Handle fragments\n if (vnodeTyped.type === Fragment) {\n let currentDomNode: Node | null = domNode;\n for (const child of vnodeTyped.children) {\n if (currentDomNode) {\n hydrateNode(child, currentDomNode, parentElement);\n currentDomNode = currentDomNode.nextSibling;\n }\n }\n return;\n }\n\n // Handle function components - render and hydrate result\n if (typeof vnodeTyped.type === \"function\") {\n // Merge vnode.children into props.children (mirrors renderVNodeToHTML)\n const props =\n vnodeTyped.children && vnodeTyped.children.length > 0\n ? { ...vnodeTyped.props, children: vnodeTyped.children }\n : vnodeTyped.props || {};\n let result = vnodeTyped.type(props);\n\n // Handle async component\n if (result instanceof Promise) {\n result.then((resolved) => hydrateNode(resolved, domNode, parentElement));\n return;\n }\n\n // Handle async iterator (streaming component)\n if (isAsyncIterator(result)) {\n result.next().then(({ value }) => {\n hydrateNode(value, domNode, parentElement);\n });\n return;\n }\n\n hydrateNode(result, domNode, parentElement);\n return;\n }\n\n // Handle DOM elements\n if (typeof vnodeTyped.type === \"string\") {\n // Skip validation for text nodes - they can legitimately be text in the DOM\n // This happens when signal values or other dynamic content renders as text\n if (domNode.nodeType === Node.TEXT_NODE) {\n // Text node in place of element - possible mismatch, but might be intentional\n // Skip hydration for this node\n return;\n }\n\n if (domNode.nodeType !== Node.ELEMENT_NODE) {\n console.warn(\"[Hydrate] Expected element, got:\", domNode.nodeType);\n return;\n }\n\n const element = domNode as Element;\n\n // Verify tag matches\n if (element.tagName.toLowerCase() !== vnodeTyped.type.toLowerCase()) {\n console.warn(\"[Hydrate] Tag mismatch:\", element.tagName, \"vs\", vnodeTyped.type);\n return;\n }\n\n // Hydrate properties (especially event listeners and reactive props)\n hydrateProperties(element, vnodeTyped.props || {});\n\n // Hydrate children\n hydrateChildren(element, vnodeTyped.children);\n return;\n }\n}\n\n/**\n * Hydrate properties onto an element\n * This is where we attach event listeners and set up reactive properties\n */\nfunction hydrateProperties(element: Element, props: Record<string, any>): void {\n for (const [key, value] of Object.entries(props)) {\n // Skip special props\n if (key === \"children\" || key === \"key\" || key === \"ref\") {\n continue;\n }\n\n // Handle event listeners - these need to be attached\n if (key.startsWith(\"on\")) {\n const eventName = key.slice(2).toLowerCase();\n if (typeof value === \"function\") {\n element.addEventListener(eventName, value as EventListener);\n }\n continue;\n }\n\n // Handle reactive properties (signals)\n if (isSignal(value)) {\n // Set initial value\n setProperty(element, key, value.value);\n\n // Set up reactivity using subscribe\n value.subscribe((newValue: any) => {\n setProperty(element, key, newValue);\n });\n continue;\n }\n\n // For static properties, they should already be set by SSR\n // We can optionally verify them, but usually we trust SSR output\n // to avoid unnecessary DOM operations\n }\n\n // Handle refs\n if (props.ref) {\n if (typeof props.ref === \"function\") {\n props.ref(element);\n } else if (typeof props.ref === \"object\" && props.ref !== null) {\n props.ref.current = element;\n }\n }\n}\n\n/**\n * Hydrate children elements\n */\nfunction hydrateChildren(element: Element, children: any[]): void {\n let currentDomNode = element.firstChild;\n\n for (const child of children) {\n if (!currentDomNode) {\n // Mismatch: VNode has more children than DOM\n // Fall back to appending new nodes\n console.warn(\"[Hydrate] Missing DOM node for child, appending\");\n const newNode = renderNode(child, element);\n if (newNode) {\n element.appendChild(newNode);\n }\n continue;\n }\n\n hydrateNode(child, currentDomNode, element);\n currentDomNode = currentDomNode.nextSibling;\n }\n\n // If DOM has extra nodes, we could warn or remove them\n // For now, we leave them (progressive enhancement)\n}\n\n/**\n * Hydrate a signal VNode\n * Set up reactivity to replace content when signal changes\n */\nfunction hydrateSignalNode(signal: any, domNode: Node, parentElement: Element): void {\n // Get current signal value\n const currentValue = signal.value;\n\n // Handle empty/null signal values - server renders as <!--signal-empty--> comment\n if (\n currentValue == null ||\n currentValue === false ||\n (Array.isArray(currentValue) && currentValue.length === 0)\n ) {\n // Expect a comment node marker\n if (domNode.nodeType === Node.COMMENT_NODE) {\n // Comment marker is correct, nothing to validate\n } else {\n console.warn(\"[Hydrate] Expected comment marker for empty signal, got:\", domNode.nodeType);\n }\n } // For simple values (string, number), the server renders them as text nodes\n else if (typeof currentValue === \"string\" || typeof currentValue === \"number\") {\n if (domNode.nodeType === Node.TEXT_NODE) {\n const expectedText = String(currentValue);\n if (domNode.textContent !== expectedText) {\n console.warn(\"[Hydrate] Signal text mismatch:\", domNode.textContent, \"->\", expectedText);\n domNode.textContent = expectedText;\n }\n }\n } else {\n // For complex values (VNodes, etc.), do full hydration\n hydrateNode(currentValue, domNode, parentElement);\n }\n\n // Set up reactivity to handle signal changes\n // Use an anchor comment node to track position in DOM\n // This is necessary because arrays render as DocumentFragments which can't be tracked\n let anchor: Comment;\n let currentNodes: Node[] = [];\n\n if (domNode.nodeType === Node.COMMENT_NODE) {\n // Already a comment (empty signal), use as anchor\n anchor = domNode as Comment;\n } else {\n // Create anchor and insert it before current node\n anchor = document.createComment(\"signal-anchor\");\n if (domNode.parentNode) {\n domNode.parentNode.insertBefore(anchor, domNode);\n }\n currentNodes = [domNode];\n }\n\n signal.subscribe((newValue: any) => {\n const parent = anchor.parentNode;\n if (!parent) {\n return;\n }\n\n // Remove old nodes\n for (const node of currentNodes) {\n if (node.parentNode) {\n node.parentNode.removeChild(node);\n }\n }\n currentNodes = [];\n\n // Render and insert new content after anchor\n const newNode = renderNode(newValue, parentElement);\n if (newNode) {\n if (newNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {\n // Fragment: insert all children after anchor in correct order\n const fragment = newNode as DocumentFragment;\n const children = Array.from(fragment.childNodes);\n\n // Insert in order by tracking the last inserted node\n let insertAfter: Node | null = anchor;\n for (const child of children) {\n parent.insertBefore(child, insertAfter.nextSibling);\n insertAfter = child;\n currentNodes.push(child);\n }\n } else {\n // Single node: insert after anchor\n parent.insertBefore(newNode, anchor.nextSibling);\n currentNodes = [newNode];\n }\n }\n });\n}\n\n/**\n * Render a VNode to a DOM node (fallback when hydration fails)\n * This is a simplified version of render() just for hydration fallback\n */\n// oxlint-disable-next-line only-used-in-recursion\nfunction renderNode(vnode: any, parentElement: Element): Node | null {\n if (vnode == null || vnode === false || vnode === true) {\n return document.createComment(\"empty\");\n }\n\n if (isSignal(vnode)) {\n return renderNode(vnode.value, parentElement);\n }\n\n if (typeof vnode === \"string\" || typeof vnode === \"number\") {\n return document.createTextNode(String(vnode));\n }\n\n if (Array.isArray(vnode)) {\n // Empty array should render as a comment marker\n if (vnode.length === 0) {\n return document.createComment(\"empty\");\n }\n\n const fragment = document.createDocumentFragment();\n for (const child of vnode) {\n const node = renderNode(child, parentElement);\n if (node) {\n fragment.appendChild(node);\n }\n }\n return fragment;\n }\n\n if (typeof vnode === \"object\" && \"type\" in vnode) {\n const vnodeTyped = vnode as VNode;\n\n // Handle special VNode types\n if (vnodeTyped.type === \"#text\") {\n return document.createTextNode(String(vnodeTyped.props?.nodeValue || \"\"));\n }\n\n if (vnodeTyped.type === \"#signal\") {\n const signal = vnodeTyped.props?.signal;\n if (signal && isSignal(signal)) {\n return renderNode(signal.value, parentElement);\n }\n return document.createTextNode(\"\");\n }\n\n if (vnodeTyped.type === Fragment) {\n const fragment = document.createDocumentFragment();\n for (const child of vnodeTyped.children) {\n const node = renderNode(child, parentElement);\n if (node) {\n fragment.appendChild(node);\n }\n }\n return fragment;\n }\n\n if (typeof vnodeTyped.type === \"function\") {\n const result = vnodeTyped.type(vnodeTyped.props || {});\n return renderNode(result, parentElement);\n }\n\n if (typeof vnodeTyped.type === \"string\") {\n const element = document.createElement(vnodeTyped.type);\n\n // Set properties\n const props = vnodeTyped.props || {};\n for (const [key, value] of Object.entries(props)) {\n if (key === \"children\" || key === \"key\") continue;\n setProperty(element, key, value);\n }\n\n // Render children\n for (const child of vnodeTyped.children) {\n const childNode = renderNode(child, element);\n if (childNode) {\n element.appendChild(childNode);\n }\n }\n\n return element;\n }\n }\n\n return null;\n}\n\n/**\n * Hydrate an island by ID\n * Handles both single-element islands (with data-island-id) and fragment islands (with comment markers)\n *\n * @param islandId - The island ID to hydrate\n * @param Component - The component function to render\n * @param markHydrated - Callback to mark the island as hydrated\n *\n * @example\n * ```tsx\n * import { hydrateIsland, markIslandHydrated } from '@semajsx/ssr/client';\n * import Counter from './Counter';\n *\n * hydrateIsland('counter-0', Counter, markIslandHydrated);\n * ```\n */\nexport function hydrateIsland(\n islandId: string,\n Component: Function,\n markHydrated: (id: string) => void,\n): void {\n // Try single-element island first (has data-island-id on root element)\n const element = document.querySelector(`[data-island-id=\"${islandId}\"]`);\n\n if (element) {\n // Single-element island: replace the element with rendered content\n const props = JSON.parse(element.getAttribute(\"data-island-props\") || \"{}\");\n const parent = element.parentNode;\n if (!parent) return;\n\n // Create VNode for the component\n const vnode: VNode = {\n type: Component as VNode[\"type\"],\n props,\n children: [],\n };\n\n // Render into temp container\n const temp = document.createElement(\"div\");\n render(vnode, temp);\n\n // Replace original element with rendered content\n const children = Array.from(temp.childNodes);\n for (const child of children) {\n parent.insertBefore(child, element);\n }\n parent.removeChild(element);\n\n markHydrated(islandId);\n return;\n }\n\n // Fragment island: find by comment marker\n const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT);\n let startComment: Comment | null = null;\n let comment: Comment | null;\n while ((comment = walker.nextNode() as Comment | null)) {\n if (comment.textContent === `island:${islandId}`) {\n startComment = comment;\n break;\n }\n }\n\n if (startComment) {\n // Get props from script tag\n const script = document.querySelector(`script[data-island=\"${islandId}\"]`);\n const props = script ? JSON.parse(script.textContent || \"{}\") : {};\n\n // Find end comment and collect nodes between markers\n const nodesToRemove: Node[] = [];\n let sibling = startComment.nextSibling;\n let endComment: Comment | null = null;\n while (sibling) {\n if (sibling.nodeType === Node.COMMENT_NODE && sibling.textContent === `/island:${islandId}`) {\n endComment = sibling as Comment;\n break;\n }\n nodesToRemove.push(sibling);\n sibling = sibling.nextSibling;\n }\n\n // Remove old nodes\n for (const node of nodesToRemove) {\n node.parentNode?.removeChild(node);\n }\n\n // Render new content with full reactivity\n const vnode: VNode = {\n type: Component as VNode[\"type\"],\n props,\n children: [],\n };\n const parent = startComment.parentNode;\n if (parent) {\n // Create temp container and use full render for reactivity\n const temp = document.createElement(\"div\");\n render(vnode, temp);\n\n // Move rendered nodes after start comment\n const children = Array.from(temp.childNodes);\n for (const child of children) {\n parent.insertBefore(child, endComment);\n }\n }\n\n // Remove markers and script\n startComment.parentNode?.removeChild(startComment);\n if (endComment) endComment.parentNode?.removeChild(endComment);\n if (script) script.parentNode?.removeChild(script);\n\n markHydrated(islandId);\n }\n}\n\n/**\n * Find all islands on the page (both element and fragment types)\n */\nfunction findAllIslands(): IslandInfo[] {\n const islands: IslandInfo[] = [];\n\n // Find element-based islands (single root element)\n const elements = document.querySelectorAll(\"[data-island-id]\");\n for (const el of elements) {\n const id = el.getAttribute(\"data-island-id\");\n const propsStr = el.getAttribute(\"data-island-props\");\n if (id) {\n islands.push({\n id,\n props: propsStr ? JSON.parse(propsStr) : {},\n element: el as HTMLElement,\n });\n }\n }\n\n // Find fragment-based islands (comment markers + script)\n const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT);\n\n let comment: Comment | null;\n while ((comment = walker.nextNode() as Comment | null)) {\n const match = comment.textContent?.match(/^island:(.+)$/);\n if (match && match[1]) {\n const id = match[1];\n // Find end comment (matches /island:${id})\n let endComment: Comment | null = null;\n let sibling = comment.nextSibling;\n while (sibling) {\n if (sibling.nodeType === Node.COMMENT_NODE && sibling.textContent === `/island:${id}`) {\n endComment = sibling as Comment;\n break;\n }\n sibling = sibling.nextSibling;\n }\n\n // Find props from script tag\n const script = document.querySelector(`script[type=\"application/json\"][data-island=\"${id}\"]`);\n const props = script ? JSON.parse(script.textContent || \"{}\") : {};\n\n islands.push({\n id,\n props,\n startComment: comment,\n endComment: endComment || undefined,\n });\n }\n }\n\n return islands;\n}\n\n/**\n * Hydrate all islands on the page\n * This function is typically called once after the page loads\n *\n * @example\n * ```tsx\n * // In your client entry point\n * import { hydrateIslands } from '@semajsx/ssr/client'\n *\n * // Wait for DOM to be ready\n * if (document.readyState === 'loading') {\n * document.addEventListener('DOMContentLoaded', hydrateIslands)\n * } else {\n * hydrateIslands()\n * }\n * ```\n */\nexport async function hydrateIslands(): Promise<void> {\n const islands = findAllIslands();\n\n if (islands.length === 0) {\n return;\n }\n\n console.log(`[SemaJSX] Found ${islands.length} islands to hydrate`);\n\n // Hydrate islands in parallel for better performance\n const hydrations = islands.map((island) => waitForIslandScript(island));\n\n await Promise.all(hydrations);\n\n console.log(`[SemaJSX] All islands hydrated`);\n}\n\n/**\n * Wait for an island's script to load and hydrate it\n * The actual hydration is performed by the island's entry point script\n * This function just waits for it to complete\n */\nasync function waitForIslandScript(island: IslandInfo): Promise<void> {\n const { id: islandId, element, startComment } = island;\n\n // Check if island is already hydrated\n if (element?.hasAttribute(\"data-hydrated\")) {\n return;\n }\n if (startComment?.parentElement?.querySelector(`[data-island-hydrated=\"${islandId}\"]`)) {\n return;\n }\n\n // Wait for hydration to complete (set by island entry point)\n return new Promise((resolve) => {\n // Check every 50ms for up to 10 seconds\n const maxAttempts = 200;\n let attempts = 0;\n\n const checkInterval = setInterval(() => {\n const isHydrated = element\n ? element.hasAttribute(\"data-hydrated\")\n : document.querySelector(`[data-island-hydrated=\"${islandId}\"]`) !== null;\n\n if (isHydrated) {\n clearInterval(checkInterval);\n resolve();\n } else if (++attempts >= maxAttempts) {\n clearInterval(checkInterval);\n console.warn(`[SemaJSX] Island ${islandId} hydration timeout`);\n resolve();\n }\n }, 50);\n });\n}\n\n/**\n * Get island info by ID\n */\nexport function getIslandInfo(islandId: string): IslandInfo | null {\n // Try element-based first\n const element = document.querySelector(`[data-island-id=\"${islandId}\"]`) as HTMLElement | null;\n\n if (element) {\n const propsStr = element.getAttribute(\"data-island-props\");\n return {\n id: islandId,\n props: propsStr ? JSON.parse(propsStr) : {},\n element,\n };\n }\n\n // Try fragment-based\n const script = document.querySelector(\n `script[type=\"application/json\"][data-island=\"${islandId}\"]`,\n );\n\n if (script) {\n const props = JSON.parse(script.textContent || \"{}\");\n // Find start comment\n const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT);\n let comment: Comment | null;\n while ((comment = walker.nextNode() as Comment | null)) {\n if (comment.textContent === `island:${islandId}`) {\n return {\n id: islandId,\n props,\n startComment: comment,\n };\n }\n }\n }\n\n return null;\n}\n\n/**\n * Manual hydration for a specific island\n * Useful for lazy-loading islands on interaction\n *\n * @param islandId - The island ID to hydrate\n *\n * @example\n * ```tsx\n * // Lazy load an island on click\n * button.addEventListener('click', () => {\n * hydrateIslandById('island-0')\n * })\n * ```\n */\nexport async function hydrateIslandById(islandId: string): Promise<void> {\n const island = getIslandInfo(islandId);\n\n if (!island) {\n console.error(`[SemaJSX] Island not found: ${islandId}`);\n return;\n }\n\n await waitForIslandScript(island);\n}\n\n/**\n * Check if islands are present on the page\n */\nexport function hasIslands(): boolean {\n // Check for element-based islands\n if (document.querySelectorAll(\"[data-island-id]\").length > 0) {\n return true;\n }\n // Check for fragment-based islands\n return document.querySelectorAll('script[type=\"application/json\"][data-island]').length > 0;\n}\n\n/**\n * Get all island IDs on the page\n */\nexport function getIslandIds(): string[] {\n const ids: string[] = [];\n\n // Element-based islands\n const elements = document.querySelectorAll(\"[data-island-id]\");\n for (const el of elements) {\n const id = el.getAttribute(\"data-island-id\");\n if (id) ids.push(id);\n }\n\n // Fragment-based islands\n const scripts = document.querySelectorAll('script[type=\"application/json\"][data-island]');\n for (const script of scripts) {\n const id = script.getAttribute(\"data-island\");\n if (id) ids.push(id);\n }\n\n return ids;\n}\n\n/**\n * Mark an island as hydrated\n * This should be called by the island entry point after hydration completes\n */\nexport function markIslandHydrated(islandId: string): void {\n // Try element-based first\n const element = document.querySelector(`[data-island-id=\"${islandId}\"]`);\n if (element) {\n element.setAttribute(\"data-hydrated\", \"true\");\n return;\n }\n\n // For fragment-based, remove the script tag (no longer needed)\n const script = document.querySelector(\n `script[type=\"application/json\"][data-island=\"${islandId}\"]`,\n );\n if (script) {\n // Mark as hydrated before removal (for any watchers)\n script.setAttribute(\"data-island-hydrated\", islandId);\n // Remove script - props already parsed, no longer needed\n script.remove();\n }\n}\n\n/**\n * Reconstruct VNode children from serialized JSON data.\n *\n * Uses the island module's exports as a registry to resolve component names\n * (prefixed with \"$\") back to their actual functions.\n *\n * @param serialized - Serialized children array from SSR\n * @param registry - Module exports mapping component names to functions\n */\nfunction reconstructChildren(serialized: any[], registry: Record<string, any>): any[] {\n const result: any[] = [];\n for (const node of serialized) {\n if (node === null) continue;\n if (typeof node === \"string\") {\n result.push(node);\n continue;\n }\n if (Array.isArray(node) && node.length === 3 && typeof node[0] === \"string\") {\n const [type, props, children] = node;\n\n // Nested island placeholder — these hydrate independently,\n // push null to preserve children index positions.\n if (type === \"$island\") {\n result.push(null);\n continue;\n }\n\n const resolvedChildren = children ? reconstructChildren(children, registry) : [];\n\n if (type.startsWith(\"$\")) {\n // Component reference — look up in registry\n const name = type.slice(1);\n const component = registry[name];\n if (!component || typeof component !== \"function\") {\n console.warn(`[Hydrate] Unknown component \"${name}\" in island children`);\n continue;\n }\n result.push(h(component, props || {}, ...resolvedChildren));\n } else {\n // HTML element\n result.push(h(type, props || {}, ...resolvedChildren));\n }\n }\n }\n return result;\n}\n\n/**\n * Hydrate all islands with a given component source\n * Finds all elements with data-island-src and hydrates them\n *\n * @param componentSrc - The component source key (e.g., \"components/Counter\")\n * @param Component - The component function to render\n * @param registry - Optional module exports for reconstructing island children\n *\n * @example\n * ```tsx\n * import { hydrateAllIslands } from '@semajsx/ssr/client';\n * import * as CounterModule from './Counter';\n *\n * hydrateAllIslands('components/Counter', CounterModule.Counter, CounterModule);\n * ```\n */\nexport function hydrateAllIslands(\n componentSrc: string,\n Component: Function,\n registry?: Record<string, any>,\n): void {\n // Find all elements with this component source\n const elements = document.querySelectorAll(`[data-island-src=\"${componentSrc}\"]`);\n\n // Also find fragment-based islands (script tags with data-island-src)\n const scripts = document.querySelectorAll(\n `script[type=\"application/json\"][data-island-src=\"${componentSrc}\"]`,\n );\n\n // Hydrate each element-based island\n elements.forEach((element) => {\n const islandId = element.getAttribute(\"data-island-id\");\n if (!islandId) return;\n\n // Skip if already hydrated\n if (element.hasAttribute(\"data-hydrated\")) return;\n\n try {\n const props = JSON.parse(element.getAttribute(\"data-island-props\") || \"{}\");\n\n // Reconstruct children from serialized VNode data if available\n if (registry) {\n const childrenScript = document.querySelector(\n `script[type=\"application/json\"][data-island-children=\"${islandId}\"]`,\n );\n if (childrenScript) {\n try {\n const serialized = JSON.parse(childrenScript.textContent || \"[]\");\n props.children = reconstructChildren(serialized, registry);\n } catch (e) {\n console.warn(\"[Hydrate] Failed to reconstruct island children:\", e);\n }\n childrenScript.remove();\n }\n }\n\n // Create VNode for the component\n const vnode: VNode = {\n type: Component as VNode[\"type\"],\n props,\n children: [],\n };\n\n // Hydrate in-place: attach event listeners, signal subscriptions,\n // and refs while preserving existing DOM and SSR-rendered children.\n hydrateNode(vnode, element, element.parentNode as Element);\n element.setAttribute(\"data-hydrated\", \"true\");\n } catch (error) {\n // SSR content is preserved — the island stays as static HTML\n console.error(`[Hydrate] Island \"${islandId}\" hydration failed:`, error);\n element.setAttribute(\"data-hydration-error\", \"true\");\n }\n });\n\n // Hydrate each fragment-based island\n scripts.forEach((script) => {\n const islandId = script.getAttribute(\"data-island\");\n if (!islandId) return;\n\n // Skip if already hydrated\n if (script.hasAttribute(\"data-island-hydrated\")) return;\n\n try {\n const props = JSON.parse(script.textContent || \"{}\");\n\n // Reconstruct children from serialized VNode data if available\n if (registry) {\n const childrenScript = document.querySelector(\n `script[type=\"application/json\"][data-island-children=\"${islandId}\"]`,\n );\n if (childrenScript) {\n try {\n const serialized = JSON.parse(childrenScript.textContent || \"[]\");\n props.children = reconstructChildren(serialized, registry);\n } catch (e) {\n console.warn(\"[Hydrate] Failed to reconstruct island children:\", e);\n }\n childrenScript.remove();\n }\n }\n\n // Find the comment markers\n const startMarker = `island:${islandId}`;\n const endMarker = `/island:${islandId}`;\n\n // Find and process the fragment\n const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT, null);\n\n let startNode: Comment | null = null;\n let endNode: Comment | null = null;\n let node: Node | null;\n\n while ((node = walker.nextNode())) {\n if (node.nodeValue === startMarker) {\n startNode = node as Comment;\n } else if (node.nodeValue === endMarker) {\n endNode = node as Comment;\n break;\n }\n }\n\n if (startNode && endNode && startNode.parentNode) {\n const parent = startNode.parentNode;\n\n // Non-destructive hydration: move existing nodes into a temp\n // container, hydrate against the existing DOM, then move back.\n // This preserves SSR content and only attaches event listeners\n // and signal subscriptions — matching element-based island behavior.\n const container = document.createElement(\"div\");\n let current: Node | null = startNode.nextSibling;\n while (current && current !== endNode) {\n const next = current.nextSibling;\n container.appendChild(current); // moves node, doesn't clone\n current = next;\n }\n\n // Create VNode and hydrate against existing DOM\n const vnode: VNode = {\n type: Component as VNode[\"type\"],\n props,\n children: [],\n };\n hydrateNode(vnode, container, container);\n\n // Move hydrated nodes back between markers\n while (container.firstChild) {\n parent.insertBefore(container.firstChild, endNode);\n }\n\n // Mark as hydrated\n script.setAttribute(\"data-island-hydrated\", islandId);\n script.remove();\n }\n } catch (error) {\n // SSR content is preserved — the fragment stays as static HTML\n console.error(`[Hydrate] Fragment island \"${islandId}\" hydration failed:`, error);\n }\n });\n}\n\n// Export types\nexport type { IslandInfo };\n","/**\n * Client-side resource utilities for dynamic CSS and asset loading\n */\n\n/**\n * Client manifest structure\n */\nexport interface ClientManifest {\n css: Record<string, string>;\n assets: Record<string, string>;\n}\n\n// Global manifest storage\nlet _manifest: ClientManifest | null = null;\n\n// Track loaded stylesheets to avoid duplicates\nconst loadedStyles = new Set<string>();\n\n/**\n * Set the client manifest (called during initialization)\n */\nexport function setManifest(manifest: ClientManifest): void {\n _manifest = manifest;\n}\n\n/**\n * Get the current manifest\n */\nexport function getManifest(): ClientManifest | null {\n return _manifest;\n}\n\n/**\n * Resolve a CSS path using the manifest\n */\nexport function resolveCSS(href: string): string {\n if (!_manifest) {\n return href;\n }\n\n // Try to find in manifest (remove leading slash for lookup)\n const lookupPath = href.startsWith(\"/\") ? href.slice(1) : href;\n return _manifest.css[lookupPath] || href;\n}\n\n/**\n * Resolve an asset path using the manifest\n */\nexport function resolveAsset(src: string): string {\n if (!_manifest) {\n return src;\n }\n\n // Try to find in manifest (remove leading slash for lookup)\n const lookupPath = src.startsWith(\"/\") ? src.slice(1) : src;\n return _manifest.assets[lookupPath] || src;\n}\n\n/**\n * Dynamically load a stylesheet\n */\nexport function loadStylesheet(href: string): Promise<void> {\n const resolvedHref = resolveCSS(href);\n\n // Skip if already loaded\n if (loadedStyles.has(resolvedHref)) {\n return Promise.resolve();\n }\n\n return new Promise((resolve, reject) => {\n // Check if already in document\n const existing = document.querySelector(`link[href=\"${resolvedHref}\"]`);\n if (existing) {\n loadedStyles.add(resolvedHref);\n resolve();\n return;\n }\n\n const link = document.createElement(\"link\");\n link.rel = \"stylesheet\";\n link.href = resolvedHref;\n\n link.onload = () => {\n loadedStyles.add(resolvedHref);\n resolve();\n };\n\n link.onerror = () => {\n reject(new Error(`Failed to load stylesheet: ${resolvedHref}`));\n };\n\n document.head.appendChild(link);\n });\n}\n\n/**\n * Style component props\n */\nexport interface StyleProps {\n href: string;\n}\n\n/**\n * Asset component props\n */\nexport interface AssetProps {\n src: string;\n}\n\n/**\n * Client-side resource tools\n */\nexport interface ClientResourceTools {\n /** Load and inject a CSS stylesheet */\n Style: (props: StyleProps) => null;\n /** Resolve asset URL from manifest */\n url: (path: string) => string;\n}\n\n/**\n * Create client-side resource tools\n *\n * @example\n * ```tsx\n * import { clientResource } from '@semajsx/ssr/client';\n *\n * const { Style, url } = clientResource();\n *\n * export default function Counter() {\n * return (\n * <>\n * <Style href=\"./counter.css\" />\n * <img src={url('./icon.png')} />\n * </>\n * );\n * }\n * ```\n */\nexport function clientResource(): ClientResourceTools {\n return {\n Style({ href }: StyleProps): null {\n // Load stylesheet on mount\n if (typeof document !== \"undefined\") {\n loadStylesheet(href);\n }\n return null;\n },\n\n url(path: string): string {\n return resolveAsset(path);\n },\n };\n}\n"],"mappings":";;;;;;;;AA2BA,SAAS,gBAAgB,OAAyD;AAChF,KAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;CAChD,MAAM,MAAM;AACZ,QACE,OAAO,IAAI,OAAO,mBAAmB,cACpC,OAAO,IAAI,SAAS,cAAc,OAAO,IAAI,WAAW;;;;;;;;;;;;;;;;;AAmB7D,SAAgB,QAAQ,OAAc,WAAiC;CAErE,MAAM,gBAAgB,UAAU;AAEhC,KAAI,CAAC,eAAe;AAClB,UAAQ,KAAK,uDAAuD;EACpE,MAAM,WAAW,WAAW,OAAO,UAAU;AAC7C,MAAI,SACF,WAAU,YAAY,SAAS;AAEjC,SAAO;;AAIT,KAAI;AACF,cAAY,OAAO,eAAe,UAAU;AAC5C,SAAO;UACA,OAAO;AACd,UAAQ,MAAM,qCAAqC,MAAM;AAEzD,UAAQ,KAAK,kDAAkD;AAC/D,YAAU,YAAY;AACtB,SAAO,WAAW,OAAO,UAAU;;;;;;AAOvC,SAAS,YAAY,OAAoB,SAAe,eAA8B;AAEpF,KAAI,SAAS,KACX;AAIF,KAAI,SAAS,MAAM,EAAE;AAGnB,oBAAkB,OAAO,SAAS,cAAc;AAChD;;AAIF,KAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAAU;AAC1D,MAAI,QAAQ,aAAa,KAAK,WAAW;GAEvC,MAAM,eAAe,OAAO,MAAM;AAClC,OAAI,QAAQ,gBAAgB,cAAc;AACxC,YAAQ,KAAK,sCAAsC,QAAQ,aAAa,MAAM,aAAa;AAC3F,YAAQ,cAAc;;;AAG1B;;AAIF,KAAI,MAAM,QAAQ,MAAM,EAAE;EACxB,IAAI,iBAA8B;AAClC,OAAK,MAAM,SAAS,MAClB,KAAI,gBAAgB;AAClB,eAAY,OAAO,gBAAgB,cAAc;AACjD,oBAAiB,eAAe;;AAGpC;;AAIF,KAAI,OAAO,UAAU,YAAY,EAAE,UAAU,OAC3C;CAGF,MAAM,aAAa;AAGnB,KAAI,WAAW,SAAS,WAAW;EACjC,MAAM,SAAS,WAAW,OAAO;AACjC,MAAI,UAAU,SAAS,OAAO,CAC5B,mBAAkB,QAAQ,SAAS,cAAc;AAEnD;;AAIF,KAAI,WAAW,SAAS,UAAU;EAChC,IAAI,iBAA8B;AAClC,OAAK,MAAM,SAAS,WAAW,SAC7B,KAAI,gBAAgB;AAClB,eAAY,OAAO,gBAAgB,cAAc;AACjD,oBAAiB,eAAe;;AAGpC;;AAIF,KAAI,OAAO,WAAW,SAAS,YAAY;EAEzC,MAAM,QACJ,WAAW,YAAY,WAAW,SAAS,SAAS,IAChD;GAAE,GAAG,WAAW;GAAO,UAAU,WAAW;GAAU,GACtD,WAAW,SAAS,EAAE;EAC5B,IAAI,SAAS,WAAW,KAAK,MAAM;AAGnC,MAAI,kBAAkB,SAAS;AAC7B,UAAO,MAAM,aAAa,YAAY,UAAU,SAAS,cAAc,CAAC;AACxE;;AAIF,MAAI,gBAAgB,OAAO,EAAE;AAC3B,UAAO,MAAM,CAAC,MAAM,EAAE,YAAY;AAChC,gBAAY,OAAO,SAAS,cAAc;KAC1C;AACF;;AAGF,cAAY,QAAQ,SAAS,cAAc;AAC3C;;AAIF,KAAI,OAAO,WAAW,SAAS,UAAU;AAGvC,MAAI,QAAQ,aAAa,KAAK,UAG5B;AAGF,MAAI,QAAQ,aAAa,KAAK,cAAc;AAC1C,WAAQ,KAAK,oCAAoC,QAAQ,SAAS;AAClE;;EAGF,MAAM,UAAU;AAGhB,MAAI,QAAQ,QAAQ,aAAa,KAAK,WAAW,KAAK,aAAa,EAAE;AACnE,WAAQ,KAAK,2BAA2B,QAAQ,SAAS,MAAM,WAAW,KAAK;AAC/E;;AAIF,oBAAkB,SAAS,WAAW,SAAS,EAAE,CAAC;AAGlD,kBAAgB,SAAS,WAAW,SAAS;AAC7C;;;;;;;AAQJ,SAAS,kBAAkB,SAAkB,OAAkC;AAC7E,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,EAAE;AAEhD,MAAI,QAAQ,cAAc,QAAQ,SAAS,QAAQ,MACjD;AAIF,MAAI,IAAI,WAAW,KAAK,EAAE;GACxB,MAAM,YAAY,IAAI,MAAM,EAAE,CAAC,aAAa;AAC5C,OAAI,OAAO,UAAU,WACnB,SAAQ,iBAAiB,WAAW,MAAuB;AAE7D;;AAIF,MAAI,SAAS,MAAM,EAAE;AAEnB,eAAY,SAAS,KAAK,MAAM,MAAM;AAGtC,SAAM,WAAW,aAAkB;AACjC,gBAAY,SAAS,KAAK,SAAS;KACnC;AACF;;;AASJ,KAAI,MAAM,KACR;MAAI,OAAO,MAAM,QAAQ,WACvB,OAAM,IAAI,QAAQ;WACT,OAAO,MAAM,QAAQ,YAAY,MAAM,QAAQ,KACxD,OAAM,IAAI,UAAU;;;;;;AAQ1B,SAAS,gBAAgB,SAAkB,UAAuB;CAChE,IAAI,iBAAiB,QAAQ;AAE7B,MAAK,MAAM,SAAS,UAAU;AAC5B,MAAI,CAAC,gBAAgB;AAGnB,WAAQ,KAAK,kDAAkD;GAC/D,MAAM,UAAU,WAAW,OAAO,QAAQ;AAC1C,OAAI,QACF,SAAQ,YAAY,QAAQ;AAE9B;;AAGF,cAAY,OAAO,gBAAgB,QAAQ;AAC3C,mBAAiB,eAAe;;;;;;;AAWpC,SAAS,kBAAkB,QAAa,SAAe,eAA8B;CAEnF,MAAM,eAAe,OAAO;AAG5B,KACE,gBAAgB,QAChB,iBAAiB,SAChB,MAAM,QAAQ,aAAa,IAAI,aAAa,WAAW,EAGxD,KAAI,QAAQ,aAAa,KAAK,cAAc,OAG1C,SAAQ,KAAK,4DAA4D,QAAQ,SAAS;UAGrF,OAAO,iBAAiB,YAAY,OAAO,iBAAiB,UACnE;MAAI,QAAQ,aAAa,KAAK,WAAW;GACvC,MAAM,eAAe,OAAO,aAAa;AACzC,OAAI,QAAQ,gBAAgB,cAAc;AACxC,YAAQ,KAAK,mCAAmC,QAAQ,aAAa,MAAM,aAAa;AACxF,YAAQ,cAAc;;;OAK1B,aAAY,cAAc,SAAS,cAAc;CAMnD,IAAI;CACJ,IAAI,eAAuB,EAAE;AAE7B,KAAI,QAAQ,aAAa,KAAK,aAE5B,UAAS;MACJ;AAEL,WAAS,SAAS,cAAc,gBAAgB;AAChD,MAAI,QAAQ,WACV,SAAQ,WAAW,aAAa,QAAQ,QAAQ;AAElD,iBAAe,CAAC,QAAQ;;AAG1B,QAAO,WAAW,aAAkB;EAClC,MAAM,SAAS,OAAO;AACtB,MAAI,CAAC,OACH;AAIF,OAAK,MAAM,QAAQ,aACjB,KAAI,KAAK,WACP,MAAK,WAAW,YAAY,KAAK;AAGrC,iBAAe,EAAE;EAGjB,MAAM,UAAU,WAAW,UAAU,cAAc;AACnD,MAAI,QACF,KAAI,QAAQ,aAAa,KAAK,wBAAwB;GAEpD,MAAM,WAAW;GACjB,MAAM,WAAW,MAAM,KAAK,SAAS,WAAW;GAGhD,IAAI,cAA2B;AAC/B,QAAK,MAAM,SAAS,UAAU;AAC5B,WAAO,aAAa,OAAO,YAAY,YAAY;AACnD,kBAAc;AACd,iBAAa,KAAK,MAAM;;SAErB;AAEL,UAAO,aAAa,SAAS,OAAO,YAAY;AAChD,kBAAe,CAAC,QAAQ;;GAG5B;;;;;;AAQJ,SAAS,WAAW,OAAY,eAAqC;AACnE,KAAI,SAAS,QAAQ,UAAU,SAAS,UAAU,KAChD,QAAO,SAAS,cAAc,QAAQ;AAGxC,KAAI,SAAS,MAAM,CACjB,QAAO,WAAW,MAAM,OAAO,cAAc;AAG/C,KAAI,OAAO,UAAU,YAAY,OAAO,UAAU,SAChD,QAAO,SAAS,eAAe,OAAO,MAAM,CAAC;AAG/C,KAAI,MAAM,QAAQ,MAAM,EAAE;AAExB,MAAI,MAAM,WAAW,EACnB,QAAO,SAAS,cAAc,QAAQ;EAGxC,MAAM,WAAW,SAAS,wBAAwB;AAClD,OAAK,MAAM,SAAS,OAAO;GACzB,MAAM,OAAO,WAAW,OAAO,cAAc;AAC7C,OAAI,KACF,UAAS,YAAY,KAAK;;AAG9B,SAAO;;AAGT,KAAI,OAAO,UAAU,YAAY,UAAU,OAAO;EAChD,MAAM,aAAa;AAGnB,MAAI,WAAW,SAAS,QACtB,QAAO,SAAS,eAAe,OAAO,WAAW,OAAO,aAAa,GAAG,CAAC;AAG3E,MAAI,WAAW,SAAS,WAAW;GACjC,MAAM,SAAS,WAAW,OAAO;AACjC,OAAI,UAAU,SAAS,OAAO,CAC5B,QAAO,WAAW,OAAO,OAAO,cAAc;AAEhD,UAAO,SAAS,eAAe,GAAG;;AAGpC,MAAI,WAAW,SAAS,UAAU;GAChC,MAAM,WAAW,SAAS,wBAAwB;AAClD,QAAK,MAAM,SAAS,WAAW,UAAU;IACvC,MAAM,OAAO,WAAW,OAAO,cAAc;AAC7C,QAAI,KACF,UAAS,YAAY,KAAK;;AAG9B,UAAO;;AAGT,MAAI,OAAO,WAAW,SAAS,WAE7B,QAAO,WADQ,WAAW,KAAK,WAAW,SAAS,EAAE,CAAC,EAC5B,cAAc;AAG1C,MAAI,OAAO,WAAW,SAAS,UAAU;GACvC,MAAM,UAAU,SAAS,cAAc,WAAW,KAAK;GAGvD,MAAM,QAAQ,WAAW,SAAS,EAAE;AACpC,QAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,EAAE;AAChD,QAAI,QAAQ,cAAc,QAAQ,MAAO;AACzC,gBAAY,SAAS,KAAK,MAAM;;AAIlC,QAAK,MAAM,SAAS,WAAW,UAAU;IACvC,MAAM,YAAY,WAAW,OAAO,QAAQ;AAC5C,QAAI,UACF,SAAQ,YAAY,UAAU;;AAIlC,UAAO;;;AAIX,QAAO;;;;;;;;;;;;;;;;;;AAmBT,SAAgB,cACd,UACA,WACA,cACM;CAEN,MAAM,UAAU,SAAS,cAAc,oBAAoB,SAAS,IAAI;AAExE,KAAI,SAAS;EAEX,MAAM,QAAQ,KAAK,MAAM,QAAQ,aAAa,oBAAoB,IAAI,KAAK;EAC3E,MAAM,SAAS,QAAQ;AACvB,MAAI,CAAC,OAAQ;EAGb,MAAM,QAAe;GACnB,MAAM;GACN;GACA,UAAU,EAAE;GACb;EAGD,MAAM,OAAO,SAAS,cAAc,MAAM;AAC1C,SAAO,OAAO,KAAK;EAGnB,MAAM,WAAW,MAAM,KAAK,KAAK,WAAW;AAC5C,OAAK,MAAM,SAAS,SAClB,QAAO,aAAa,OAAO,QAAQ;AAErC,SAAO,YAAY,QAAQ;AAE3B,eAAa,SAAS;AACtB;;CAIF,MAAM,SAAS,SAAS,iBAAiB,SAAS,MAAM,WAAW,aAAa;CAChF,IAAI,eAA+B;CACnC,IAAI;AACJ,QAAQ,UAAU,OAAO,UAAU,CACjC,KAAI,QAAQ,gBAAgB,UAAU,YAAY;AAChD,iBAAe;AACf;;AAIJ,KAAI,cAAc;EAEhB,MAAM,SAAS,SAAS,cAAc,uBAAuB,SAAS,IAAI;EAC1E,MAAM,QAAQ,SAAS,KAAK,MAAM,OAAO,eAAe,KAAK,GAAG,EAAE;EAGlE,MAAM,gBAAwB,EAAE;EAChC,IAAI,UAAU,aAAa;EAC3B,IAAI,aAA6B;AACjC,SAAO,SAAS;AACd,OAAI,QAAQ,aAAa,KAAK,gBAAgB,QAAQ,gBAAgB,WAAW,YAAY;AAC3F,iBAAa;AACb;;AAEF,iBAAc,KAAK,QAAQ;AAC3B,aAAU,QAAQ;;AAIpB,OAAK,MAAM,QAAQ,cACjB,MAAK,YAAY,YAAY,KAAK;EAIpC,MAAM,QAAe;GACnB,MAAM;GACN;GACA,UAAU,EAAE;GACb;EACD,MAAM,SAAS,aAAa;AAC5B,MAAI,QAAQ;GAEV,MAAM,OAAO,SAAS,cAAc,MAAM;AAC1C,UAAO,OAAO,KAAK;GAGnB,MAAM,WAAW,MAAM,KAAK,KAAK,WAAW;AAC5C,QAAK,MAAM,SAAS,SAClB,QAAO,aAAa,OAAO,WAAW;;AAK1C,eAAa,YAAY,YAAY,aAAa;AAClD,MAAI,WAAY,YAAW,YAAY,YAAY,WAAW;AAC9D,MAAI,OAAQ,QAAO,YAAY,YAAY,OAAO;AAElD,eAAa,SAAS;;;;;;AAO1B,SAAS,iBAA+B;CACtC,MAAM,UAAwB,EAAE;CAGhC,MAAM,WAAW,SAAS,iBAAiB,mBAAmB;AAC9D,MAAK,MAAM,MAAM,UAAU;EACzB,MAAM,KAAK,GAAG,aAAa,iBAAiB;EAC5C,MAAM,WAAW,GAAG,aAAa,oBAAoB;AACrD,MAAI,GACF,SAAQ,KAAK;GACX;GACA,OAAO,WAAW,KAAK,MAAM,SAAS,GAAG,EAAE;GAC3C,SAAS;GACV,CAAC;;CAKN,MAAM,SAAS,SAAS,iBAAiB,SAAS,MAAM,WAAW,aAAa;CAEhF,IAAI;AACJ,QAAQ,UAAU,OAAO,UAAU,EAAqB;EACtD,MAAM,QAAQ,QAAQ,aAAa,MAAM,gBAAgB;AACzD,MAAI,SAAS,MAAM,IAAI;GACrB,MAAM,KAAK,MAAM;GAEjB,IAAI,aAA6B;GACjC,IAAI,UAAU,QAAQ;AACtB,UAAO,SAAS;AACd,QAAI,QAAQ,aAAa,KAAK,gBAAgB,QAAQ,gBAAgB,WAAW,MAAM;AACrF,kBAAa;AACb;;AAEF,cAAU,QAAQ;;GAIpB,MAAM,SAAS,SAAS,cAAc,gDAAgD,GAAG,IAAI;GAC7F,MAAM,QAAQ,SAAS,KAAK,MAAM,OAAO,eAAe,KAAK,GAAG,EAAE;AAElE,WAAQ,KAAK;IACX;IACA;IACA,cAAc;IACd,YAAY,cAAc;IAC3B,CAAC;;;AAIN,QAAO;;;;;;;;;;;;;;;;;;;AAoBT,eAAsB,iBAAgC;CACpD,MAAM,UAAU,gBAAgB;AAEhC,KAAI,QAAQ,WAAW,EACrB;AAGF,SAAQ,IAAI,mBAAmB,QAAQ,OAAO,qBAAqB;CAGnE,MAAM,aAAa,QAAQ,KAAK,WAAW,oBAAoB,OAAO,CAAC;AAEvE,OAAM,QAAQ,IAAI,WAAW;AAE7B,SAAQ,IAAI,iCAAiC;;;;;;;AAQ/C,eAAe,oBAAoB,QAAmC;CACpE,MAAM,EAAE,IAAI,UAAU,SAAS,iBAAiB;AAGhD,KAAI,SAAS,aAAa,gBAAgB,CACxC;AAEF,KAAI,cAAc,eAAe,cAAc,0BAA0B,SAAS,IAAI,CACpF;AAIF,QAAO,IAAI,SAAS,YAAY;EAE9B,MAAM,cAAc;EACpB,IAAI,WAAW;EAEf,MAAM,gBAAgB,kBAAkB;AAKtC,OAJmB,UACf,QAAQ,aAAa,gBAAgB,GACrC,SAAS,cAAc,0BAA0B,SAAS,IAAI,KAAK,MAEvD;AACd,kBAAc,cAAc;AAC5B,aAAS;cACA,EAAE,YAAY,aAAa;AACpC,kBAAc,cAAc;AAC5B,YAAQ,KAAK,oBAAoB,SAAS,oBAAoB;AAC9D,aAAS;;KAEV,GAAG;GACN;;;;;AAMJ,SAAgB,cAAc,UAAqC;CAEjE,MAAM,UAAU,SAAS,cAAc,oBAAoB,SAAS,IAAI;AAExE,KAAI,SAAS;EACX,MAAM,WAAW,QAAQ,aAAa,oBAAoB;AAC1D,SAAO;GACL,IAAI;GACJ,OAAO,WAAW,KAAK,MAAM,SAAS,GAAG,EAAE;GAC3C;GACD;;CAIH,MAAM,SAAS,SAAS,cACtB,gDAAgD,SAAS,IAC1D;AAED,KAAI,QAAQ;EACV,MAAM,QAAQ,KAAK,MAAM,OAAO,eAAe,KAAK;EAEpD,MAAM,SAAS,SAAS,iBAAiB,SAAS,MAAM,WAAW,aAAa;EAChF,IAAI;AACJ,SAAQ,UAAU,OAAO,UAAU,CACjC,KAAI,QAAQ,gBAAgB,UAAU,WACpC,QAAO;GACL,IAAI;GACJ;GACA,cAAc;GACf;;AAKP,QAAO;;;;;;;;;;;;;;;;AAiBT,eAAsB,kBAAkB,UAAiC;CACvE,MAAM,SAAS,cAAc,SAAS;AAEtC,KAAI,CAAC,QAAQ;AACX,UAAQ,MAAM,+BAA+B,WAAW;AACxD;;AAGF,OAAM,oBAAoB,OAAO;;;;;AAMnC,SAAgB,aAAsB;AAEpC,KAAI,SAAS,iBAAiB,mBAAmB,CAAC,SAAS,EACzD,QAAO;AAGT,QAAO,SAAS,iBAAiB,iDAA+C,CAAC,SAAS;;;;;AAM5F,SAAgB,eAAyB;CACvC,MAAM,MAAgB,EAAE;CAGxB,MAAM,WAAW,SAAS,iBAAiB,mBAAmB;AAC9D,MAAK,MAAM,MAAM,UAAU;EACzB,MAAM,KAAK,GAAG,aAAa,iBAAiB;AAC5C,MAAI,GAAI,KAAI,KAAK,GAAG;;CAItB,MAAM,UAAU,SAAS,iBAAiB,iDAA+C;AACzF,MAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,KAAK,OAAO,aAAa,cAAc;AAC7C,MAAI,GAAI,KAAI,KAAK,GAAG;;AAGtB,QAAO;;;;;;AAOT,SAAgB,mBAAmB,UAAwB;CAEzD,MAAM,UAAU,SAAS,cAAc,oBAAoB,SAAS,IAAI;AACxE,KAAI,SAAS;AACX,UAAQ,aAAa,iBAAiB,OAAO;AAC7C;;CAIF,MAAM,SAAS,SAAS,cACtB,gDAAgD,SAAS,IAC1D;AACD,KAAI,QAAQ;AAEV,SAAO,aAAa,wBAAwB,SAAS;AAErD,SAAO,QAAQ;;;;;;;;;;;;AAanB,SAAS,oBAAoB,YAAmB,UAAsC;CACpF,MAAM,SAAgB,EAAE;AACxB,MAAK,MAAM,QAAQ,YAAY;AAC7B,MAAI,SAAS,KAAM;AACnB,MAAI,OAAO,SAAS,UAAU;AAC5B,UAAO,KAAK,KAAK;AACjB;;AAEF,MAAI,MAAM,QAAQ,KAAK,IAAI,KAAK,WAAW,KAAK,OAAO,KAAK,OAAO,UAAU;GAC3E,MAAM,CAAC,MAAM,OAAO,YAAY;AAIhC,OAAI,SAAS,WAAW;AACtB,WAAO,KAAK,KAAK;AACjB;;GAGF,MAAM,mBAAmB,WAAW,oBAAoB,UAAU,SAAS,GAAG,EAAE;AAEhF,OAAI,KAAK,WAAW,IAAI,EAAE;IAExB,MAAM,OAAO,KAAK,MAAM,EAAE;IAC1B,MAAM,YAAY,SAAS;AAC3B,QAAI,CAAC,aAAa,OAAO,cAAc,YAAY;AACjD,aAAQ,KAAK,gCAAgC,KAAK,sBAAsB;AACxE;;AAEF,WAAO,KAAK,EAAE,WAAW,SAAS,EAAE,EAAE,GAAG,iBAAiB,CAAC;SAG3D,QAAO,KAAK,EAAE,MAAM,SAAS,EAAE,EAAE,GAAG,iBAAiB,CAAC;;;AAI5D,QAAO;;;;;;;;;;;;;;;;;;AAmBT,SAAgB,kBACd,cACA,WACA,UACM;CAEN,MAAM,WAAW,SAAS,iBAAiB,qBAAqB,aAAa,IAAI;CAGjF,MAAM,UAAU,SAAS,iBACvB,oDAAoD,aAAa,IAClE;AAGD,UAAS,SAAS,YAAY;EAC5B,MAAM,WAAW,QAAQ,aAAa,iBAAiB;AACvD,MAAI,CAAC,SAAU;AAGf,MAAI,QAAQ,aAAa,gBAAgB,CAAE;AAE3C,MAAI;GACF,MAAM,QAAQ,KAAK,MAAM,QAAQ,aAAa,oBAAoB,IAAI,KAAK;AAG3E,OAAI,UAAU;IACZ,MAAM,iBAAiB,SAAS,cAC9B,yDAAyD,SAAS,IACnE;AACD,QAAI,gBAAgB;AAClB,SAAI;AAEF,YAAM,WAAW,oBADE,KAAK,MAAM,eAAe,eAAe,KAAK,EAChB,SAAS;cACnD,GAAG;AACV,cAAQ,KAAK,oDAAoD,EAAE;;AAErE,oBAAe,QAAQ;;;AAa3B,eARqB;IACnB,MAAM;IACN;IACA,UAAU,EAAE;IACb,EAIkB,SAAS,QAAQ,WAAsB;AAC1D,WAAQ,aAAa,iBAAiB,OAAO;WACtC,OAAO;AAEd,WAAQ,MAAM,qBAAqB,SAAS,sBAAsB,MAAM;AACxE,WAAQ,aAAa,wBAAwB,OAAO;;GAEtD;AAGF,SAAQ,SAAS,WAAW;EAC1B,MAAM,WAAW,OAAO,aAAa,cAAc;AACnD,MAAI,CAAC,SAAU;AAGf,MAAI,OAAO,aAAa,uBAAuB,CAAE;AAEjD,MAAI;GACF,MAAM,QAAQ,KAAK,MAAM,OAAO,eAAe,KAAK;AAGpD,OAAI,UAAU;IACZ,MAAM,iBAAiB,SAAS,cAC9B,yDAAyD,SAAS,IACnE;AACD,QAAI,gBAAgB;AAClB,SAAI;AAEF,YAAM,WAAW,oBADE,KAAK,MAAM,eAAe,eAAe,KAAK,EAChB,SAAS;cACnD,GAAG;AACV,cAAQ,KAAK,oDAAoD,EAAE;;AAErE,oBAAe,QAAQ;;;GAK3B,MAAM,cAAc,UAAU;GAC9B,MAAM,YAAY,WAAW;GAG7B,MAAM,SAAS,SAAS,iBAAiB,SAAS,MAAM,WAAW,cAAc,KAAK;GAEtF,IAAI,YAA4B;GAChC,IAAI,UAA0B;GAC9B,IAAI;AAEJ,UAAQ,OAAO,OAAO,UAAU,CAC9B,KAAI,KAAK,cAAc,YACrB,aAAY;YACH,KAAK,cAAc,WAAW;AACvC,cAAU;AACV;;AAIJ,OAAI,aAAa,WAAW,UAAU,YAAY;IAChD,MAAM,SAAS,UAAU;IAMzB,MAAM,YAAY,SAAS,cAAc,MAAM;IAC/C,IAAI,UAAuB,UAAU;AACrC,WAAO,WAAW,YAAY,SAAS;KACrC,MAAM,OAAO,QAAQ;AACrB,eAAU,YAAY,QAAQ;AAC9B,eAAU;;AASZ,gBALqB;KACnB,MAAM;KACN;KACA,UAAU,EAAE;KACb,EACkB,WAAW,UAAU;AAGxC,WAAO,UAAU,WACf,QAAO,aAAa,UAAU,YAAY,QAAQ;AAIpD,WAAO,aAAa,wBAAwB,SAAS;AACrD,WAAO,QAAQ;;WAEV,OAAO;AAEd,WAAQ,MAAM,8BAA8B,SAAS,sBAAsB,MAAM;;GAEnF;;;;;ACz/BJ,IAAI,YAAmC;AAGvC,MAAM,+BAAe,IAAI,KAAa;;;;AAKtC,SAAgB,YAAY,UAAgC;AAC1D,aAAY;;;;;AAMd,SAAgB,cAAqC;AACnD,QAAO;;;;;AAMT,SAAgB,WAAW,MAAsB;AAC/C,KAAI,CAAC,UACH,QAAO;CAIT,MAAM,aAAa,KAAK,WAAW,IAAI,GAAG,KAAK,MAAM,EAAE,GAAG;AAC1D,QAAO,UAAU,IAAI,eAAe;;;;;AAMtC,SAAgB,aAAa,KAAqB;AAChD,KAAI,CAAC,UACH,QAAO;CAIT,MAAM,aAAa,IAAI,WAAW,IAAI,GAAG,IAAI,MAAM,EAAE,GAAG;AACxD,QAAO,UAAU,OAAO,eAAe;;;;;AAMzC,SAAgB,eAAe,MAA6B;CAC1D,MAAM,eAAe,WAAW,KAAK;AAGrC,KAAI,aAAa,IAAI,aAAa,CAChC,QAAO,QAAQ,SAAS;AAG1B,QAAO,IAAI,SAAS,SAAS,WAAW;AAGtC,MADiB,SAAS,cAAc,cAAc,aAAa,IAAI,EACzD;AACZ,gBAAa,IAAI,aAAa;AAC9B,YAAS;AACT;;EAGF,MAAM,OAAO,SAAS,cAAc,OAAO;AAC3C,OAAK,MAAM;AACX,OAAK,OAAO;AAEZ,OAAK,eAAe;AAClB,gBAAa,IAAI,aAAa;AAC9B,YAAS;;AAGX,OAAK,gBAAgB;AACnB,0BAAO,IAAI,MAAM,8BAA8B,eAAe,CAAC;;AAGjE,WAAS,KAAK,YAAY,KAAK;GAC/B;;;;;;;;;;;;;;;;;;;;;AA8CJ,SAAgB,iBAAsC;AACpD,QAAO;EACL,MAAM,EAAE,QAA0B;AAEhC,OAAI,OAAO,aAAa,YACtB,gBAAe,KAAK;AAEtB,UAAO;;EAGT,IAAI,MAAsB;AACxB,UAAO,aAAa,KAAK;;EAE5B"}
1
+ {"version":3,"file":"client-CEJQ4fit.mjs","names":[],"sources":["../../ssr/src/client/hydrate.ts","../../ssr/src/client/client-resource.ts"],"sourcesContent":["/**\n * Client-side hydration for SSR\n * This module runs in the browser and hydrates server-rendered content\n */\n\nimport type { VNode } from \"@semajsx/core\";\nimport { Fragment, h } from \"@semajsx/core\";\nimport { setProperty, render } from \"@semajsx/dom\";\nimport { isSignal } from \"@semajsx/signal\";\n\n/**\n * Island info collected from the DOM\n */\ninterface IslandInfo {\n id: string;\n props: Record<string, any>;\n /** Element with data-island-id (single element islands) */\n element?: HTMLElement;\n /** Start comment node (fragment islands) */\n startComment?: Comment;\n /** End comment node (fragment islands) */\n endComment?: Comment;\n}\n\n/**\n * Type guard for async iterators\n */\nfunction isAsyncIterator(value: unknown): value is AsyncIterableIterator<unknown> {\n if (!value || typeof value !== \"object\") return false;\n const obj = value as Record<string | symbol, unknown>;\n return (\n typeof obj[Symbol.asyncIterator] === \"function\" ||\n (typeof obj.next === \"function\" && typeof obj.return === \"function\")\n );\n}\n\n/**\n * Hydrate a server-rendered DOM tree with client-side interactivity\n * Unlike render(), this preserves existing DOM and only attaches event listeners\n *\n * @param vnode - The VNode to hydrate\n * @param container - The DOM container with server-rendered content\n * @returns The hydrated root node\n *\n * @example\n * ```tsx\n * const vnode = <Counter initial={5} />\n * const container = document.querySelector('[data-island-id=\"island-0\"]')\n * hydrate(vnode, container)\n * ```\n */\nexport function hydrate(vnode: VNode, container: Element): Node | null {\n // Standard hydration: hydrate container's first child\n const nodeToHydrate = container.firstChild;\n\n if (!nodeToHydrate) {\n console.warn(\"[Hydrate] Container is empty, falling back to render\");\n const rendered = renderNode(vnode, container);\n if (rendered) {\n container.appendChild(rendered);\n }\n return rendered;\n }\n\n // Hydrate the VNode tree onto the existing DOM\n try {\n hydrateNode(vnode, nodeToHydrate, container);\n return nodeToHydrate;\n } catch (error) {\n console.error(\"[Hydrate] Error during hydration:\", error);\n // Fall back to client-side rendering if hydration fails\n console.warn(\"[Hydrate] Falling back to client-side rendering\");\n container.innerHTML = \"\";\n return renderNode(vnode, container);\n }\n}\n\n/**\n * Hydrate a VNode onto an existing DOM node\n */\nfunction hydrateNode(vnode: VNode | any, domNode: Node, parentElement: Element): void {\n // Handle null/undefined\n if (vnode == null) {\n return;\n }\n\n // Handle signals - unwrap and subscribe\n if (isSignal(vnode)) {\n // For signal VNodes, we need to hydrate the current value\n // and set up reactivity to update when signal changes\n hydrateSignalNode(vnode, domNode, parentElement);\n return;\n }\n\n // Handle primitives (text nodes)\n if (typeof vnode === \"string\" || typeof vnode === \"number\") {\n if (domNode.nodeType === Node.TEXT_NODE) {\n // Text node already exists, verify content matches\n const expectedText = String(vnode);\n if (domNode.textContent !== expectedText) {\n console.warn(\"[Hydrate] Text mismatch, updating:\", domNode.textContent, \"->\", expectedText);\n domNode.textContent = expectedText;\n }\n }\n return;\n }\n\n // Handle arrays\n if (Array.isArray(vnode)) {\n let currentDomNode: Node | null = domNode;\n for (const child of vnode) {\n if (currentDomNode) {\n hydrateNode(child, currentDomNode, parentElement);\n currentDomNode = currentDomNode.nextSibling;\n }\n }\n return;\n }\n\n // Must be a VNode object\n if (typeof vnode !== \"object\" || !(\"type\" in vnode)) {\n return;\n }\n\n const vnodeTyped = vnode as VNode;\n\n // Handle signal nodes - special reactive nodes\n if (vnodeTyped.type === \"#signal\") {\n const signal = vnodeTyped.props?.signal;\n if (signal && isSignal(signal)) {\n hydrateSignalNode(signal, domNode, parentElement);\n }\n return;\n }\n\n // Handle fragments\n if (vnodeTyped.type === Fragment) {\n let currentDomNode: Node | null = domNode;\n for (const child of vnodeTyped.children) {\n if (currentDomNode) {\n hydrateNode(child, currentDomNode, parentElement);\n currentDomNode = currentDomNode.nextSibling;\n }\n }\n return;\n }\n\n // Handle function components - render and hydrate result\n if (typeof vnodeTyped.type === \"function\") {\n // Merge vnode.children into props.children (mirrors renderVNodeToHTML)\n const props =\n vnodeTyped.children && vnodeTyped.children.length > 0\n ? { ...vnodeTyped.props, children: vnodeTyped.children }\n : vnodeTyped.props || {};\n let result = vnodeTyped.type(props);\n\n // Handle async component\n if (result instanceof Promise) {\n result.then((resolved) => hydrateNode(resolved, domNode, parentElement));\n return;\n }\n\n // Handle async iterator (streaming component)\n if (isAsyncIterator(result)) {\n result.next().then(({ value }) => {\n hydrateNode(value, domNode, parentElement);\n });\n return;\n }\n\n hydrateNode(result, domNode, parentElement);\n return;\n }\n\n // Handle DOM elements\n if (typeof vnodeTyped.type === \"string\") {\n // Skip validation for text nodes - they can legitimately be text in the DOM\n // This happens when signal values or other dynamic content renders as text\n if (domNode.nodeType === Node.TEXT_NODE) {\n // Text node in place of element - possible mismatch, but might be intentional\n // Skip hydration for this node\n return;\n }\n\n if (domNode.nodeType !== Node.ELEMENT_NODE) {\n console.warn(\"[Hydrate] Expected element, got:\", domNode.nodeType);\n return;\n }\n\n const element = domNode as Element;\n\n // Verify tag matches\n if (element.tagName.toLowerCase() !== vnodeTyped.type.toLowerCase()) {\n console.warn(\"[Hydrate] Tag mismatch:\", element.tagName, \"vs\", vnodeTyped.type);\n return;\n }\n\n // Hydrate properties (especially event listeners and reactive props)\n hydrateProperties(element, vnodeTyped.props || {});\n\n // Hydrate children\n hydrateChildren(element, vnodeTyped.children);\n return;\n }\n}\n\n/**\n * Hydrate properties onto an element\n * This is where we attach event listeners and set up reactive properties\n */\nfunction hydrateProperties(element: Element, props: Record<string, any>): void {\n for (const [key, value] of Object.entries(props)) {\n // Skip special props\n if (key === \"children\" || key === \"key\" || key === \"ref\") {\n continue;\n }\n\n // Handle event listeners - these need to be attached\n if (key.startsWith(\"on\")) {\n const eventName = key.slice(2).toLowerCase();\n if (typeof value === \"function\") {\n element.addEventListener(eventName, value as EventListener);\n }\n continue;\n }\n\n // Handle reactive properties (signals)\n if (isSignal(value)) {\n // Set initial value\n setProperty(element, key, value.value);\n\n // Set up reactivity using subscribe\n value.subscribe((newValue: any) => {\n setProperty(element, key, newValue);\n });\n continue;\n }\n\n // For static properties, they should already be set by SSR\n // We can optionally verify them, but usually we trust SSR output\n // to avoid unnecessary DOM operations\n }\n\n // Handle refs\n if (props.ref) {\n if (typeof props.ref === \"function\") {\n props.ref(element);\n } else if (typeof props.ref === \"object\" && props.ref !== null) {\n props.ref.current = element;\n }\n }\n}\n\n/**\n * Hydrate children elements\n */\nfunction hydrateChildren(element: Element, children: any[]): void {\n let currentDomNode = element.firstChild;\n\n for (const child of children) {\n if (!currentDomNode) {\n // Mismatch: VNode has more children than DOM\n // Fall back to appending new nodes\n console.warn(\"[Hydrate] Missing DOM node for child, appending\");\n const newNode = renderNode(child, element);\n if (newNode) {\n element.appendChild(newNode);\n }\n continue;\n }\n\n hydrateNode(child, currentDomNode, element);\n currentDomNode = currentDomNode.nextSibling;\n }\n\n // If DOM has extra nodes, we could warn or remove them\n // For now, we leave them (progressive enhancement)\n}\n\n/**\n * Hydrate a signal VNode\n * Set up reactivity to replace content when signal changes\n */\nfunction hydrateSignalNode(signal: any, domNode: Node, parentElement: Element): void {\n // Get current signal value\n const currentValue = signal.value;\n\n // Handle empty/null signal values - server renders as <!--signal-empty--> comment\n if (\n currentValue == null ||\n currentValue === false ||\n (Array.isArray(currentValue) && currentValue.length === 0)\n ) {\n // Expect a comment node marker\n if (domNode.nodeType === Node.COMMENT_NODE) {\n // Comment marker is correct, nothing to validate\n } else {\n console.warn(\"[Hydrate] Expected comment marker for empty signal, got:\", domNode.nodeType);\n }\n } // For simple values (string, number), the server renders them as text nodes\n else if (typeof currentValue === \"string\" || typeof currentValue === \"number\") {\n if (domNode.nodeType === Node.TEXT_NODE) {\n const expectedText = String(currentValue);\n if (domNode.textContent !== expectedText) {\n console.warn(\"[Hydrate] Signal text mismatch:\", domNode.textContent, \"->\", expectedText);\n domNode.textContent = expectedText;\n }\n }\n } else {\n // For complex values (VNodes, etc.), do full hydration\n hydrateNode(currentValue, domNode, parentElement);\n }\n\n // Set up reactivity to handle signal changes\n // Use an anchor comment node to track position in DOM\n // This is necessary because arrays render as DocumentFragments which can't be tracked\n let anchor: Comment;\n let currentNodes: Node[] = [];\n\n if (domNode.nodeType === Node.COMMENT_NODE) {\n // Already a comment (empty signal), use as anchor\n anchor = domNode as Comment;\n } else {\n // Create anchor and insert it before current node\n anchor = document.createComment(\"signal-anchor\");\n if (domNode.parentNode) {\n domNode.parentNode.insertBefore(anchor, domNode);\n }\n currentNodes = [domNode];\n }\n\n signal.subscribe((newValue: any) => {\n const parent = anchor.parentNode;\n if (!parent) {\n return;\n }\n\n // Remove old nodes\n for (const node of currentNodes) {\n if (node.parentNode) {\n node.parentNode.removeChild(node);\n }\n }\n currentNodes = [];\n\n // Render and insert new content after anchor\n const newNode = renderNode(newValue, parentElement);\n if (newNode) {\n if (newNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {\n // Fragment: insert all children after anchor in correct order\n const fragment = newNode as DocumentFragment;\n const children = Array.from(fragment.childNodes);\n\n // Insert in order by tracking the last inserted node\n let insertAfter: Node | null = anchor;\n for (const child of children) {\n parent.insertBefore(child, insertAfter.nextSibling);\n insertAfter = child;\n currentNodes.push(child);\n }\n } else {\n // Single node: insert after anchor\n parent.insertBefore(newNode, anchor.nextSibling);\n currentNodes = [newNode];\n }\n }\n });\n}\n\n/**\n * Render a VNode to a DOM node (fallback when hydration fails)\n * This is a simplified version of render() just for hydration fallback\n */\n// oxlint-disable-next-line only-used-in-recursion\nfunction renderNode(vnode: any, parentElement: Element): Node | null {\n if (vnode == null || vnode === false || vnode === true) {\n return document.createComment(\"empty\");\n }\n\n if (isSignal(vnode)) {\n return renderNode(vnode.value, parentElement);\n }\n\n if (typeof vnode === \"string\" || typeof vnode === \"number\") {\n return document.createTextNode(String(vnode));\n }\n\n if (Array.isArray(vnode)) {\n // Empty array should render as a comment marker\n if (vnode.length === 0) {\n return document.createComment(\"empty\");\n }\n\n const fragment = document.createDocumentFragment();\n for (const child of vnode) {\n const node = renderNode(child, parentElement);\n if (node) {\n fragment.appendChild(node);\n }\n }\n return fragment;\n }\n\n if (typeof vnode === \"object\" && \"type\" in vnode) {\n const vnodeTyped = vnode as VNode;\n\n // Handle special VNode types\n if (vnodeTyped.type === \"#text\") {\n return document.createTextNode(String(vnodeTyped.props?.nodeValue || \"\"));\n }\n\n if (vnodeTyped.type === \"#signal\") {\n const signal = vnodeTyped.props?.signal;\n if (signal && isSignal(signal)) {\n return renderNode(signal.value, parentElement);\n }\n return document.createTextNode(\"\");\n }\n\n if (vnodeTyped.type === Fragment) {\n const fragment = document.createDocumentFragment();\n for (const child of vnodeTyped.children) {\n const node = renderNode(child, parentElement);\n if (node) {\n fragment.appendChild(node);\n }\n }\n return fragment;\n }\n\n if (typeof vnodeTyped.type === \"function\") {\n const result = vnodeTyped.type(vnodeTyped.props || {});\n return renderNode(result, parentElement);\n }\n\n if (typeof vnodeTyped.type === \"string\") {\n const element = document.createElement(vnodeTyped.type);\n\n // Set properties\n const props = vnodeTyped.props || {};\n for (const [key, value] of Object.entries(props)) {\n if (key === \"children\" || key === \"key\") continue;\n setProperty(element, key, value);\n }\n\n // Render children\n for (const child of vnodeTyped.children) {\n const childNode = renderNode(child, element);\n if (childNode) {\n element.appendChild(childNode);\n }\n }\n\n return element;\n }\n }\n\n return null;\n}\n\n/**\n * Hydrate an island by ID\n * Handles both single-element islands (with data-island-id) and fragment islands (with comment markers)\n *\n * @param islandId - The island ID to hydrate\n * @param Component - The component function to render\n * @param markHydrated - Callback to mark the island as hydrated\n *\n * @example\n * ```tsx\n * import { hydrateIsland, markIslandHydrated } from '@semajsx/ssr/client';\n * import Counter from './Counter';\n *\n * hydrateIsland('counter-0', Counter, markIslandHydrated);\n * ```\n */\nexport function hydrateIsland(\n islandId: string,\n Component: Function,\n markHydrated: (id: string) => void,\n): void {\n // Try single-element island first (has data-island-id on root element)\n const element = document.querySelector(`[data-island-id=\"${islandId}\"]`);\n\n if (element) {\n // Single-element island: replace the element with rendered content\n const props = JSON.parse(element.getAttribute(\"data-island-props\") || \"{}\");\n const parent = element.parentNode;\n if (!parent) return;\n\n // Create VNode for the component\n const vnode: VNode = {\n type: Component as VNode[\"type\"],\n props,\n children: [],\n };\n\n // Render into temp container\n const temp = document.createElement(\"div\");\n render(vnode, temp);\n\n // Replace original element with rendered content\n const children = Array.from(temp.childNodes);\n for (const child of children) {\n parent.insertBefore(child, element);\n }\n parent.removeChild(element);\n\n markHydrated(islandId);\n return;\n }\n\n // Fragment island: find by comment marker\n const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT);\n let startComment: Comment | null = null;\n let comment: Comment | null;\n while ((comment = walker.nextNode() as Comment | null)) {\n if (comment.textContent === `island:${islandId}`) {\n startComment = comment;\n break;\n }\n }\n\n if (startComment) {\n // Get props from script tag\n const script = document.querySelector(`script[data-island=\"${islandId}\"]`);\n const props = script ? JSON.parse(script.textContent || \"{}\") : {};\n\n // Find end comment and collect nodes between markers\n const nodesToRemove: Node[] = [];\n let sibling = startComment.nextSibling;\n let endComment: Comment | null = null;\n while (sibling) {\n if (sibling.nodeType === Node.COMMENT_NODE && sibling.textContent === `/island:${islandId}`) {\n endComment = sibling as Comment;\n break;\n }\n nodesToRemove.push(sibling);\n sibling = sibling.nextSibling;\n }\n\n // Remove old nodes\n for (const node of nodesToRemove) {\n node.parentNode?.removeChild(node);\n }\n\n // Render new content with full reactivity\n const vnode: VNode = {\n type: Component as VNode[\"type\"],\n props,\n children: [],\n };\n const parent = startComment.parentNode;\n if (parent) {\n // Create temp container and use full render for reactivity\n const temp = document.createElement(\"div\");\n render(vnode, temp);\n\n // Move rendered nodes after start comment\n const children = Array.from(temp.childNodes);\n for (const child of children) {\n parent.insertBefore(child, endComment);\n }\n }\n\n // Remove markers and script\n startComment.parentNode?.removeChild(startComment);\n if (endComment) endComment.parentNode?.removeChild(endComment);\n if (script) script.parentNode?.removeChild(script);\n\n markHydrated(islandId);\n }\n}\n\n/**\n * Find all islands on the page (both element and fragment types)\n */\nfunction findAllIslands(): IslandInfo[] {\n const islands: IslandInfo[] = [];\n\n // Find element-based islands (single root element)\n const elements = document.querySelectorAll(\"[data-island-id]\");\n for (const el of elements) {\n const id = el.getAttribute(\"data-island-id\");\n const propsStr = el.getAttribute(\"data-island-props\");\n if (id) {\n islands.push({\n id,\n props: propsStr ? JSON.parse(propsStr) : {},\n element: el as HTMLElement,\n });\n }\n }\n\n // Find fragment-based islands (comment markers + script)\n const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT);\n\n let comment: Comment | null;\n while ((comment = walker.nextNode() as Comment | null)) {\n const match = comment.textContent?.match(/^island:(.+)$/);\n if (match && match[1]) {\n const id = match[1];\n // Find end comment (matches /island:${id})\n let endComment: Comment | null = null;\n let sibling = comment.nextSibling;\n while (sibling) {\n if (sibling.nodeType === Node.COMMENT_NODE && sibling.textContent === `/island:${id}`) {\n endComment = sibling as Comment;\n break;\n }\n sibling = sibling.nextSibling;\n }\n\n // Find props from script tag\n const script = document.querySelector(`script[type=\"application/json\"][data-island=\"${id}\"]`);\n const props = script ? JSON.parse(script.textContent || \"{}\") : {};\n\n islands.push({\n id,\n props,\n startComment: comment,\n endComment: endComment || undefined,\n });\n }\n }\n\n return islands;\n}\n\n/**\n * Hydrate all islands on the page\n * This function is typically called once after the page loads\n *\n * @example\n * ```tsx\n * // In your client entry point\n * import { hydrateIslands } from '@semajsx/ssr/client'\n *\n * // Wait for DOM to be ready\n * if (document.readyState === 'loading') {\n * document.addEventListener('DOMContentLoaded', hydrateIslands)\n * } else {\n * hydrateIslands()\n * }\n * ```\n */\nexport async function hydrateIslands(): Promise<void> {\n const islands = findAllIslands();\n\n if (islands.length === 0) {\n return;\n }\n\n console.log(`[SemaJSX] Found ${islands.length} islands to hydrate`);\n\n // Hydrate islands in parallel for better performance\n const hydrations = islands.map((island) => waitForIslandScript(island));\n\n await Promise.all(hydrations);\n\n console.log(`[SemaJSX] All islands hydrated`);\n}\n\n/**\n * Wait for an island's script to load and hydrate it\n * The actual hydration is performed by the island's entry point script\n * This function just waits for it to complete\n */\nasync function waitForIslandScript(island: IslandInfo): Promise<void> {\n const { id: islandId, element, startComment } = island;\n\n // Check if island is already hydrated\n if (element?.hasAttribute(\"data-hydrated\")) {\n return;\n }\n if (startComment?.parentElement?.querySelector(`[data-island-hydrated=\"${islandId}\"]`)) {\n return;\n }\n\n // Wait for hydration to complete (set by island entry point)\n return new Promise((resolve) => {\n // Check every 50ms for up to 10 seconds\n const maxAttempts = 200;\n let attempts = 0;\n\n const checkInterval = setInterval(() => {\n const isHydrated = element\n ? element.hasAttribute(\"data-hydrated\")\n : document.querySelector(`[data-island-hydrated=\"${islandId}\"]`) !== null;\n\n if (isHydrated) {\n clearInterval(checkInterval);\n resolve();\n } else if (++attempts >= maxAttempts) {\n clearInterval(checkInterval);\n console.warn(`[SemaJSX] Island ${islandId} hydration timeout`);\n resolve();\n }\n }, 50);\n });\n}\n\n/**\n * Get island info by ID\n */\nexport function getIslandInfo(islandId: string): IslandInfo | null {\n // Try element-based first\n const element = document.querySelector(`[data-island-id=\"${islandId}\"]`) as HTMLElement | null;\n\n if (element) {\n const propsStr = element.getAttribute(\"data-island-props\");\n return {\n id: islandId,\n props: propsStr ? JSON.parse(propsStr) : {},\n element,\n };\n }\n\n // Try fragment-based\n const script = document.querySelector(\n `script[type=\"application/json\"][data-island=\"${islandId}\"]`,\n );\n\n if (script) {\n const props = JSON.parse(script.textContent || \"{}\");\n // Find start comment\n const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT);\n let comment: Comment | null;\n while ((comment = walker.nextNode() as Comment | null)) {\n if (comment.textContent === `island:${islandId}`) {\n return {\n id: islandId,\n props,\n startComment: comment,\n };\n }\n }\n }\n\n return null;\n}\n\n/**\n * Manual hydration for a specific island\n * Useful for lazy-loading islands on interaction\n *\n * @param islandId - The island ID to hydrate\n *\n * @example\n * ```tsx\n * // Lazy load an island on click\n * button.addEventListener('click', () => {\n * hydrateIslandById('island-0')\n * })\n * ```\n */\nexport async function hydrateIslandById(islandId: string): Promise<void> {\n const island = getIslandInfo(islandId);\n\n if (!island) {\n console.error(`[SemaJSX] Island not found: ${islandId}`);\n return;\n }\n\n await waitForIslandScript(island);\n}\n\n/**\n * Check if islands are present on the page\n */\nexport function hasIslands(): boolean {\n // Check for element-based islands\n if (document.querySelectorAll(\"[data-island-id]\").length > 0) {\n return true;\n }\n // Check for fragment-based islands\n return document.querySelectorAll('script[type=\"application/json\"][data-island]').length > 0;\n}\n\n/**\n * Get all island IDs on the page\n */\nexport function getIslandIds(): string[] {\n const ids: string[] = [];\n\n // Element-based islands\n const elements = document.querySelectorAll(\"[data-island-id]\");\n for (const el of elements) {\n const id = el.getAttribute(\"data-island-id\");\n if (id) ids.push(id);\n }\n\n // Fragment-based islands\n const scripts = document.querySelectorAll('script[type=\"application/json\"][data-island]');\n for (const script of scripts) {\n const id = script.getAttribute(\"data-island\");\n if (id) ids.push(id);\n }\n\n return ids;\n}\n\n/**\n * Mark an island as hydrated\n * This should be called by the island entry point after hydration completes\n */\nexport function markIslandHydrated(islandId: string): void {\n // Try element-based first\n const element = document.querySelector(`[data-island-id=\"${islandId}\"]`);\n if (element) {\n element.setAttribute(\"data-hydrated\", \"true\");\n return;\n }\n\n // For fragment-based, remove the script tag (no longer needed)\n const script = document.querySelector(\n `script[type=\"application/json\"][data-island=\"${islandId}\"]`,\n );\n if (script) {\n // Mark as hydrated before removal (for any watchers)\n script.setAttribute(\"data-island-hydrated\", islandId);\n // Remove script - props already parsed, no longer needed\n script.remove();\n }\n}\n\n/**\n * Reconstruct VNode children from serialized JSON data.\n *\n * Uses the island module's exports as a registry to resolve component names\n * (prefixed with \"$\") back to their actual functions.\n *\n * @param serialized - Serialized children array from SSR\n * @param registry - Module exports mapping component names to functions\n */\nfunction reconstructChildren(serialized: any[], registry: Record<string, any>): any[] {\n const result: any[] = [];\n for (const node of serialized) {\n if (node === null) continue;\n if (typeof node === \"string\") {\n result.push(node);\n continue;\n }\n if (Array.isArray(node) && node.length === 3 && typeof node[0] === \"string\") {\n const [type, props, children] = node;\n\n // Nested island placeholder — these hydrate independently,\n // push null to preserve children index positions.\n if (type === \"$island\") {\n result.push(null);\n continue;\n }\n\n const resolvedChildren = children ? reconstructChildren(children, registry) : [];\n\n if (type.startsWith(\"$\")) {\n // Component reference — look up in registry\n const name = type.slice(1);\n const component = registry[name];\n if (!component || typeof component !== \"function\") {\n console.warn(`[Hydrate] Unknown component \"${name}\" in island children`);\n continue;\n }\n result.push(h(component, props || {}, ...resolvedChildren));\n } else {\n // HTML element\n result.push(h(type, props || {}, ...resolvedChildren));\n }\n }\n }\n return result;\n}\n\n/**\n * Hydrate all islands with a given component source\n * Finds all elements with data-island-src and hydrates them\n *\n * @param componentSrc - The component source key (e.g., \"components/Counter\")\n * @param Component - The component function to render\n * @param registry - Optional module exports for reconstructing island children\n *\n * @example\n * ```tsx\n * import { hydrateAllIslands } from '@semajsx/ssr/client';\n * import * as CounterModule from './Counter';\n *\n * hydrateAllIslands('components/Counter', CounterModule.Counter, CounterModule);\n * ```\n */\nexport function hydrateAllIslands(\n componentSrc: string,\n Component: Function,\n registry?: Record<string, any>,\n): void {\n // Find all elements with this component source\n const elements = document.querySelectorAll(`[data-island-src=\"${componentSrc}\"]`);\n\n // Also find fragment-based islands (script tags with data-island-src)\n const scripts = document.querySelectorAll(\n `script[type=\"application/json\"][data-island-src=\"${componentSrc}\"]`,\n );\n\n // Hydrate each element-based island\n elements.forEach((element) => {\n const islandId = element.getAttribute(\"data-island-id\");\n if (!islandId) return;\n\n // Skip if already hydrated\n if (element.hasAttribute(\"data-hydrated\")) return;\n\n try {\n const props = JSON.parse(element.getAttribute(\"data-island-props\") || \"{}\");\n\n // Reconstruct children from serialized VNode data if available\n if (registry) {\n const childrenScript = document.querySelector(\n `script[type=\"application/json\"][data-island-children=\"${islandId}\"]`,\n );\n if (childrenScript) {\n try {\n const serialized = JSON.parse(childrenScript.textContent || \"[]\");\n props.children = reconstructChildren(serialized, registry);\n } catch (e) {\n console.warn(\"[Hydrate] Failed to reconstruct island children:\", e);\n }\n childrenScript.remove();\n }\n }\n\n // Create VNode for the component\n const vnode: VNode = {\n type: Component as VNode[\"type\"],\n props,\n children: [],\n };\n\n // Hydrate in-place: attach event listeners, signal subscriptions,\n // and refs while preserving existing DOM and SSR-rendered children.\n hydrateNode(vnode, element, element.parentNode as Element);\n element.setAttribute(\"data-hydrated\", \"true\");\n } catch (error) {\n // SSR content is preserved — the island stays as static HTML\n console.error(`[Hydrate] Island \"${islandId}\" hydration failed:`, error);\n element.setAttribute(\"data-hydration-error\", \"true\");\n }\n });\n\n // Hydrate each fragment-based island\n scripts.forEach((script) => {\n const islandId = script.getAttribute(\"data-island\");\n if (!islandId) return;\n\n // Skip if already hydrated\n if (script.hasAttribute(\"data-island-hydrated\")) return;\n\n try {\n const props = JSON.parse(script.textContent || \"{}\");\n\n // Reconstruct children from serialized VNode data if available\n if (registry) {\n const childrenScript = document.querySelector(\n `script[type=\"application/json\"][data-island-children=\"${islandId}\"]`,\n );\n if (childrenScript) {\n try {\n const serialized = JSON.parse(childrenScript.textContent || \"[]\");\n props.children = reconstructChildren(serialized, registry);\n } catch (e) {\n console.warn(\"[Hydrate] Failed to reconstruct island children:\", e);\n }\n childrenScript.remove();\n }\n }\n\n // Find the comment markers\n const startMarker = `island:${islandId}`;\n const endMarker = `/island:${islandId}`;\n\n // Find and process the fragment\n const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT, null);\n\n let startNode: Comment | null = null;\n let endNode: Comment | null = null;\n let node: Node | null;\n\n while ((node = walker.nextNode())) {\n if (node.nodeValue === startMarker) {\n startNode = node as Comment;\n } else if (node.nodeValue === endMarker) {\n endNode = node as Comment;\n break;\n }\n }\n\n if (startNode && endNode && startNode.parentNode) {\n const parent = startNode.parentNode;\n\n // Non-destructive hydration: move existing nodes into a temp\n // container, hydrate against the existing DOM, then move back.\n // This preserves SSR content and only attaches event listeners\n // and signal subscriptions — matching element-based island behavior.\n const container = document.createElement(\"div\");\n let current: Node | null = startNode.nextSibling;\n while (current && current !== endNode) {\n const next = current.nextSibling;\n container.appendChild(current); // moves node, doesn't clone\n current = next;\n }\n\n // Create VNode and hydrate against existing DOM\n const vnode: VNode = {\n type: Component as VNode[\"type\"],\n props,\n children: [],\n };\n hydrateNode(vnode, container, container);\n\n // Move hydrated nodes back between markers\n while (container.firstChild) {\n parent.insertBefore(container.firstChild, endNode);\n }\n\n // Mark as hydrated\n script.setAttribute(\"data-island-hydrated\", islandId);\n script.remove();\n }\n } catch (error) {\n // SSR content is preserved — the fragment stays as static HTML\n console.error(`[Hydrate] Fragment island \"${islandId}\" hydration failed:`, error);\n }\n });\n}\n\n// Export types\nexport type { IslandInfo };\n","/**\n * Client-side resource utilities for dynamic CSS and asset loading\n */\n\n/**\n * Client manifest structure\n */\nexport interface ClientManifest {\n css: Record<string, string>;\n assets: Record<string, string>;\n}\n\n// Global manifest storage\nlet _manifest: ClientManifest | null = null;\n\n// Track loaded stylesheets to avoid duplicates\nconst loadedStyles = new Set<string>();\n\n/**\n * Set the client manifest (called during initialization)\n */\nexport function setManifest(manifest: ClientManifest): void {\n _manifest = manifest;\n}\n\n/**\n * Get the current manifest\n */\nexport function getManifest(): ClientManifest | null {\n return _manifest;\n}\n\n/**\n * Resolve a CSS path using the manifest\n */\nexport function resolveCSS(href: string): string {\n if (!_manifest) {\n return href;\n }\n\n // Try to find in manifest (remove leading slash for lookup)\n const lookupPath = href.startsWith(\"/\") ? href.slice(1) : href;\n return _manifest.css[lookupPath] || href;\n}\n\n/**\n * Resolve an asset path using the manifest\n */\nexport function resolveAsset(src: string): string {\n if (!_manifest) {\n return src;\n }\n\n // Try to find in manifest (remove leading slash for lookup)\n const lookupPath = src.startsWith(\"/\") ? src.slice(1) : src;\n return _manifest.assets[lookupPath] || src;\n}\n\n/**\n * Dynamically load a stylesheet\n */\nexport function loadStylesheet(href: string): Promise<void> {\n const resolvedHref = resolveCSS(href);\n\n // Skip if already loaded\n if (loadedStyles.has(resolvedHref)) {\n return Promise.resolve();\n }\n\n return new Promise((resolve, reject) => {\n // Check if already in document\n const existing = document.querySelector(`link[href=\"${resolvedHref}\"]`);\n if (existing) {\n loadedStyles.add(resolvedHref);\n resolve();\n return;\n }\n\n const link = document.createElement(\"link\");\n link.rel = \"stylesheet\";\n link.href = resolvedHref;\n\n link.onload = () => {\n loadedStyles.add(resolvedHref);\n resolve();\n };\n\n link.onerror = () => {\n reject(new Error(`Failed to load stylesheet: ${resolvedHref}`));\n };\n\n document.head.appendChild(link);\n });\n}\n\n/**\n * Style component props\n */\nexport interface StyleProps {\n href: string;\n}\n\n/**\n * Asset component props\n */\nexport interface AssetProps {\n src: string;\n}\n\n/**\n * Client-side resource tools\n */\nexport interface ClientResourceTools {\n /** Load and inject a CSS stylesheet */\n Style: (props: StyleProps) => null;\n /** Resolve asset URL from manifest */\n url: (path: string) => string;\n}\n\n/**\n * Create client-side resource tools\n *\n * @example\n * ```tsx\n * import { clientResource } from '@semajsx/ssr/client';\n *\n * const { Style, url } = clientResource();\n *\n * export default function Counter() {\n * return (\n * <>\n * <Style href=\"./counter.css\" />\n * <img src={url('./icon.png')} />\n * </>\n * );\n * }\n * ```\n */\nexport function clientResource(): ClientResourceTools {\n return {\n Style({ href }: StyleProps): null {\n // Load stylesheet on mount\n if (typeof document !== \"undefined\") {\n loadStylesheet(href);\n }\n return null;\n },\n\n url(path: string): string {\n return resolveAsset(path);\n },\n };\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import "./src-Cv4rRVzv.mjs";
1
+ import "./src--YS4EvMz.mjs";
2
2
  import "./jsx-runtime-BFs1c0xz.mjs";
3
- import { n as renderDocument, t as DefaultDocument } from "./document-XKyAs62C.mjs";
3
+ import { n as renderDocument, t as DefaultDocument } from "./document-Cfdhi7vG.mjs";
4
4
 
5
5
  export { renderDocument };
@@ -1,4 +1,4 @@
1
- import { a as jsxs, i as jsx, v as Fragment } from "./src-Cv4rRVzv.mjs";
1
+ import { a as jsxs, i as jsx, v as Fragment } from "./src--YS4EvMz.mjs";
2
2
 
3
3
  //#region ../ssr/src/document.tsx
4
4
  /** @jsxImportSource @semajsx/dom */
@@ -128,4 +128,4 @@ function escapeHTML(str) {
128
128
 
129
129
  //#endregion
130
130
  export { renderDocument as n, DefaultDocument as t };
131
- //# sourceMappingURL=document-XKyAs62C.mjs.map
131
+ //# sourceMappingURL=document-Cfdhi7vG.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"document-XKyAs62C.mjs","names":[],"sources":["../../ssr/src/document.tsx"],"sourcesContent":["/** @jsxImportSource @semajsx/dom */\n\nimport { Fragment } from \"@semajsx/core\";\nimport type { DocumentTemplate } from \"./shared/types\";\n\n/**\n * Default HTML document template\n *\n * This is a simple, production-ready HTML5 document template.\n * You can customize it by providing your own document template to the router.\n *\n * @example\n * ```tsx\n * const router = createViteRouter(routes, {\n * document: DefaultDocument,\n * title: \"My App\" // Required if using DefaultDocument\n * });\n * ```\n */\nexport const DefaultDocument: DocumentTemplate = ({ children, scripts, css, title }) => (\n <html lang=\"en\">\n <head>\n <meta charSet=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n {title && <title>{title}</title>}\n {css.map((href) => (\n <link key={href} rel=\"stylesheet\" href={href} />\n ))}\n </head>\n <body>\n {children}\n {scripts}\n </body>\n </html>\n);\n\n/**\n * Render a document VNode to a complete HTML string\n * Prepends <!DOCTYPE html> to the rendered output\n *\n * @param documentVNode - The document VNode (should be <html> element)\n * @returns Complete HTML string with doctype\n */\nexport function renderDocument(documentVNode: any): string {\n // Import renderToString from render.ts\n // We can't import it directly to avoid circular dependencies\n // So we use a simple HTML renderer for the document shell\n\n const html = renderDocumentVNode(documentVNode);\n return `<!DOCTYPE html>\\n${html}`;\n}\n\n/**\n * Simple HTML renderer for document structure\n * This handles the outer HTML/head/body structure without islands\n */\nfunction renderDocumentVNode(vnode: any): string {\n if (vnode == null) {\n return \"\";\n }\n\n if (typeof vnode === \"string\" || typeof vnode === \"number\") {\n const str = String(vnode);\n // Check if it's already rendered HTML (starts with < or contains HTML tags)\n if (str.startsWith(\"<\") || str.includes(\"</\")) {\n return str;\n }\n return escapeHTML(str);\n }\n\n if (typeof vnode === \"boolean\") {\n return \"\";\n }\n\n if (Array.isArray(vnode)) {\n return vnode.map((child) => renderDocumentVNode(child)).join(\"\");\n }\n\n if (typeof vnode !== \"object\" || !(\"type\" in vnode)) {\n return \"\";\n }\n\n const { type, props, children } = vnode;\n\n // Handle dangerouslySetInnerHTML (for raw HTML injection)\n if (props?.dangerouslySetInnerHTML?.__html) {\n return props.dangerouslySetInnerHTML.__html;\n }\n\n // Handle Fragment (Symbol type)\n if (type === Fragment) {\n return (children || []).map((child: any) => renderDocumentVNode(child)).join(\"\");\n }\n\n // Handle special VNode types\n if (typeof type === \"string\") {\n // Handle internal node types like #text, #signal\n if (type.startsWith(\"#\")) {\n if (type === \"#text\") {\n const value = props?.nodeValue ?? \"\";\n // Check if it's already rendered HTML\n if (typeof value === \"string\" && (value.startsWith(\"<\") || value.includes(\"</\"))) {\n return value;\n }\n return escapeHTML(String(value));\n }\n // For #signal and other special types, render children\n return (children || []).map((child: any) => renderDocumentVNode(child)).join(\"\");\n }\n return renderElement(type, props, children);\n }\n\n if (typeof type === \"function\") {\n const result = type(props || {});\n return renderDocumentVNode(result);\n }\n\n return \"\";\n}\n\n/**\n * Render an HTML element\n */\nfunction renderElement(tag: string, props: any, children: any[]): string {\n const attrs = renderAttributes(props || {});\n\n // Self-closing tags\n const selfClosing = [\"meta\", \"link\", \"br\", \"hr\", \"img\", \"input\"];\n if (selfClosing.includes(tag)) {\n return `<${tag}${attrs} />`;\n }\n\n // Raw text elements: content must NOT be HTML-escaped\n const isRawText = tag === \"style\" || tag === \"script\";\n const childrenHTML = isRawText\n ? (children || []).map((child) => extractRawText(child)).join(\"\")\n : (children || []).map((child) => renderDocumentVNode(child)).join(\"\");\n\n return `<${tag}${attrs}>${childrenHTML}</${tag}>`;\n}\n\n/**\n * Extract raw text from a VNode without HTML escaping.\n * Used for <style> and <script> content.\n */\nfunction extractRawText(vnode: any): string {\n if (vnode == null || typeof vnode === \"boolean\") return \"\";\n if (typeof vnode === \"string\" || typeof vnode === \"number\") return String(vnode);\n if (Array.isArray(vnode)) return vnode.map(extractRawText).join(\"\");\n if (typeof vnode === \"object\" && \"type\" in vnode) {\n if (vnode.type === \"#text\") return String(vnode.props?.nodeValue ?? \"\");\n if (vnode.type === \"#signal\") return (vnode.children || []).map(extractRawText).join(\"\");\n // Nested elements inside style/script (rare) - render normally\n return renderDocumentVNode(vnode);\n }\n return \"\";\n}\n\n/**\n * Render attributes\n */\nfunction renderAttributes(props: Record<string, any>): string {\n const attrs: string[] = [];\n\n for (const [key, value] of Object.entries(props)) {\n if (key === \"children\" || key === \"key\" || key === \"ref\" || key === \"dangerouslySetInnerHTML\") {\n continue;\n }\n\n if (value == null || value === false) {\n continue;\n }\n\n // Boolean attributes\n if (value === true) {\n attrs.push(key);\n continue;\n }\n\n // Map React/JSX attribute names to HTML\n const attrName =\n key === \"className\"\n ? \"class\"\n : key === \"htmlFor\"\n ? \"for\"\n : key === \"charSet\"\n ? \"charset\"\n : key === \"crossOrigin\"\n ? \"crossorigin\"\n : key === \"httpEquiv\"\n ? \"http-equiv\"\n : key;\n\n attrs.push(`${attrName}=\"${escapeHTML(String(value))}\"`);\n }\n\n return attrs.length > 0 ? \" \" + attrs.join(\" \") : \"\";\n}\n\n/**\n * Escape HTML special characters\n */\nfunction escapeHTML(str: string): string {\n return str\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"document-Cfdhi7vG.mjs","names":[],"sources":["../../ssr/src/document.tsx"],"sourcesContent":["/** @jsxImportSource @semajsx/dom */\n\nimport { Fragment } from \"@semajsx/core\";\nimport type { DocumentTemplate } from \"./shared/types\";\n\n/**\n * Default HTML document template\n *\n * This is a simple, production-ready HTML5 document template.\n * You can customize it by providing your own document template to the router.\n *\n * @example\n * ```tsx\n * const router = createViteRouter(routes, {\n * document: DefaultDocument,\n * title: \"My App\" // Required if using DefaultDocument\n * });\n * ```\n */\nexport const DefaultDocument: DocumentTemplate = ({ children, scripts, css, title }) => (\n <html lang=\"en\">\n <head>\n <meta charSet=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n {title && <title>{title}</title>}\n {css.map((href) => (\n <link key={href} rel=\"stylesheet\" href={href} />\n ))}\n </head>\n <body>\n {children}\n {scripts}\n </body>\n </html>\n);\n\n/**\n * Render a document VNode to a complete HTML string\n * Prepends <!DOCTYPE html> to the rendered output\n *\n * @param documentVNode - The document VNode (should be <html> element)\n * @returns Complete HTML string with doctype\n */\nexport function renderDocument(documentVNode: any): string {\n // Import renderToString from render.ts\n // We can't import it directly to avoid circular dependencies\n // So we use a simple HTML renderer for the document shell\n\n const html = renderDocumentVNode(documentVNode);\n return `<!DOCTYPE html>\\n${html}`;\n}\n\n/**\n * Simple HTML renderer for document structure\n * This handles the outer HTML/head/body structure without islands\n */\nfunction renderDocumentVNode(vnode: any): string {\n if (vnode == null) {\n return \"\";\n }\n\n if (typeof vnode === \"string\" || typeof vnode === \"number\") {\n const str = String(vnode);\n // Check if it's already rendered HTML (starts with < or contains HTML tags)\n if (str.startsWith(\"<\") || str.includes(\"</\")) {\n return str;\n }\n return escapeHTML(str);\n }\n\n if (typeof vnode === \"boolean\") {\n return \"\";\n }\n\n if (Array.isArray(vnode)) {\n return vnode.map((child) => renderDocumentVNode(child)).join(\"\");\n }\n\n if (typeof vnode !== \"object\" || !(\"type\" in vnode)) {\n return \"\";\n }\n\n const { type, props, children } = vnode;\n\n // Handle dangerouslySetInnerHTML (for raw HTML injection)\n if (props?.dangerouslySetInnerHTML?.__html) {\n return props.dangerouslySetInnerHTML.__html;\n }\n\n // Handle Fragment (Symbol type)\n if (type === Fragment) {\n return (children || []).map((child: any) => renderDocumentVNode(child)).join(\"\");\n }\n\n // Handle special VNode types\n if (typeof type === \"string\") {\n // Handle internal node types like #text, #signal\n if (type.startsWith(\"#\")) {\n if (type === \"#text\") {\n const value = props?.nodeValue ?? \"\";\n // Check if it's already rendered HTML\n if (typeof value === \"string\" && (value.startsWith(\"<\") || value.includes(\"</\"))) {\n return value;\n }\n return escapeHTML(String(value));\n }\n // For #signal and other special types, render children\n return (children || []).map((child: any) => renderDocumentVNode(child)).join(\"\");\n }\n return renderElement(type, props, children);\n }\n\n if (typeof type === \"function\") {\n const result = type(props || {});\n return renderDocumentVNode(result);\n }\n\n return \"\";\n}\n\n/**\n * Render an HTML element\n */\nfunction renderElement(tag: string, props: any, children: any[]): string {\n const attrs = renderAttributes(props || {});\n\n // Self-closing tags\n const selfClosing = [\"meta\", \"link\", \"br\", \"hr\", \"img\", \"input\"];\n if (selfClosing.includes(tag)) {\n return `<${tag}${attrs} />`;\n }\n\n // Raw text elements: content must NOT be HTML-escaped\n const isRawText = tag === \"style\" || tag === \"script\";\n const childrenHTML = isRawText\n ? (children || []).map((child) => extractRawText(child)).join(\"\")\n : (children || []).map((child) => renderDocumentVNode(child)).join(\"\");\n\n return `<${tag}${attrs}>${childrenHTML}</${tag}>`;\n}\n\n/**\n * Extract raw text from a VNode without HTML escaping.\n * Used for <style> and <script> content.\n */\nfunction extractRawText(vnode: any): string {\n if (vnode == null || typeof vnode === \"boolean\") return \"\";\n if (typeof vnode === \"string\" || typeof vnode === \"number\") return String(vnode);\n if (Array.isArray(vnode)) return vnode.map(extractRawText).join(\"\");\n if (typeof vnode === \"object\" && \"type\" in vnode) {\n if (vnode.type === \"#text\") return String(vnode.props?.nodeValue ?? \"\");\n if (vnode.type === \"#signal\") return (vnode.children || []).map(extractRawText).join(\"\");\n // Nested elements inside style/script (rare) - render normally\n return renderDocumentVNode(vnode);\n }\n return \"\";\n}\n\n/**\n * Render attributes\n */\nfunction renderAttributes(props: Record<string, any>): string {\n const attrs: string[] = [];\n\n for (const [key, value] of Object.entries(props)) {\n if (key === \"children\" || key === \"key\" || key === \"ref\" || key === \"dangerouslySetInnerHTML\") {\n continue;\n }\n\n if (value == null || value === false) {\n continue;\n }\n\n // Boolean attributes\n if (value === true) {\n attrs.push(key);\n continue;\n }\n\n // Map React/JSX attribute names to HTML\n const attrName =\n key === \"className\"\n ? \"class\"\n : key === \"htmlFor\"\n ? \"for\"\n : key === \"charSet\"\n ? \"charset\"\n : key === \"crossOrigin\"\n ? \"crossorigin\"\n : key === \"httpEquiv\"\n ? \"http-equiv\"\n : key;\n\n attrs.push(`${attrName}=\"${escapeHTML(String(value))}\"`);\n }\n\n return attrs.length > 0 ? \" \" + attrs.join(\" \") : \"\";\n}\n\n/**\n * Escape HTML special characters\n */\nfunction escapeHTML(str: string): string {\n return str\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAmBA,MAAa,mBAAqC,EAAE,UAAU,SAAS,KAAK,YAC1E,qBAAC;CAAK,MAAK;YACT,qBAAC;EACC,oBAAC,UAAK,SAAQ,UAAU;EACxB,oBAAC;GAAK,MAAK;GAAW,SAAQ;IAA0C;EACvE,SAAS,oBAAC,qBAAO,QAAc;EAC/B,IAAI,KAAK,SACR,oBAAC;GAAgB,KAAI;GAAmB;KAA7B,KAAqC,CAChD;KACG,EACP,qBAAC,qBACE,UACA,WACI;EACF;;;;;;;;AAUT,SAAgB,eAAe,eAA4B;AAMzD,QAAO,oBADM,oBAAoB,cAAc;;;;;;AAQjD,SAAS,oBAAoB,OAAoB;AAC/C,KAAI,SAAS,KACX,QAAO;AAGT,KAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAAU;EAC1D,MAAM,MAAM,OAAO,MAAM;AAEzB,MAAI,IAAI,WAAW,IAAI,IAAI,IAAI,SAAS,KAAK,CAC3C,QAAO;AAET,SAAO,WAAW,IAAI;;AAGxB,KAAI,OAAO,UAAU,UACnB,QAAO;AAGT,KAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,MAAM,KAAK,UAAU,oBAAoB,MAAM,CAAC,CAAC,KAAK,GAAG;AAGlE,KAAI,OAAO,UAAU,YAAY,EAAE,UAAU,OAC3C,QAAO;CAGT,MAAM,EAAE,MAAM,OAAO,aAAa;AAGlC,KAAI,OAAO,yBAAyB,OAClC,QAAO,MAAM,wBAAwB;AAIvC,KAAI,SAAS,SACX,SAAQ,YAAY,EAAE,EAAE,KAAK,UAAe,oBAAoB,MAAM,CAAC,CAAC,KAAK,GAAG;AAIlF,KAAI,OAAO,SAAS,UAAU;AAE5B,MAAI,KAAK,WAAW,IAAI,EAAE;AACxB,OAAI,SAAS,SAAS;IACpB,MAAM,QAAQ,OAAO,aAAa;AAElC,QAAI,OAAO,UAAU,aAAa,MAAM,WAAW,IAAI,IAAI,MAAM,SAAS,KAAK,EAC7E,QAAO;AAET,WAAO,WAAW,OAAO,MAAM,CAAC;;AAGlC,WAAQ,YAAY,EAAE,EAAE,KAAK,UAAe,oBAAoB,MAAM,CAAC,CAAC,KAAK,GAAG;;AAElF,SAAO,cAAc,MAAM,OAAO,SAAS;;AAG7C,KAAI,OAAO,SAAS,WAElB,QAAO,oBADQ,KAAK,SAAS,EAAE,CAAC,CACE;AAGpC,QAAO;;;;;AAMT,SAAS,cAAc,KAAa,OAAY,UAAyB;CACvE,MAAM,QAAQ,iBAAiB,SAAS,EAAE,CAAC;AAI3C,KADoB;EAAC;EAAQ;EAAQ;EAAM;EAAM;EAAO;EAAQ,CAChD,SAAS,IAAI,CAC3B,QAAO,IAAI,MAAM,MAAM;AASzB,QAAO,IAAI,MAAM,MAAM,GALL,QAAQ,WAAW,QAAQ,YAExC,YAAY,EAAE,EAAE,KAAK,UAAU,eAAe,MAAM,CAAC,CAAC,KAAK,GAAG,IAC9D,YAAY,EAAE,EAAE,KAAK,UAAU,oBAAoB,MAAM,CAAC,CAAC,KAAK,GAAG,CAEjC,IAAI,IAAI;;;;;;AAOjD,SAAS,eAAe,OAAoB;AAC1C,KAAI,SAAS,QAAQ,OAAO,UAAU,UAAW,QAAO;AACxD,KAAI,OAAO,UAAU,YAAY,OAAO,UAAU,SAAU,QAAO,OAAO,MAAM;AAChF,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,MAAM,IAAI,eAAe,CAAC,KAAK,GAAG;AACnE,KAAI,OAAO,UAAU,YAAY,UAAU,OAAO;AAChD,MAAI,MAAM,SAAS,QAAS,QAAO,OAAO,MAAM,OAAO,aAAa,GAAG;AACvE,MAAI,MAAM,SAAS,UAAW,SAAQ,MAAM,YAAY,EAAE,EAAE,IAAI,eAAe,CAAC,KAAK,GAAG;AAExF,SAAO,oBAAoB,MAAM;;AAEnC,QAAO;;;;;AAMT,SAAS,iBAAiB,OAAoC;CAC5D,MAAM,QAAkB,EAAE;AAE1B,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,EAAE;AAChD,MAAI,QAAQ,cAAc,QAAQ,SAAS,QAAQ,SAAS,QAAQ,0BAClE;AAGF,MAAI,SAAS,QAAQ,UAAU,MAC7B;AAIF,MAAI,UAAU,MAAM;AAClB,SAAM,KAAK,IAAI;AACf;;EAIF,MAAM,WACJ,QAAQ,cACJ,UACA,QAAQ,YACN,QACA,QAAQ,YACN,YACA,QAAQ,gBACN,gBACA,QAAQ,cACN,eACA;AAEd,QAAM,KAAK,GAAG,SAAS,IAAI,WAAW,OAAO,MAAM,CAAC,CAAC,GAAG;;AAG1D,QAAO,MAAM,SAAS,IAAI,MAAM,MAAM,KAAK,IAAI,GAAG;;;;;AAMpD,SAAS,WAAW,KAAqB;AACvC,QAAO,IACJ,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,SAAS,CACvB,QAAQ,MAAM,QAAQ"}
@@ -1,5 +1,5 @@
1
- import "../src-Cv4rRVzv.mjs";
2
- import { _ as createTextNode, a as setAppStyleTarget, b as replaceNode, c as PortalComponent, d as setProperty, f as setRef, g as createElement, h as createComment, i as getStyleTarget, l as createPortal, m as appendChild, n as AppStyleAnchor, o as setComponentStyleTarget, p as setSignalProperty, r as ComponentStyleAnchor, s as ForwardComponent, t as Native, u as render, v as insertBefore, x as setText, y as removeChild } from "../src-C_aFsFJ3.mjs";
1
+ import "../src--YS4EvMz.mjs";
2
+ import { _ as createTextNode, a as setAppStyleTarget, b as replaceNode, c as PortalComponent, d as setProperty, f as setRef, g as createElement, h as createComment, i as getStyleTarget, l as createPortal, m as appendChild, n as AppStyleAnchor, o as setComponentStyleTarget, p as setSignalProperty, r as ComponentStyleAnchor, s as ForwardComponent, t as Native, u as render, v as insertBefore, x as setText, y as removeChild } from "../src-Cm12Y2XV.mjs";
3
3
  import "../src-DV9uwtE5.mjs";
4
4
 
5
5
  export { AppStyleAnchor, ComponentStyleAnchor, ForwardComponent, Native, PortalComponent, appendChild, createComment, createElement, createPortal, createTextNode, getStyleTarget, insertBefore, removeChild, render, replaceNode, setAppStyleTarget, setComponentStyleTarget, setProperty, setRef, setSignalProperty, setText };
@@ -1,4 +1,4 @@
1
- import { f as ISLAND_MARKER, h, v as Fragment } from "../src-Cv4rRVzv.mjs";
1
+ import { f as ISLAND_MARKER, h, v as Fragment } from "../src--YS4EvMz.mjs";
2
2
  import "../jsx-runtime-BFs1c0xz.mjs";
3
3
 
4
4
  //#region ../dom/src/jsx-dev-runtime.ts
@@ -1,4 +1,4 @@
1
- import { _ as Forward, a as jsxs, i as jsx, v as Fragment } from "../src-Cv4rRVzv.mjs";
1
+ import { _ as Forward, a as jsxs, i as jsx, v as Fragment } from "../src--YS4EvMz.mjs";
2
2
  import "../jsx-runtime-BFs1c0xz.mjs";
3
3
 
4
4
  export { Forward, Fragment, jsx, jsxs };
package/dist/index.d.mts CHANGED
@@ -139,6 +139,17 @@ interface RenderStrategy<TNode> {
139
139
  * This is used for DOM optimization but not needed for terminal
140
140
  */
141
141
  tryReuseNode?(oldNode: TNode, newNode: TNode, oldRendered: RenderedNode<TNode>, newRendered: RenderedNode<TNode>): boolean;
142
+ /**
143
+ * Optional: Called before a component function is invoked.
144
+ * Renderers can use this to set up per-component state (e.g., cleanup scopes).
145
+ */
146
+ onBeforeComponent?(): void;
147
+ /**
148
+ * Optional: Called after a component function returns.
149
+ * Returns an array of cleanup functions to attach to the component's RenderedNode subscriptions.
150
+ * This enables per-component lifecycle hooks like onCleanup().
151
+ */
152
+ onAfterComponent?(): Array<() => void>;
142
153
  }
143
154
  /**
144
155
  * Check if a value is a Promise
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../core/src/vnode.ts","../../core/src/context.ts","../../core/src/render-core.ts"],"mappings":";;;;;;;;;;;iBAQgB,CAAA,CACd,IAAA,EAAM,SAAA,EACN,KAAA,EAAO,MAAA,yBACJ,QAAA,EAAU,OAAA,KACZ,KAAA;;;AAJH;iBAgBgB,eAAA,CAAgB,IAAA,oBAAwB,KAAA;;;;iBAuDxC,OAAA,CAAQ,KAAA,YAAiB,KAAA,IAAS,KAAA;;;;iBAalC,cAAA,CAAe,QAAA,EAAU,OAAA,KAAY,KAAA;;;KCnFzC,UAAA,GAAa,GAAA;;;;;;;ADDzB;;;;;;;;;;iBCmBgB,OAAA,GAAA,CAAW,IAAA,YAAgB,SAAA,CAAY,CAAA;;;;;;;;ADHvD;;;;;AAuDA;;;;;;;iBC5BgB,OAAA,CAAQ,KAAA,EAAO,YAAA,GAAe,KAAA;;ADyC9C;;;;;iBCvBgB,kBAAA,CAAmB,UAAA,EAAY,UAAA,GAAa,YAAA;;;;;;UC1D3C,YAAA;EACf,KAAA,EAAO,KAAA;EACP,IAAA,EAAM,KAAA;EACN,aAAA,EAAe,KAAA;EACf,QAAA,EAAU,YAAA,CAAa,KAAA;AAAA;;;;UAMR,cAAA;;;;EAIf,cAAA,CAAe,IAAA,WAAe,KAAA;;;;EAK9B,aAAA,CAAc,IAAA,WAAe,KAAA;;;;EAK7B,aAAA,CAAc,IAAA,WAAe,KAAA;EFXf;;;EEgBd,SAAA,CAAU,IAAA,EAAM,KAAA,GAAQ,KAAA;EFuC1B;;;EElCE,cAAA,CAAe,IAAA,EAAM,KAAA,GAAQ,KAAA;;;;EAK7B,YAAA,CAAa,MAAA,EAAQ,KAAA,EAAO,OAAA,EAAS,KAAA,EAAO,UAAA,EAAY,KAAA;EF6BR;AAalD;;EErCE,WAAA,CAAY,MAAA,EAAQ,KAAA,EAAO,KAAA,EAAO,KAAA;EFqCiB;;;EEhCnD,WAAA,CAAY,IAAA,EAAM,KAAA;;;;EAKlB,WAAA,CAAY,OAAA,EAAS,KAAA,EAAO,OAAA,EAAS,KAAA;;ADxDvC;;EC6DE,WAAA,CAAY,IAAA,EAAM,KAAA,EAAO,GAAA,UAAa,KAAA;ED7Df;;AAkBzB;ECgDE,iBAAA,CAAkB,IAAA,EAAM,KAAA,EAAO,GAAA,UAAa,MAAA;;;;;EAM5C,MAAA,EAAQ,IAAA,EAAM,KAAA,EAAO,GAAA,EAAK,GAAA,CAAI,KAAA;;;;AD9BhC;;ECqCE,YAAA,EACE,OAAA,EAAS,KAAA,EACT,OAAA,EAAS,KAAA,EACT,WAAA,EAAa,YAAA,CAAa,KAAA,GAC1B,WAAA,EAAa,YAAA,CAAa,KAAA;AAAA;;;;iBAOd,SAAA,GAAA,CAAa,KAAA,QAAa,KAAA,IAAS,OAAA,CAAQ,CAAA;;AD9B3D;;iBCqCgB,eAAA,GAAA,CAAmB,KAAA,QAAa,KAAA,IAAS,qBAAA,CAAsB,CAAA;;;;iBAO/D,cAAA,OAAA,CAAsB,QAAA,EAAU,cAAA,CAAe,KAAA;EAC7D,UAAA,GAAa,KAAA,EAAO,KAAA,EAAO,aAAA,EAAe,UAAA,KAAe,YAAA,CAAa,KAAA;EACtE,OAAA,GAAU,IAAA,EAAM,YAAA,CAAa,KAAA;EAC7B,oBAAA,GAAuB,IAAA,EAAM,YAAA,CAAa,KAAA;AAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../core/src/vnode.ts","../../core/src/context.ts","../../core/src/render-core.ts"],"mappings":";;;;;;;;;;;iBAQgB,CAAA,CACd,IAAA,EAAM,SAAA,EACN,KAAA,EAAO,MAAA,yBACJ,QAAA,EAAU,OAAA,KACZ,KAAA;;;AAJH;iBAgBgB,eAAA,CAAgB,IAAA,oBAAwB,KAAA;;;;iBAuDxC,OAAA,CAAQ,KAAA,YAAiB,KAAA,IAAS,KAAA;;;;iBAalC,cAAA,CAAe,QAAA,EAAU,OAAA,KAAY,KAAA;;;KCnFzC,UAAA,GAAa,GAAA;;;;;;;ADDzB;;;;;;;;;;iBCmBgB,OAAA,GAAA,CAAW,IAAA,YAAgB,SAAA,CAAY,CAAA;;;;;;;;ADHvD;;;;;AAuDA;;;;;;;iBC5BgB,OAAA,CAAQ,KAAA,EAAO,YAAA,GAAe,KAAA;;ADyC9C;;;;;iBCvBgB,kBAAA,CAAmB,UAAA,EAAY,UAAA,GAAa,YAAA;;;;;;UC1D3C,YAAA;EACf,KAAA,EAAO,KAAA;EACP,IAAA,EAAM,KAAA;EACN,aAAA,EAAe,KAAA;EACf,QAAA,EAAU,YAAA,CAAa,KAAA;AAAA;;;;UAMR,cAAA;;;;EAIf,cAAA,CAAe,IAAA,WAAe,KAAA;;;;EAK9B,aAAA,CAAc,IAAA,WAAe,KAAA;;;;EAK7B,aAAA,CAAc,IAAA,WAAe,KAAA;EFXf;;;EEgBd,SAAA,CAAU,IAAA,EAAM,KAAA,GAAQ,KAAA;EFuC1B;;;EElCE,cAAA,CAAe,IAAA,EAAM,KAAA,GAAQ,KAAA;;;;EAK7B,YAAA,CAAa,MAAA,EAAQ,KAAA,EAAO,OAAA,EAAS,KAAA,EAAO,UAAA,EAAY,KAAA;EF6BR;AAalD;;EErCE,WAAA,CAAY,MAAA,EAAQ,KAAA,EAAO,KAAA,EAAO,KAAA;EFqCiB;;;EEhCnD,WAAA,CAAY,IAAA,EAAM,KAAA;;;;EAKlB,WAAA,CAAY,OAAA,EAAS,KAAA,EAAO,OAAA,EAAS,KAAA;;ADxDvC;;EC6DE,WAAA,CAAY,IAAA,EAAM,KAAA,EAAO,GAAA,UAAa,KAAA;ED7Df;;AAkBzB;ECgDE,iBAAA,CAAkB,IAAA,EAAM,KAAA,EAAO,GAAA,UAAa,MAAA;;;;;EAM5C,MAAA,EAAQ,IAAA,EAAM,KAAA,EAAO,GAAA,EAAK,GAAA,CAAI,KAAA;;;;AD9BhC;;ECqCE,YAAA,EACE,OAAA,EAAS,KAAA,EACT,OAAA,EAAS,KAAA,EACT,WAAA,EAAa,YAAA,CAAa,KAAA,GAC1B,WAAA,EAAa,YAAA,CAAa,KAAA;EDzCgB;;;;ECgD5C,iBAAA;EDhD4C;AAkB9C;;;;ECqCE,gBAAA,KAAqB,KAAA;AAAA;;;;iBAMP,SAAA,GAAA,CAAa,KAAA,QAAa,KAAA,IAAS,OAAA,CAAQ,CAAA;;;AArG3D;iBA4GgB,eAAA,GAAA,CAAmB,KAAA,QAAa,KAAA,IAAS,qBAAA,CAAsB,CAAA;;;;iBAO/D,cAAA,OAAA,CAAsB,QAAA,EAAU,cAAA,CAAe,KAAA;EAC7D,UAAA,GAAa,KAAA,EAAO,KAAA,EAAO,aAAA,EAAe,UAAA,KAAe,YAAA,CAAa,KAAA;EACtE,OAAA,GAAU,IAAA,EAAM,YAAA,CAAa,KAAA;EAC7B,oBAAA,GAAuB,IAAA,EAAM,YAAA,CAAa,KAAA;AAAA"}
package/dist/index.mjs CHANGED
@@ -2,6 +2,6 @@ import { n as batch, t as signal } from "./signal-4PgGfydw.mjs";
2
2
  import { n as memo, t as computed } from "./computed-BpjqvQu1.mjs";
3
3
  import { n as unwrap, t as isSignal } from "./utils-BrGmTgfG.mjs";
4
4
  import "./signal/index.mjs";
5
- import { _ as Forward, a as jsxs, c as when, d as createComponentAPI, f as ISLAND_MARKER, g as isVNode, h, i as jsx, l as Context, m as createTextVNode, n as isAsyncIterator, o as resource, p as createFragment, r as isPromise, s as stream, t as createRenderer, u as context, v as Fragment, y as Portal } from "./src-Cv4rRVzv.mjs";
5
+ import { _ as Forward, a as jsxs, c as when, d as createComponentAPI, f as ISLAND_MARKER, g as isVNode, h, i as jsx, l as Context, m as createTextVNode, n as isAsyncIterator, o as resource, p as createFragment, r as isPromise, s as stream, t as createRenderer, u as context, v as Fragment, y as Portal } from "./src--YS4EvMz.mjs";
6
6
 
7
7
  export { Context, Forward, Fragment, ISLAND_MARKER, Portal, batch, computed, context, createComponentAPI, createFragment, createRenderer, createTextVNode, h, isAsyncIterator, isPromise, isSignal, isVNode, jsx, jsxs, memo, resource, signal, stream, unwrap, when };
@@ -1,5 +1,5 @@
1
1
  import { c as JSXNode, d as VNode, m as WithSignals, n as ComponentAPI } from "./types-C83YtOen.mjs";
2
- import { i as TerminalStyle } from "./types-Bj5q5x2Q.mjs";
2
+ import { i as TerminalStyle } from "./types-Bm8rZGKW.mjs";
3
3
 
4
4
  //#region ../terminal/src/jsx-runtime.d.ts
5
5
  /**
@@ -109,4 +109,4 @@ declare namespace JSX {
109
109
  }
110
110
  //#endregion
111
111
  export { TextAttributes as i, JSX as n, TerminalAttributes as r, BoxAttributes as t };
112
- //# sourceMappingURL=jsx-runtime-Dc77fsnM.d.mts.map
112
+ //# sourceMappingURL=jsx-runtime-tdaY-P9K.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"jsx-runtime-Dc77fsnM.d.mts","names":[],"sources":["../../terminal/src/jsx-runtime.ts"],"mappings":";;;;;;;UAeU,sBAAA,SAA+B,aAAA;EAEvC,GAAA;EACA,QAAA,GAAW,OAAA;AAAA;;;;AAsBb;KAfY,kBAAA,GAAqB,WAAA,CAAY,sBAAA;;;;AA8B7C;;;;;AAKA;;;;;KApBY,aAAA,GAAgB,kBAAA;;;;;;;;;;;;;;KAehB,cAAA,GAAiB,kBAAA;;;;kBAKZ,GAAA;EAAA,KACH,OAAA,GAAU,KAAA;EAAA,KAEV,WAAA,SACF,iBAAA,KACJ,KAAA,UAAe,OAAA,MACf,KAAA,OAAY,GAAA,EAAK,YAAA,KAAiB,OAAA;EAAA,UAEvB,wBAAA;IACf,QAAA;EAAA;EAAA,UAGe,mBAAA;IACf,GAAA;EAAA;EAAA,UAGe,iBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA6Bf,GAAA,EAAK,aAAA;;;;;;;;;;;;;;;;;;;;;IAsBL,IAAA,EAAM,cAAA;EAAA;AAAA"}
1
+ {"version":3,"file":"jsx-runtime-tdaY-P9K.d.mts","names":[],"sources":["../../terminal/src/jsx-runtime.ts"],"mappings":";;;;;;;UAeU,sBAAA,SAA+B,aAAA;EAEvC,GAAA;EACA,QAAA,GAAW,OAAA;AAAA;;;;AAsBb;KAfY,kBAAA,GAAqB,WAAA,CAAY,sBAAA;;;;AA8B7C;;;;;AAKA;;;;;KApBY,aAAA,GAAgB,kBAAA;;;;;;;;;;;;;;KAehB,cAAA,GAAiB,kBAAA;;;;kBAKZ,GAAA;EAAA,KACH,OAAA,GAAU,KAAA;EAAA,KAEV,WAAA,SACF,iBAAA,KACJ,KAAA,UAAe,OAAA,MACf,KAAA,OAAY,GAAA,EAAK,YAAA,KAAiB,OAAA;EAAA,UAEvB,wBAAA;IACf,QAAA;EAAA;EAAA,UAGe,mBAAA;IACf,GAAA;EAAA;EAAA,UAGe,iBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA6Bf,GAAA,EAAK,aAAA;;;;;;;;;;;;;;;;;;;;;IAsBL,IAAA,EAAM,cAAA;EAAA;AAAA"}
@@ -1,5 +1,5 @@
1
- import { h, i as jsx } from "./src-Cv4rRVzv.mjs";
2
- import { t as Native } from "./src-C_aFsFJ3.mjs";
1
+ import { h, i as jsx } from "./src--YS4EvMz.mjs";
2
+ import { t as Native } from "./src-Cm12Y2XV.mjs";
3
3
  import * as allExports from "lucide";
4
4
  import { createElement } from "lucide";
5
5
 
@@ -123,4 +123,4 @@ function lucide(options = {}) {
123
123
 
124
124
  //#endregion
125
125
  export { Icon as n, lucide as t };
126
- //# sourceMappingURL=lucide-Ddt_N9dJ.mjs.map
126
+ //# sourceMappingURL=lucide-DWk3itzO.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"lucide-Ddt_N9dJ.mjs","names":[],"sources":["../../ssg/src/plugins/lucide/component.tsx","../../ssg/src/plugins/lucide/index.ts"],"sourcesContent":["/** @jsxImportSource @semajsx/dom */\n\nimport { h } from \"@semajsx/core\";\nimport type { VNode } from \"@semajsx/core\";\nimport { Native } from \"@semajsx/dom\";\nimport { createElement } from \"lucide\";\nimport * as allExports from \"lucide\";\n\n/**\n * lucide IconNode: array of [tag, attrs] tuples\n * e.g., [[\"path\", { d: \"M4.5 16.5...\" }], [\"circle\", { cx: \"12\", ... }]]\n */\ntype IconNode = [string, Record<string, string>][];\n\n/**\n * Convert kebab-case icon name to PascalCase for lucide lookup.\n *\n * \"arrow-right\" → \"ArrowRight\"\n * \"rocket\" → \"Rocket\"\n */\nfunction toPascalCase(name: string): string {\n return name\n .split(\"-\")\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(\"\");\n}\n\n/**\n * Look up an icon by kebab-case name from lucide's exports.\n * Returns the IconNode data (array of [tag, attrs] tuples) or undefined.\n */\nfunction getIcon(name: string): IconNode | undefined {\n const pascalName = toPascalCase(name);\n const icon = (allExports as Record<string, unknown>)[pascalName];\n // IconNode is an array of tuples; functions (createElement, createIcons) are filtered out\n if (Array.isArray(icon)) return icon as IconNode;\n return undefined;\n}\n\nexport interface IconProps {\n /** Icon name in kebab-case (e.g., \"rocket\", \"arrow-right\", \"circle-check\") */\n name: string;\n /** Icon size in pixels (default: 24) */\n size?: number;\n /** Stroke color (default: \"currentColor\") */\n color?: string;\n /** Stroke width (default: 2) */\n strokeWidth?: number;\n /** Additional CSS class */\n class?: string;\n}\n\n/**\n * Default SVG attributes matching lucide's defaults.\n */\nconst SVG_DEFAULTS = {\n xmlns: \"http://www.w3.org/2000/svg\",\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n \"stroke-width\": 2,\n \"stroke-linecap\": \"round\",\n \"stroke-linejoin\": \"round\",\n} as const;\n\n/**\n * Isomorphic Lucide icon component.\n *\n * - **Browser**: uses lucide's `createElement` to produce a real SVGElement,\n * embedded via `<Native>` for optimal performance.\n * - **Server (SSG/SSR)**: constructs JSX SVG from IconNode data,\n * rendered to HTML string by the SSR renderer.\n *\n * @example\n * ```mdx\n * <Icon name=\"rocket\" />\n * <Icon name=\"arrow-right\" size={20} color=\"#007aff\" />\n * ```\n */\nexport function Icon({\n name,\n size = 24,\n color = \"currentColor\",\n strokeWidth = 2,\n class: className,\n}: IconProps): VNode {\n const iconNode = getIcon(name);\n\n if (!iconNode) {\n return (\n <span class=\"lucide-icon-missing\" title={`Unknown icon: ${name}`}>\n {\"?\"}\n </span>\n );\n }\n\n const iconClass = className ? `lucide lucide-${name} ${className}` : `lucide lucide-${name}`;\n\n // Browser: use lucide's createElement + Native\n if (typeof document !== \"undefined\") {\n const element = createElement(iconNode, {\n width: size,\n height: size,\n stroke: color,\n \"stroke-width\": strokeWidth,\n class: iconClass,\n });\n return <Native element={element} />;\n }\n\n // Server: construct SVG from IconNode data\n return h(\n \"svg\",\n {\n ...SVG_DEFAULTS,\n width: size,\n height: size,\n stroke: color,\n \"stroke-width\": strokeWidth,\n class: iconClass,\n },\n ...iconNode.map(([tag, attrs]) => h(tag, attrs)),\n );\n}\n","import type { SSGPlugin } from \"../../types\";\nimport { Icon, type IconProps } from \"./component\";\n\nexport type { IconProps } from \"./component\";\nexport { Icon };\n\nexport interface LucidePluginOptions {\n /** Default icon size in pixels (default: 24) */\n size?: number;\n /** Default stroke color (default: \"currentColor\") */\n color?: string;\n /** Default stroke width (default: 2) */\n strokeWidth?: number;\n}\n\n/**\n * Lucide icon plugin for SSG.\n *\n * Registers the `<Icon>` component for use in MDX content.\n * Optionally configure default icon properties.\n *\n * @example\n * ```tsx\n * import { createSSG } from \"@semajsx/ssg\";\n * import { lucide } from \"@semajsx/ssg/plugins/lucide\";\n *\n * const ssg = createSSG({\n * plugins: [lucide()],\n * });\n * ```\n *\n * @example\n * ```tsx\n * // With custom defaults\n * const ssg = createSSG({\n * plugins: [lucide({ size: 20, color: \"#333\" })],\n * });\n * ```\n *\n * @example\n * ```mdx\n * <Icon name=\"rocket\" />\n * <Icon name=\"arrow-right\" size={16} color=\"#007aff\" />\n * ```\n */\nexport function lucide(options: LucidePluginOptions = {}): SSGPlugin {\n const { size, color, strokeWidth } = options;\n const hasDefaults = size !== undefined || color !== undefined || strokeWidth !== undefined;\n\n // Wrap Icon with custom defaults if any options are provided\n const IconComponent = hasDefaults\n ? (props: IconProps) =>\n Icon({\n ...props,\n size: props.size ?? size,\n color: props.color ?? color,\n strokeWidth: props.strokeWidth ?? strokeWidth,\n })\n : Icon;\n\n return {\n name: \"lucide\",\n config() {\n return {\n mdx: {\n components: { Icon: IconComponent },\n },\n };\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;AAoBA,SAAS,aAAa,MAAsB;AAC1C,QAAO,KACJ,MAAM,IAAI,CACV,KAAK,SAAS,KAAK,OAAO,EAAE,CAAC,aAAa,GAAG,KAAK,MAAM,EAAE,CAAC,CAC3D,KAAK,GAAG;;;;;;AAOb,SAAS,QAAQ,MAAoC;CAEnD,MAAM,OAAQ,WADK,aAAa,KAAK;AAGrC,KAAI,MAAM,QAAQ,KAAK,CAAE,QAAO;;;;;AAoBlC,MAAM,eAAe;CACnB,OAAO;CACP,SAAS;CACT,MAAM;CACN,QAAQ;CACR,gBAAgB;CAChB,kBAAkB;CAClB,mBAAmB;CACpB;;;;;;;;;;;;;;;AAgBD,SAAgB,KAAK,EACnB,MACA,OAAO,IACP,QAAQ,gBACR,cAAc,GACd,OAAO,aACY;CACnB,MAAM,WAAW,QAAQ,KAAK;AAE9B,KAAI,CAAC,SACH,QACE,oBAAC;EAAK,OAAM;EAAsB,OAAO,iBAAiB;YACvD;GACI;CAIX,MAAM,YAAY,YAAY,iBAAiB,KAAK,GAAG,cAAc,iBAAiB;AAGtF,KAAI,OAAO,aAAa,YAQtB,QAAO,oBAAC,UAAO,SAPC,cAAc,UAAU;EACtC,OAAO;EACP,QAAQ;EACR,QAAQ;EACR,gBAAgB;EAChB,OAAO;EACR,CAAC,GACiC;AAIrC,QAAO,EACL,OACA;EACE,GAAG;EACH,OAAO;EACP,QAAQ;EACR,QAAQ;EACR,gBAAgB;EAChB,OAAO;EACR,EACD,GAAG,SAAS,KAAK,CAAC,KAAK,WAAW,EAAE,KAAK,MAAM,CAAC,CACjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC7EH,SAAgB,OAAO,UAA+B,EAAE,EAAa;CACnE,MAAM,EAAE,MAAM,OAAO,gBAAgB;CAIrC,MAAM,gBAHc,SAAS,UAAa,UAAU,UAAa,gBAAgB,UAI5E,UACC,KAAK;EACH,GAAG;EACH,MAAM,MAAM,QAAQ;EACpB,OAAO,MAAM,SAAS;EACtB,aAAa,MAAM,eAAe;EACnC,CAAC,GACJ;AAEJ,QAAO;EACL,MAAM;EACN,SAAS;AACP,UAAO,EACL,KAAK,EACH,YAAY,EAAE,MAAM,eAAe,EACpC,EACF;;EAEJ"}
1
+ {"version":3,"file":"lucide-DWk3itzO.mjs","names":[],"sources":["../../ssg/src/plugins/lucide/component.tsx","../../ssg/src/plugins/lucide/index.ts"],"sourcesContent":["/** @jsxImportSource @semajsx/dom */\n\nimport { h } from \"@semajsx/core\";\nimport type { VNode } from \"@semajsx/core\";\nimport { Native } from \"@semajsx/dom\";\nimport { createElement } from \"lucide\";\nimport * as allExports from \"lucide\";\n\n/**\n * lucide IconNode: array of [tag, attrs] tuples\n * e.g., [[\"path\", { d: \"M4.5 16.5...\" }], [\"circle\", { cx: \"12\", ... }]]\n */\ntype IconNode = [string, Record<string, string>][];\n\n/**\n * Convert kebab-case icon name to PascalCase for lucide lookup.\n *\n * \"arrow-right\" → \"ArrowRight\"\n * \"rocket\" → \"Rocket\"\n */\nfunction toPascalCase(name: string): string {\n return name\n .split(\"-\")\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(\"\");\n}\n\n/**\n * Look up an icon by kebab-case name from lucide's exports.\n * Returns the IconNode data (array of [tag, attrs] tuples) or undefined.\n */\nfunction getIcon(name: string): IconNode | undefined {\n const pascalName = toPascalCase(name);\n const icon = (allExports as Record<string, unknown>)[pascalName];\n // IconNode is an array of tuples; functions (createElement, createIcons) are filtered out\n if (Array.isArray(icon)) return icon as IconNode;\n return undefined;\n}\n\nexport interface IconProps {\n /** Icon name in kebab-case (e.g., \"rocket\", \"arrow-right\", \"circle-check\") */\n name: string;\n /** Icon size in pixels (default: 24) */\n size?: number;\n /** Stroke color (default: \"currentColor\") */\n color?: string;\n /** Stroke width (default: 2) */\n strokeWidth?: number;\n /** Additional CSS class */\n class?: string;\n}\n\n/**\n * Default SVG attributes matching lucide's defaults.\n */\nconst SVG_DEFAULTS = {\n xmlns: \"http://www.w3.org/2000/svg\",\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n \"stroke-width\": 2,\n \"stroke-linecap\": \"round\",\n \"stroke-linejoin\": \"round\",\n} as const;\n\n/**\n * Isomorphic Lucide icon component.\n *\n * - **Browser**: uses lucide's `createElement` to produce a real SVGElement,\n * embedded via `<Native>` for optimal performance.\n * - **Server (SSG/SSR)**: constructs JSX SVG from IconNode data,\n * rendered to HTML string by the SSR renderer.\n *\n * @example\n * ```mdx\n * <Icon name=\"rocket\" />\n * <Icon name=\"arrow-right\" size={20} color=\"#007aff\" />\n * ```\n */\nexport function Icon({\n name,\n size = 24,\n color = \"currentColor\",\n strokeWidth = 2,\n class: className,\n}: IconProps): VNode {\n const iconNode = getIcon(name);\n\n if (!iconNode) {\n return (\n <span class=\"lucide-icon-missing\" title={`Unknown icon: ${name}`}>\n {\"?\"}\n </span>\n );\n }\n\n const iconClass = className ? `lucide lucide-${name} ${className}` : `lucide lucide-${name}`;\n\n // Browser: use lucide's createElement + Native\n if (typeof document !== \"undefined\") {\n const element = createElement(iconNode, {\n width: size,\n height: size,\n stroke: color,\n \"stroke-width\": strokeWidth,\n class: iconClass,\n });\n return <Native element={element} />;\n }\n\n // Server: construct SVG from IconNode data\n return h(\n \"svg\",\n {\n ...SVG_DEFAULTS,\n width: size,\n height: size,\n stroke: color,\n \"stroke-width\": strokeWidth,\n class: iconClass,\n },\n ...iconNode.map(([tag, attrs]) => h(tag, attrs)),\n );\n}\n","import type { SSGPlugin } from \"../../types\";\nimport { Icon, type IconProps } from \"./component\";\n\nexport type { IconProps } from \"./component\";\nexport { Icon };\n\nexport interface LucidePluginOptions {\n /** Default icon size in pixels (default: 24) */\n size?: number;\n /** Default stroke color (default: \"currentColor\") */\n color?: string;\n /** Default stroke width (default: 2) */\n strokeWidth?: number;\n}\n\n/**\n * Lucide icon plugin for SSG.\n *\n * Registers the `<Icon>` component for use in MDX content.\n * Optionally configure default icon properties.\n *\n * @example\n * ```tsx\n * import { createSSG } from \"@semajsx/ssg\";\n * import { lucide } from \"@semajsx/ssg/plugins/lucide\";\n *\n * const ssg = createSSG({\n * plugins: [lucide()],\n * });\n * ```\n *\n * @example\n * ```tsx\n * // With custom defaults\n * const ssg = createSSG({\n * plugins: [lucide({ size: 20, color: \"#333\" })],\n * });\n * ```\n *\n * @example\n * ```mdx\n * <Icon name=\"rocket\" />\n * <Icon name=\"arrow-right\" size={16} color=\"#007aff\" />\n * ```\n */\nexport function lucide(options: LucidePluginOptions = {}): SSGPlugin {\n const { size, color, strokeWidth } = options;\n const hasDefaults = size !== undefined || color !== undefined || strokeWidth !== undefined;\n\n // Wrap Icon with custom defaults if any options are provided\n const IconComponent = hasDefaults\n ? (props: IconProps) =>\n Icon({\n ...props,\n size: props.size ?? size,\n color: props.color ?? color,\n strokeWidth: props.strokeWidth ?? strokeWidth,\n })\n : Icon;\n\n return {\n name: \"lucide\",\n config() {\n return {\n mdx: {\n components: { Icon: IconComponent },\n },\n };\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;AAoBA,SAAS,aAAa,MAAsB;AAC1C,QAAO,KACJ,MAAM,IAAI,CACV,KAAK,SAAS,KAAK,OAAO,EAAE,CAAC,aAAa,GAAG,KAAK,MAAM,EAAE,CAAC,CAC3D,KAAK,GAAG;;;;;;AAOb,SAAS,QAAQ,MAAoC;CAEnD,MAAM,OAAQ,WADK,aAAa,KAAK;AAGrC,KAAI,MAAM,QAAQ,KAAK,CAAE,QAAO;;;;;AAoBlC,MAAM,eAAe;CACnB,OAAO;CACP,SAAS;CACT,MAAM;CACN,QAAQ;CACR,gBAAgB;CAChB,kBAAkB;CAClB,mBAAmB;CACpB;;;;;;;;;;;;;;;AAgBD,SAAgB,KAAK,EACnB,MACA,OAAO,IACP,QAAQ,gBACR,cAAc,GACd,OAAO,aACY;CACnB,MAAM,WAAW,QAAQ,KAAK;AAE9B,KAAI,CAAC,SACH,QACE,oBAAC;EAAK,OAAM;EAAsB,OAAO,iBAAiB;YACvD;GACI;CAIX,MAAM,YAAY,YAAY,iBAAiB,KAAK,GAAG,cAAc,iBAAiB;AAGtF,KAAI,OAAO,aAAa,YAQtB,QAAO,oBAAC,UAAO,SAPC,cAAc,UAAU;EACtC,OAAO;EACP,QAAQ;EACR,QAAQ;EACR,gBAAgB;EAChB,OAAO;EACR,CAAC,GACiC;AAIrC,QAAO,EACL,OACA;EACE,GAAG;EACH,OAAO;EACP,QAAQ;EACR,QAAQ;EACR,gBAAgB;EAChB,OAAO;EACR,EACD,GAAG,SAAS,KAAK,CAAC,KAAK,WAAW,EAAE,KAAK,MAAM,CAAC,CACjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC7EH,SAAgB,OAAO,UAA+B,EAAE,EAAa;CACnE,MAAM,EAAE,MAAM,OAAO,gBAAgB;CAIrC,MAAM,gBAHc,SAAS,UAAa,UAAU,UAAa,gBAAgB,UAI5E,UACC,KAAK;EACH,GAAG;EACH,MAAM,MAAM,QAAQ;EACpB,OAAO,MAAM,SAAS;EACtB,aAAa,MAAM,eAAe;EACnC,CAAC,GACJ;AAEJ,QAAO;EACL,MAAM;EACN,SAAS;AACP,UAAO,EACL,KAAK,EACH,YAAY,EAAE,MAAM,eAAe,EACpC,EACF;;EAEJ"}
@@ -1,4 +1,4 @@
1
- import { f as ISLAND_MARKER } from "./src-Cv4rRVzv.mjs";
1
+ import { f as ISLAND_MARKER } from "./src--YS4EvMz.mjs";
2
2
 
3
3
  //#region ../ssr/src/client/island.ts
4
4
  /**
@@ -193,4 +193,4 @@ function isAssetVNode(vnode) {
193
193
 
194
194
  //#endregion
195
195
  export { isLinkVNode as a, getIslandMetadata as c, island as d, isAssetVNode as i, isIslandComponent as l, LINK_MARKER as n, isStyleVNode as o, STYLE_MARKER as r, resource as s, ASSET_MARKER as t, isIslandVNode as u };
196
- //# sourceMappingURL=resource-pm7qP-jV.mjs.map
196
+ //# sourceMappingURL=resource-BU0Po0ez.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"resource-pm7qP-jV.mjs","names":[],"sources":["../../ssr/src/client/island.ts","../../ssr/src/client/resource.ts"],"sourcesContent":["import { ISLAND_MARKER, type Component, type JSXNode, type VNode } from \"@semajsx/core\";\nimport type { IslandMarker } from \"../shared/types\";\n\n/**\n * Mark a component as an Island that should be hydrated on the client\n *\n * @param component - The component function to mark as an island\n * @param modulePath - The module path to the component file (use import.meta.url or relative path)\n * @returns A wrapped component that can be used in SSR\n *\n * @example\n * ```tsx\n * // Using import.meta.url (recommended)\n * export const Counter = island(\n * function Counter({ initial = 0 }) {\n * const count = signal(initial)\n * return <button onClick={() => count.value++}>{count}</button>\n * },\n * import.meta.url\n * )\n *\n * // Using relative path\n * export const TodoList = island(\n * TodoListComponent,\n * './components/TodoList.tsx'\n * )\n * ```\n */\nexport function island<T extends Component<any>>(component: T, modulePath: string): T {\n // Create a wrapped component that marks itself as an island\n const wrappedComponent = ((props: any): JSXNode => {\n // Create a VNode with island marker\n const vnode: VNode & { [ISLAND_MARKER]?: IslandMarker } = {\n type: component,\n props: props || {},\n children: [],\n [ISLAND_MARKER]: {\n modulePath,\n component,\n props,\n },\n };\n\n return vnode;\n }) as T;\n\n // Also mark the wrapper function itself for static analysis\n (wrappedComponent as any)[ISLAND_MARKER] = {\n modulePath,\n component,\n };\n\n // Preserve component name for debugging\n Object.defineProperty(wrappedComponent, \"name\", {\n value: component.name || \"IslandComponent\",\n configurable: true,\n });\n\n return wrappedComponent;\n}\n\n/**\n * Check if a component is marked as an Island\n */\nexport function isIslandComponent(component: any): boolean {\n return component && ISLAND_MARKER in component;\n}\n\n/**\n * Check if a VNode is an Island instance\n */\nexport function isIslandVNode(vnode: VNode): vnode is VNode & {\n [ISLAND_MARKER]: IslandMarker;\n} {\n return ISLAND_MARKER in vnode;\n}\n\n/**\n * Get island metadata from a VNode\n */\nexport function getIslandMetadata(vnode: VNode): IslandMarker | undefined {\n if (isIslandVNode(vnode)) {\n return vnode[ISLAND_MARKER];\n }\n return undefined;\n}\n","import { ISLAND_MARKER, type Component, type JSXNode, type VNode } from \"@semajsx/core\";\nimport type { IslandMarker } from \"../shared/types\";\n\n/**\n * Symbol markers for resource types\n */\nexport const STYLE_MARKER: symbol = Symbol.for(\"semajsx.style\");\nexport const LINK_MARKER: symbol = Symbol.for(\"semajsx.link\");\nexport const ASSET_MARKER: symbol = Symbol.for(\"semajsx.asset\");\n\n/**\n * Style component props\n */\nexport interface StyleProps {\n href: string;\n}\n\n/**\n * Link component props\n */\nexport interface LinkProps {\n href: string;\n rel?: string;\n as?: string;\n}\n\n/**\n * Asset component props\n */\nexport interface AssetProps {\n src: string;\n type?: \"image\" | \"font\" | \"script\";\n}\n\n/**\n * Resource tools returned by resource()\n */\nexport interface ResourceTools {\n /** CSS stylesheet declaration */\n Style: (props: StyleProps) => VNode;\n /** External resource link */\n Link: (props: LinkProps) => VNode;\n /** Static asset declaration */\n Asset: (props: AssetProps) => VNode;\n /** Resolve relative path to absolute */\n url: (path: string) => string;\n /** Create an island component */\n island: <T extends Component<any>>(component: T, name?: string) => T;\n}\n\n/**\n * Create resource tools bound to a module path\n *\n * @param baseUrl - The module URL (use import.meta.url)\n * @returns Resource tools with resolved paths\n *\n * @example\n * ```tsx\n * import { resource } from '@semajsx/ssr';\n *\n * const { Style, Link, Asset, url, island } = resource(import.meta.url);\n *\n * export default function Page() {\n * return (\n * <>\n * <Style href=\"./page.css\" />\n * <img src={url('./hero.png')} />\n * <Counter />\n * </>\n * );\n * }\n *\n * const Counter = island(function Counter({ initial = 0 }) {\n * return <button>{initial}</button>;\n * });\n * ```\n */\nexport function resource(baseUrl: string): ResourceTools {\n /**\n * Resolve a relative path against the base URL\n */\n const resolve = (href: string): string => {\n if (href.startsWith(\"./\") || href.startsWith(\"../\")) {\n const url = new URL(href, baseUrl);\n return url.pathname + url.search + url.hash;\n }\n return href;\n };\n\n return {\n Style({ href }: StyleProps): VNode {\n return {\n type: STYLE_MARKER as unknown as string,\n props: { href: resolve(href) },\n children: [],\n };\n },\n\n Link({ href, rel = \"stylesheet\", as: asType }: LinkProps): VNode {\n return {\n type: LINK_MARKER as unknown as string,\n props: { href: resolve(href), rel, as: asType },\n children: [],\n };\n },\n\n Asset({ src, type = \"image\" }: AssetProps): VNode {\n return {\n type: ASSET_MARKER as unknown as string,\n props: { src: resolve(src), type },\n children: [],\n };\n },\n\n url(path: string): string {\n return resolve(path);\n },\n\n island<T extends Component<any>>(component: T, name?: string): T {\n const componentName = name || component.name || \"Anonymous\";\n\n const wrappedComponent = ((props: Record<string, unknown>): JSXNode => {\n const vnode: VNode & { [ISLAND_MARKER]?: IslandMarker } = {\n type: component,\n props: props || {},\n children: [],\n [ISLAND_MARKER]: {\n modulePath: baseUrl,\n component,\n props,\n },\n };\n\n return vnode;\n }) as T;\n\n // Mark the wrapper function for static analysis\n (wrappedComponent as Record<symbol, unknown>)[ISLAND_MARKER] = {\n modulePath: baseUrl,\n component,\n };\n\n // Preserve component name\n Object.defineProperty(wrappedComponent, \"name\", {\n value: componentName,\n configurable: true,\n });\n\n return wrappedComponent;\n },\n };\n}\n\n/**\n * Check if a VNode is a Style resource\n */\nexport function isStyleVNode(vnode: VNode): boolean {\n return vnode.type === STYLE_MARKER;\n}\n\n/**\n * Check if a VNode is a Link resource\n */\nexport function isLinkVNode(vnode: VNode): boolean {\n return vnode.type === LINK_MARKER;\n}\n\n/**\n * Check if a VNode is an Asset resource\n */\nexport function isAssetVNode(vnode: VNode): boolean {\n return vnode.type === ASSET_MARKER;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,OAAiC,WAAc,YAAuB;CAEpF,MAAM,qBAAqB,UAAwB;AAajD,SAX0D;GACxD,MAAM;GACN,OAAO,SAAS,EAAE;GAClB,UAAU,EAAE;IACX,gBAAgB;IACf;IACA;IACA;IACD;GACF;;AAMH,CAAC,iBAAyB,iBAAiB;EACzC;EACA;EACD;AAGD,QAAO,eAAe,kBAAkB,QAAQ;EAC9C,OAAO,UAAU,QAAQ;EACzB,cAAc;EACf,CAAC;AAEF,QAAO;;;;;AAMT,SAAgB,kBAAkB,WAAyB;AACzD,QAAO,aAAa,iBAAiB;;;;;AAMvC,SAAgB,cAAc,OAE5B;AACA,QAAO,iBAAiB;;;;;AAM1B,SAAgB,kBAAkB,OAAwC;AACxE,KAAI,cAAc,MAAM,CACtB,QAAO,MAAM;;;;;;;;AC5EjB,MAAa,eAAuB,OAAO,IAAI,gBAAgB;AAC/D,MAAa,cAAsB,OAAO,IAAI,eAAe;AAC7D,MAAa,eAAuB,OAAO,IAAI,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqE/D,SAAgB,SAAS,SAAgC;;;;CAIvD,MAAM,WAAW,SAAyB;AACxC,MAAI,KAAK,WAAW,KAAK,IAAI,KAAK,WAAW,MAAM,EAAE;GACnD,MAAM,MAAM,IAAI,IAAI,MAAM,QAAQ;AAClC,UAAO,IAAI,WAAW,IAAI,SAAS,IAAI;;AAEzC,SAAO;;AAGT,QAAO;EACL,MAAM,EAAE,QAA2B;AACjC,UAAO;IACL,MAAM;IACN,OAAO,EAAE,MAAM,QAAQ,KAAK,EAAE;IAC9B,UAAU,EAAE;IACb;;EAGH,KAAK,EAAE,MAAM,MAAM,cAAc,IAAI,UAA4B;AAC/D,UAAO;IACL,MAAM;IACN,OAAO;KAAE,MAAM,QAAQ,KAAK;KAAE;KAAK,IAAI;KAAQ;IAC/C,UAAU,EAAE;IACb;;EAGH,MAAM,EAAE,KAAK,OAAO,WAA8B;AAChD,UAAO;IACL,MAAM;IACN,OAAO;KAAE,KAAK,QAAQ,IAAI;KAAE;KAAM;IAClC,UAAU,EAAE;IACb;;EAGH,IAAI,MAAsB;AACxB,UAAO,QAAQ,KAAK;;EAGtB,OAAiC,WAAc,MAAkB;GAC/D,MAAM,gBAAgB,QAAQ,UAAU,QAAQ;GAEhD,MAAM,qBAAqB,UAA4C;AAYrE,WAX0D;KACxD,MAAM;KACN,OAAO,SAAS,EAAE;KAClB,UAAU,EAAE;MACX,gBAAgB;MACf,YAAY;MACZ;MACA;MACD;KACF;;AAMH,GAAC,iBAA6C,iBAAiB;IAC7D,YAAY;IACZ;IACD;AAGD,UAAO,eAAe,kBAAkB,QAAQ;IAC9C,OAAO;IACP,cAAc;IACf,CAAC;AAEF,UAAO;;EAEV;;;;;AAMH,SAAgB,aAAa,OAAuB;AAClD,QAAO,MAAM,SAAS;;;;;AAMxB,SAAgB,YAAY,OAAuB;AACjD,QAAO,MAAM,SAAS;;;;;AAMxB,SAAgB,aAAa,OAAuB;AAClD,QAAO,MAAM,SAAS"}
1
+ {"version":3,"file":"resource-BU0Po0ez.mjs","names":[],"sources":["../../ssr/src/client/island.ts","../../ssr/src/client/resource.ts"],"sourcesContent":["import { ISLAND_MARKER, type Component, type JSXNode, type VNode } from \"@semajsx/core\";\nimport type { IslandMarker } from \"../shared/types\";\n\n/**\n * Mark a component as an Island that should be hydrated on the client\n *\n * @param component - The component function to mark as an island\n * @param modulePath - The module path to the component file (use import.meta.url or relative path)\n * @returns A wrapped component that can be used in SSR\n *\n * @example\n * ```tsx\n * // Using import.meta.url (recommended)\n * export const Counter = island(\n * function Counter({ initial = 0 }) {\n * const count = signal(initial)\n * return <button onClick={() => count.value++}>{count}</button>\n * },\n * import.meta.url\n * )\n *\n * // Using relative path\n * export const TodoList = island(\n * TodoListComponent,\n * './components/TodoList.tsx'\n * )\n * ```\n */\nexport function island<T extends Component<any>>(component: T, modulePath: string): T {\n // Create a wrapped component that marks itself as an island\n const wrappedComponent = ((props: any): JSXNode => {\n // Create a VNode with island marker\n const vnode: VNode & { [ISLAND_MARKER]?: IslandMarker } = {\n type: component,\n props: props || {},\n children: [],\n [ISLAND_MARKER]: {\n modulePath,\n component,\n props,\n },\n };\n\n return vnode;\n }) as T;\n\n // Also mark the wrapper function itself for static analysis\n (wrappedComponent as any)[ISLAND_MARKER] = {\n modulePath,\n component,\n };\n\n // Preserve component name for debugging\n Object.defineProperty(wrappedComponent, \"name\", {\n value: component.name || \"IslandComponent\",\n configurable: true,\n });\n\n return wrappedComponent;\n}\n\n/**\n * Check if a component is marked as an Island\n */\nexport function isIslandComponent(component: any): boolean {\n return component && ISLAND_MARKER in component;\n}\n\n/**\n * Check if a VNode is an Island instance\n */\nexport function isIslandVNode(vnode: VNode): vnode is VNode & {\n [ISLAND_MARKER]: IslandMarker;\n} {\n return ISLAND_MARKER in vnode;\n}\n\n/**\n * Get island metadata from a VNode\n */\nexport function getIslandMetadata(vnode: VNode): IslandMarker | undefined {\n if (isIslandVNode(vnode)) {\n return vnode[ISLAND_MARKER];\n }\n return undefined;\n}\n","import { ISLAND_MARKER, type Component, type JSXNode, type VNode } from \"@semajsx/core\";\nimport type { IslandMarker } from \"../shared/types\";\n\n/**\n * Symbol markers for resource types\n */\nexport const STYLE_MARKER: symbol = Symbol.for(\"semajsx.style\");\nexport const LINK_MARKER: symbol = Symbol.for(\"semajsx.link\");\nexport const ASSET_MARKER: symbol = Symbol.for(\"semajsx.asset\");\n\n/**\n * Style component props\n */\nexport interface StyleProps {\n href: string;\n}\n\n/**\n * Link component props\n */\nexport interface LinkProps {\n href: string;\n rel?: string;\n as?: string;\n}\n\n/**\n * Asset component props\n */\nexport interface AssetProps {\n src: string;\n type?: \"image\" | \"font\" | \"script\";\n}\n\n/**\n * Resource tools returned by resource()\n */\nexport interface ResourceTools {\n /** CSS stylesheet declaration */\n Style: (props: StyleProps) => VNode;\n /** External resource link */\n Link: (props: LinkProps) => VNode;\n /** Static asset declaration */\n Asset: (props: AssetProps) => VNode;\n /** Resolve relative path to absolute */\n url: (path: string) => string;\n /** Create an island component */\n island: <T extends Component<any>>(component: T, name?: string) => T;\n}\n\n/**\n * Create resource tools bound to a module path\n *\n * @param baseUrl - The module URL (use import.meta.url)\n * @returns Resource tools with resolved paths\n *\n * @example\n * ```tsx\n * import { resource } from '@semajsx/ssr';\n *\n * const { Style, Link, Asset, url, island } = resource(import.meta.url);\n *\n * export default function Page() {\n * return (\n * <>\n * <Style href=\"./page.css\" />\n * <img src={url('./hero.png')} />\n * <Counter />\n * </>\n * );\n * }\n *\n * const Counter = island(function Counter({ initial = 0 }) {\n * return <button>{initial}</button>;\n * });\n * ```\n */\nexport function resource(baseUrl: string): ResourceTools {\n /**\n * Resolve a relative path against the base URL\n */\n const resolve = (href: string): string => {\n if (href.startsWith(\"./\") || href.startsWith(\"../\")) {\n const url = new URL(href, baseUrl);\n return url.pathname + url.search + url.hash;\n }\n return href;\n };\n\n return {\n Style({ href }: StyleProps): VNode {\n return {\n type: STYLE_MARKER as unknown as string,\n props: { href: resolve(href) },\n children: [],\n };\n },\n\n Link({ href, rel = \"stylesheet\", as: asType }: LinkProps): VNode {\n return {\n type: LINK_MARKER as unknown as string,\n props: { href: resolve(href), rel, as: asType },\n children: [],\n };\n },\n\n Asset({ src, type = \"image\" }: AssetProps): VNode {\n return {\n type: ASSET_MARKER as unknown as string,\n props: { src: resolve(src), type },\n children: [],\n };\n },\n\n url(path: string): string {\n return resolve(path);\n },\n\n island<T extends Component<any>>(component: T, name?: string): T {\n const componentName = name || component.name || \"Anonymous\";\n\n const wrappedComponent = ((props: Record<string, unknown>): JSXNode => {\n const vnode: VNode & { [ISLAND_MARKER]?: IslandMarker } = {\n type: component,\n props: props || {},\n children: [],\n [ISLAND_MARKER]: {\n modulePath: baseUrl,\n component,\n props,\n },\n };\n\n return vnode;\n }) as T;\n\n // Mark the wrapper function for static analysis\n (wrappedComponent as Record<symbol, unknown>)[ISLAND_MARKER] = {\n modulePath: baseUrl,\n component,\n };\n\n // Preserve component name\n Object.defineProperty(wrappedComponent, \"name\", {\n value: componentName,\n configurable: true,\n });\n\n return wrappedComponent;\n },\n };\n}\n\n/**\n * Check if a VNode is a Style resource\n */\nexport function isStyleVNode(vnode: VNode): boolean {\n return vnode.type === STYLE_MARKER;\n}\n\n/**\n * Check if a VNode is a Link resource\n */\nexport function isLinkVNode(vnode: VNode): boolean {\n return vnode.type === LINK_MARKER;\n}\n\n/**\n * Check if a VNode is an Asset resource\n */\nexport function isAssetVNode(vnode: VNode): boolean {\n return vnode.type === ASSET_MARKER;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,OAAiC,WAAc,YAAuB;CAEpF,MAAM,qBAAqB,UAAwB;AAajD,SAX0D;GACxD,MAAM;GACN,OAAO,SAAS,EAAE;GAClB,UAAU,EAAE;IACX,gBAAgB;IACf;IACA;IACA;IACD;GACF;;AAMH,CAAC,iBAAyB,iBAAiB;EACzC;EACA;EACD;AAGD,QAAO,eAAe,kBAAkB,QAAQ;EAC9C,OAAO,UAAU,QAAQ;EACzB,cAAc;EACf,CAAC;AAEF,QAAO;;;;;AAMT,SAAgB,kBAAkB,WAAyB;AACzD,QAAO,aAAa,iBAAiB;;;;;AAMvC,SAAgB,cAAc,OAE5B;AACA,QAAO,iBAAiB;;;;;AAM1B,SAAgB,kBAAkB,OAAwC;AACxE,KAAI,cAAc,MAAM,CACtB,QAAO,MAAM;;;;;;;;AC5EjB,MAAa,eAAuB,OAAO,IAAI,gBAAgB;AAC/D,MAAa,cAAsB,OAAO,IAAI,eAAe;AAC7D,MAAa,eAAuB,OAAO,IAAI,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqE/D,SAAgB,SAAS,SAAgC;;;;CAIvD,MAAM,WAAW,SAAyB;AACxC,MAAI,KAAK,WAAW,KAAK,IAAI,KAAK,WAAW,MAAM,EAAE;GACnD,MAAM,MAAM,IAAI,IAAI,MAAM,QAAQ;AAClC,UAAO,IAAI,WAAW,IAAI,SAAS,IAAI;;AAEzC,SAAO;;AAGT,QAAO;EACL,MAAM,EAAE,QAA2B;AACjC,UAAO;IACL,MAAM;IACN,OAAO,EAAE,MAAM,QAAQ,KAAK,EAAE;IAC9B,UAAU,EAAE;IACb;;EAGH,KAAK,EAAE,MAAM,MAAM,cAAc,IAAI,UAA4B;AAC/D,UAAO;IACL,MAAM;IACN,OAAO;KAAE,MAAM,QAAQ,KAAK;KAAE;KAAK,IAAI;KAAQ;IAC/C,UAAU,EAAE;IACb;;EAGH,MAAM,EAAE,KAAK,OAAO,WAA8B;AAChD,UAAO;IACL,MAAM;IACN,OAAO;KAAE,KAAK,QAAQ,IAAI;KAAE;KAAM;IAClC,UAAU,EAAE;IACb;;EAGH,IAAI,MAAsB;AACxB,UAAO,QAAQ,KAAK;;EAGtB,OAAiC,WAAc,MAAkB;GAC/D,MAAM,gBAAgB,QAAQ,UAAU,QAAQ;GAEhD,MAAM,qBAAqB,UAA4C;AAYrE,WAX0D;KACxD,MAAM;KACN,OAAO,SAAS,EAAE;KAClB,UAAU,EAAE;MACX,gBAAgB;MACf,YAAY;MACZ;MACA;MACD;KACF;;AAMH,GAAC,iBAA6C,iBAAiB;IAC7D,YAAY;IACZ;IACD;AAGD,UAAO,eAAe,kBAAkB,QAAQ;IAC9C,OAAO;IACP,cAAc;IACf,CAAC;AAEF,UAAO;;EAEV;;;;;AAMH,SAAgB,aAAa,OAAuB;AAClD,QAAO,MAAM,SAAS;;;;;AAMxB,SAAgB,YAAY,OAAuB;AACjD,QAAO,MAAM,SAAS;;;;;AAMxB,SAAgB,aAAa,OAAuB;AAClD,QAAO,MAAM,SAAS"}
@@ -531,7 +531,10 @@ function createRenderer(strategy) {
531
531
  currentContext.set(context, value);
532
532
  } else for (const [context, value] of provide) currentContext.set(context, value);
533
533
  }
534
- const result = Component(props, createComponentAPI(currentContext));
534
+ const ctx = createComponentAPI(currentContext);
535
+ strategy.onBeforeComponent?.();
536
+ const result = Component(props, ctx);
537
+ const componentCleanups = strategy.onAfterComponent?.() ?? [];
535
538
  if (isPromise(result)) {
536
539
  const rendered = renderNode({
537
540
  type: "#signal",
@@ -548,7 +551,7 @@ function createRenderer(strategy) {
548
551
  return {
549
552
  vnode,
550
553
  node: rendered.node,
551
- subscriptions: rendered.subscriptions,
554
+ subscriptions: [...componentCleanups, ...rendered.subscriptions],
552
555
  children: [rendered]
553
556
  };
554
557
  }
@@ -568,7 +571,7 @@ function createRenderer(strategy) {
568
571
  return {
569
572
  vnode,
570
573
  node: rendered.node,
571
- subscriptions: rendered.subscriptions,
574
+ subscriptions: [...componentCleanups, ...rendered.subscriptions],
572
575
  children: [rendered]
573
576
  };
574
577
  }
@@ -584,7 +587,7 @@ function createRenderer(strategy) {
584
587
  return {
585
588
  vnode,
586
589
  node: rendered.node,
587
- subscriptions: rendered.subscriptions,
590
+ subscriptions: [...componentCleanups, ...rendered.subscriptions],
588
591
  children: [rendered]
589
592
  };
590
593
  }
@@ -592,7 +595,7 @@ function createRenderer(strategy) {
592
595
  return {
593
596
  vnode,
594
597
  node: rendered.node,
595
- subscriptions: rendered.subscriptions,
598
+ subscriptions: [...componentCleanups, ...rendered.subscriptions],
596
599
  children: [rendered]
597
600
  };
598
601
  }
@@ -654,4 +657,4 @@ function createRenderer(strategy) {
654
657
 
655
658
  //#endregion
656
659
  export { Forward as _, jsxs as a, when as c, createComponentAPI as d, ISLAND_MARKER as f, isVNode as g, h, jsx as i, Context as l, createTextVNode as m, isAsyncIterator as n, resource as o, createFragment as p, isPromise as r, stream as s, createRenderer as t, context as u, Fragment as v, Portal as y };
657
- //# sourceMappingURL=src-Cv4rRVzv.mjs.map
660
+ //# sourceMappingURL=src--YS4EvMz.mjs.map