nuclo 0.1.113 → 0.1.115
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.
- package/dist/core/elementFactory.d.ts.map +1 -1
- package/dist/core/modifierProcessor.d.ts.map +1 -1
- package/dist/core/runtimeBootstrap.d.ts.map +1 -1
- package/dist/hydration/context.d.ts +35 -0
- package/dist/hydration/context.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/list/runtime.d.ts.map +1 -1
- package/dist/nuclo.cjs +1 -1
- package/dist/nuclo.cjs.map +1 -1
- package/dist/nuclo.mjs +1 -1
- package/dist/nuclo.mjs.map +1 -1
- package/dist/nuclo.umd.js +1 -1
- package/dist/nuclo.umd.js.map +1 -1
- package/dist/ssr/index.d.ts +1 -0
- package/dist/ssr/index.d.ts.map +1 -1
- package/dist/ssr/nuclo.ssr.cjs +1 -1
- package/dist/ssr/nuclo.ssr.cjs.map +1 -1
- package/dist/ssr/nuclo.ssr.mjs +1 -1
- package/dist/ssr/nuclo.ssr.mjs.map +1 -1
- package/dist/style/cssGenerator.d.ts +7 -0
- package/dist/style/cssGenerator.d.ts.map +1 -1
- package/dist/utility/render.d.ts +23 -0
- package/dist/utility/render.d.ts.map +1 -1
- package/dist/when/builder.d.ts +3 -0
- package/dist/when/builder.d.ts.map +1 -1
- package/dist/when/runtime.d.ts +5 -0
- package/dist/when/runtime.d.ts.map +1 -1
- package/package.json +63 -63
- package/types/features/render.d.ts +21 -0
package/dist/ssr/index.d.ts
CHANGED
package/dist/ssr/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ssr/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,2BAA2B,EAC5B,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ssr/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,2BAA2B,EAC5B,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/ssr/nuclo.ssr.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e){let t=``;for(let n=0;n<e.length;n++){let r=e.charCodeAt(n);r>=65&&r<=90?(n>0&&(t+=`-`),t+=String.fromCharCode(r+32)):t+=e[n]}return t}function t(e){let t={"&":`&`,"<":`<`,">":`>`,'"':`"`,"'":`'`};return e.replace(/[&<>"']/g,e=>t[e])}function n(e){return e.replace(/[&<>]/g,e=>e===`&`?`&`:e===`<`?`<`:`>`)}function r(e){return globalThis.document?document.createElement(e):null}const i=new Set(`viewBox.preserveAspectRatio.markerWidth.markerHeight.gradientTransform.patternTransform.clipPathUnits.gradientUnits.patternUnits.pathLength.refX.refY.stdDeviation.baseFrequency.numOctaves.kernelMatrix.tableValues.targetX.targetY.specularExponent.specularConstant.diffuseConstant.surfaceScale.xChannelSelector.yChannelSelector.edgeMode.stitchTiles.spreadMethod.patternContentUnits.markerUnits.startOffset.textLength.lengthAdjust`.split(`.`)),a=new Set([`allowfullscreen`,`async`,`autofocus`,`autoplay`,`checked`,`controls`,`default`,`defer`,`disabled`,`formnovalidate`,`hidden`,`ismap`,`loop`,`multiple`,`muted`,`nomodule`,`novalidate`,`open`,`readonly`,`required`,`reversed`,`selected`]);function o(n,r){if(r==null||r===!1)return``;if(r===!0)return` ${n}`;if(a.has(n)){if(r===`false`)return``;if(r===`true`||r===``||r===n)return` ${n}`}if(n===`style`&&typeof r==`object`){let n=Object.entries(r).filter(([,e])=>e!=null&&e!==``).map(([t,n])=>`${e(t)}: ${n};`).join(` `);return n?` style="${t(n)}"`:``}return` ${n}="${t(String(r))}"`}function s(n){let r=``;if(`attributes`in n&&n.attributes instanceof Map){let a=n;if(a.id&&!n.attributes.has(`id`)&&(r+=o(`id`,a.id)),a.className&&!n.attributes.has(`class`)&&(r+=o(`class`,a.className)),!n.attributes.has(`style`)&&a.style){let n=a.style.cssText||``;if(n){let i=n.split(`;`).map(e=>e.trim()).filter(Boolean).map(t=>{let n=t.indexOf(`:`);if(n===-1)return t;let r=t.slice(0,n).trim(),i=t.slice(n+1).trim();return i?`${e(r)}: ${i};`:``}).filter(Boolean).join(` `);i&&(r+=` style="${t(i)}"`)}}for(let[t,a]of n.attributes){let n=i.has(t)?t:e(t);r+=o(n,a)}return r}if(n.attributes&&n.attributes.length)for(let e=0;e<n.attributes.length;e++){let t=n.attributes[e];t&&t.name&&(r+=o(t.name,t.value))}return r}const c=new Set([`area`,`base`,`br`,`col`,`embed`,`hr`,`img`,`input`,`link`,`meta`,`param`,`source`,`track`,`wbr`]);function l(e){if(`tagName`in e&&`children`in e){let t=e.children;if(Array.isArray(t))return t}if(`childNodes`in e){let t=e.childNodes;if(t&&(Array.isArray(t)||t.length!==void 0))return t}return[]}function u(e){if(e.nodeType===3)return n(e.textContent||``);if(e.nodeType===8)return`<!--${e.textContent||``}-->`;if(e.nodeType===1){let t=e,r=t.tagName.toLowerCase(),i=s(t);if(c.has(r))return`<${r}${i} />`;let a=``,o=l(t);if(o&&o.length>0)for(let e=0;e<o.length;e++){let t=o[e];t&&(a+=u(t))}else{let t=e.textContent;typeof t==`string`&&t&&(a=n(t))}return`<${r}${i}>${a}</${r}>`}if(e.nodeType===11){let t=``,n=l(e);if(n&&n.length>0)for(let e=0;e<n.length;e++){let r=n[e];r&&(t+=u(r))}return t}return``}function d(e){if(!e)return``;if(typeof e==`function`)try{let t=r(`div`);if(!t)throw Error(`Document is not available. Make sure polyfills are loaded.`);let n=e(t,0);return n&&typeof n==`object`&&`nodeType`in n?u(n):``}catch(e){return console.error(`Error rendering component to string:`,e),``}return`nodeType`in e?u(e):``}function f(e){return e.map(e=>d(e))}function p(e,t=`div`,n={}){let r=d(e);return`<${t}${Object.entries(n).map(([e,t])=>o(e,t)).join(``)}>${r}</${t}>`}exports.renderManyToString=f,exports.renderToString=d,exports.renderToStringWithContainer=p;
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e){let t=``;for(let n=0;n<e.length;n++){let r=e.charCodeAt(n);r>=65&&r<=90?(n>0&&(t+=`-`),t+=String.fromCharCode(r+32)):t+=e[n]}return t}function t(e){let t={"&":`&`,"<":`<`,">":`>`,'"':`"`,"'":`'`};return e.replace(/[&<>"']/g,e=>t[e])}function n(e){return e.replace(/[&<>]/g,e=>e===`&`?`&`:e===`<`?`<`:`>`)}function r(e){return globalThis.document?document.createElement(e):null}const i=new Set(`viewBox.preserveAspectRatio.markerWidth.markerHeight.gradientTransform.patternTransform.clipPathUnits.gradientUnits.patternUnits.pathLength.refX.refY.stdDeviation.baseFrequency.numOctaves.kernelMatrix.tableValues.targetX.targetY.specularExponent.specularConstant.diffuseConstant.surfaceScale.xChannelSelector.yChannelSelector.edgeMode.stitchTiles.spreadMethod.patternContentUnits.markerUnits.startOffset.textLength.lengthAdjust`.split(`.`)),a=new Set([`allowfullscreen`,`async`,`autofocus`,`autoplay`,`checked`,`controls`,`default`,`defer`,`disabled`,`formnovalidate`,`hidden`,`ismap`,`loop`,`multiple`,`muted`,`nomodule`,`novalidate`,`open`,`readonly`,`required`,`reversed`,`selected`]);function o(n,r){if(r==null||r===!1)return``;if(r===!0)return` ${n}`;if(a.has(n)){if(r===`false`)return``;if(r===`true`||r===``||r===n)return` ${n}`}if(n===`style`&&typeof r==`object`){let n=Object.entries(r).filter(([,e])=>e!=null&&e!==``).map(([t,n])=>`${e(t)}: ${n};`).join(` `);return n?` style="${t(n)}"`:``}return` ${n}="${t(String(r))}"`}function s(n){let r=``;if(`attributes`in n&&n.attributes instanceof Map){let a=n;if(a.id&&!n.attributes.has(`id`)&&(r+=o(`id`,a.id)),a.className&&!n.attributes.has(`class`)&&(r+=o(`class`,a.className)),!n.attributes.has(`style`)&&a.style){let n=a.style.cssText||``;if(n){let i=n.split(`;`).map(e=>e.trim()).filter(Boolean).map(t=>{let n=t.indexOf(`:`);if(n===-1)return t;let r=t.slice(0,n).trim(),i=t.slice(n+1).trim();return i?`${e(r)}: ${i};`:``}).filter(Boolean).join(` `);i&&(r+=` style="${t(i)}"`)}}for(let[t,a]of n.attributes){let n=i.has(t)?t:e(t);r+=o(n,a)}return r}if(n.attributes&&n.attributes.length)for(let e=0;e<n.attributes.length;e++){let t=n.attributes[e];t&&t.name&&(r+=o(t.name,t.value))}return r}const c=new Set([`area`,`base`,`br`,`col`,`embed`,`hr`,`img`,`input`,`link`,`meta`,`param`,`source`,`track`,`wbr`]);function l(e){if(`tagName`in e&&`children`in e){let t=e.children;if(Array.isArray(t))return t}if(`childNodes`in e){let t=e.childNodes;if(t&&(Array.isArray(t)||t.length!==void 0))return t}return[]}function u(e){if(e.nodeType===3)return n(e.textContent||``);if(e.nodeType===8)return`<!--${e.textContent||``}-->`;if(e.nodeType===1){let t=e,r=t.tagName.toLowerCase(),i=s(t);if(c.has(r))return`<${r}${i} />`;let a=``,o=l(t);if(o&&o.length>0)for(let e=0;e<o.length;e++){let t=o[e];t&&(a+=u(t))}else{let t=e.textContent;typeof t==`string`&&t&&(a=n(t))}return`<${r}${i}>${a}</${r}>`}if(e.nodeType===11){let t=``,n=l(e);if(n&&n.length>0)for(let e=0;e<n.length;e++){let r=n[e];r&&(t+=u(r))}return t}return``}function d(e){if(!e)return``;if(typeof e==`function`)try{let t=r(`div`);if(!t)throw Error(`Document is not available. Make sure polyfills are loaded.`);let n=e(t,0);return n&&typeof n==`object`&&`nodeType`in n?u(n):``}catch(e){return console.error(`Error rendering component to string:`,e),``}return`nodeType`in e?u(e):``}function f(e){return e.map(e=>d(e))}function p(e,t=`div`,n={}){let r=d(e);return`<${t}${Object.entries(n).map(([e,t])=>o(e,t)).join(``)}>${r}</${t}>`}function m(e){}exports.renderManyToString=f,exports.renderToString=d,exports.renderToStringWithContainer=p,exports.setSSRCollector=m;
|
|
2
2
|
//# sourceMappingURL=nuclo.ssr.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nuclo.ssr.cjs","names":[],"sources":["../../src/utility/stringUtils.ts","../../src/utility/dom.ts","../../src/ssr/renderToString.ts"],"sourcesContent":["/**\n * String utility functions\n */\n\n/**\n * Converts camelCase to kebab-case (optimized for performance)\n * Uses direct character code manipulation for maximum speed\n * @example camelToKebab('backgroundColor') => 'background-color'\n */\nexport function camelToKebab(str: string): string {\n let result = '';\n for (let i = 0; i < str.length; i++) {\n const code = str.charCodeAt(i);\n // A-Z is 65-90, a-z is 97-122 (difference of 32)\n if (code >= 65 && code <= 90) {\n if (i > 0) result += '-';\n result += String.fromCharCode(code + 32);\n } else {\n result += str[i];\n }\n }\n return result;\n}\n\n/**\n * Escapes HTML special characters in attribute values (includes \" and ')\n */\nexport function escapeHtml(text: string): string {\n const map: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": '''\n };\n return text.replace(/[&<>\"']/g, (char) => map[char]);\n}\n\n/**\n * Escapes HTML special characters in text node content.\n * Only &, < and > need escaping — quotes are safe inside text nodes.\n */\nexport function escapeText(text: string): string {\n return text.replace(/[&<>]/g, (char) => (char === '&' ? '&' : char === '<' ? '<' : '>'));\n}\n","import { logError } from \"./errorHandler\";\nimport { removeAllListeners } from \"./on\";\nimport { cleanupReactiveTextNode, cleanupReactiveElement } from \"../core/reactiveCleanup\";\nimport { unregisterConditionalNode } from \"./conditionalInfo\";\n\nexport const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';\n\n/**\n * Creates an HTML element.\n * Wrapper for document.createElement with type safety.\n */\nexport function createElement<K extends keyof HTMLElementTagNameMap>(\n tagName: K\n): HTMLElementTagNameMap[K] | null;\nexport function createElement(tagName: string): ExpandedElement | null;\nexport function createElement(tagName: string): ExpandedElement | null {\n return globalThis.document ? document.createElement(tagName) as ExpandedElement : null;\n}\n\n/**\n * Creates an element in the given namespace (typically for SVG elements).\n * Wrapper for document.createElementNS with type safety.\n */\nexport function createElementNS(\n namespace: string,\n tagName: string\n): ExpandedElement | null {\n return globalThis.document ? document.createElementNS(namespace, tagName) as ExpandedElement : null;\n}\n\n/**\n * Creates a text node with the given content.\n * Wrapper for document.createTextNode.\n */\nexport function createTextNode(text: string): Text | null {\n return globalThis.document ? document.createTextNode(text) : null;\n}\n\n/**\n * Creates a document fragment.\n * Wrapper for document.createDocumentFragment.\n */\nexport function createDocumentFragment(): DocumentFragment | null {\n return globalThis.document ? document.createDocumentFragment() : null;\n}\n\nfunction safeAppendChild(parent: Element | Node, child: Node): boolean {\n try {\n parent.appendChild(child);\n return true;\n } catch (error) {\n logError('Failed to append child node', error);\n return false;\n }\n}\n\n/**\n * Recursively removes all event listeners and reactive subscriptions from a node and its descendants\n * to prevent memory leaks when elements are removed from the DOM.\n */\nfunction cleanupEventListeners(node: Node): void {\n // Clean up the node itself based on its type\n if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as HTMLElement;\n // Remove all event listeners\n removeAllListeners(element);\n // Remove reactive attribute resolvers\n cleanupReactiveElement(element);\n // Remove conditional info\n unregisterConditionalNode(element);\n } else if (node.nodeType === Node.TEXT_NODE) {\n // Remove reactive text node info\n cleanupReactiveTextNode(node as Text);\n } else if (node.nodeType === Node.COMMENT_NODE) {\n // Remove conditional info from comment nodes (used by when/list)\n unregisterConditionalNode(node);\n }\n \n // Recursively clean up all child nodes\n if (node.childNodes && node.childNodes.length > 0) {\n for (let i = 0; i < node.childNodes.length; i++) {\n cleanupEventListeners(node.childNodes[i]);\n }\n }\n}\n\nexport function safeRemoveChild(child: Node): boolean {\n if (!child?.parentNode) return false;\n try {\n // Clean up all event listeners before removing the element\n cleanupEventListeners(child);\n child.parentNode.removeChild(child);\n return true;\n } catch (error) {\n logError('Failed to remove child node', error);\n return false;\n }\n}\n\nfunction safeInsertBefore(parent: Node, newNode: Node, referenceNode: Node | null): boolean {\n try {\n parent.insertBefore(newNode, referenceNode);\n return true;\n } catch (error) {\n logError('Failed to insert node before reference', error);\n return false;\n }\n}\n\nexport function createComment(text: string): Comment | null {\n return globalThis.document ? globalThis.document.createComment(text) : null;\n}\n\nexport function createConditionalComment(tagName: string, suffix = \"hidden\"): Comment | null {\n return globalThis.document ? globalThis.document.createComment(`conditional-${tagName}-${suffix}`) : null;\n}\n\nexport function createMarkerPair(prefix: string, id: number | string): { start: Comment; end: Comment } {\n const endComment = createComment(`${prefix}-end`);\n if (!endComment) throw new Error(\"Failed to create comment: document not available\");\n const startComment = createComment(`${prefix}-start-${id}`);\n if (!startComment) throw new Error(\"Failed to create comment: document not available\");\n return { start: startComment, end: endComment };\n}\n\nexport function clearBetweenMarkers(startMarker: Comment, endMarker: Comment): void {\n let current = startMarker.nextSibling;\n while (current && current !== endMarker) {\n const next = current.nextSibling;\n safeRemoveChild(current);\n current = next;\n }\n}\n\nexport function insertNodesBefore(nodes: Node[], referenceNode: Node): void {\n const parent = referenceNode.parentNode;\n if (parent) {\n for (let i = 0; i < nodes.length; i++) {\n safeInsertBefore(parent, nodes[i], referenceNode);\n }\n }\n}\n\nexport function appendChildren(\n parent: Element | Node,\n ...children: Array<Element | Node | string | null | undefined>\n): Element | Node {\n if (!parent) return parent;\n\n for (let i = 0; i < children.length; i++) {\n const child = children[i];\n if (child != null) {\n let nodeToAppend: Node;\n\n if (typeof child === \"string\") {\n const textNode = createTextNode(child);\n if (textNode) {\n nodeToAppend = textNode;\n } else {\n continue;\n }\n } else {\n nodeToAppend = child as Node;\n }\n\n safeAppendChild(parent, nodeToAppend);\n }\n }\n\n return parent;\n}\n\nexport function isNodeConnected(node: Node | null | undefined): boolean {\n if (!node) return false;\n return node.isConnected === true;\n}\n\n/**\n * Safely replaces an old node with a new node in the DOM.\n * Returns true on success, false on failure (and logs the error).\n */\nexport function replaceNodeSafely(oldNode: Node, newNode: Node): boolean {\n if (!oldNode?.parentNode) return false;\n try {\n oldNode.parentNode.replaceChild(newNode, oldNode);\n return true;\n } catch (error) {\n logError(\"Error replacing conditional node\", error);\n return false;\n }\n}\n","/**\n * Server-Side Rendering (SSR) utilities for Nuclo\n * Renders Nuclo components to HTML strings in Node.js environment\n */\n\nimport { escapeHtml, escapeText, camelToKebab } from '../utility/stringUtils';\nimport { createElement } from '../utility/dom';\n\ntype RenderableInput =\n | NodeModFn<ElementTagName>\n | Element\n | Node\n | null\n | undefined;\n\n/**\n * SVG attributes that are natively camelCase and must NOT be converted to kebab-case.\n * Most SVG attributes are already kebab-case (stroke-width, fill-rule, etc.), but a\n * handful are defined as camelCase in the SVG spec and must be preserved.\n */\nconst SVG_PRESERVE_CASE_ATTRS = new Set([\n 'viewBox', 'preserveAspectRatio', 'markerWidth', 'markerHeight',\n 'gradientTransform', 'patternTransform', 'clipPathUnits', 'gradientUnits',\n 'patternUnits', 'pathLength', 'refX', 'refY', 'stdDeviation',\n 'baseFrequency', 'numOctaves', 'kernelMatrix', 'tableValues',\n 'targetX', 'targetY', 'specularExponent', 'specularConstant',\n 'diffuseConstant', 'surfaceScale', 'xChannelSelector', 'yChannelSelector',\n 'edgeMode', 'stitchTiles', 'spreadMethod', 'patternContentUnits',\n 'markerUnits', 'startOffset', 'textLength', 'lengthAdjust',\n]);\n\n/**\n * HTML boolean attributes — presence means true, absence means false.\n * When the stored value is the string \"true\" or \"false\" (from setAttribute),\n * we must re-apply boolean semantics instead of outputting the raw string.\n */\nconst HTML_BOOLEAN_ATTRIBUTES = new Set([\n 'allowfullscreen', 'async', 'autofocus', 'autoplay', 'checked', 'controls',\n 'default', 'defer', 'disabled', 'formnovalidate', 'hidden', 'ismap', 'loop',\n 'multiple', 'muted', 'nomodule', 'novalidate', 'open', 'readonly', 'required',\n 'reversed', 'selected',\n]);\n\n/**\n * Serializes a DOM attribute value\n */\nfunction serializeAttribute(name: string, value: unknown): string {\n if (value === null || value === undefined || value === false) {\n return '';\n }\n\n if (value === true) {\n return ` ${name}`;\n }\n\n // Boolean attributes stored as strings via setAttribute() need special handling\n if (HTML_BOOLEAN_ATTRIBUTES.has(name)) {\n if (value === 'false') return '';\n if (value === 'true' || value === '' || value === name) return ` ${name}`;\n }\n\n if (name === 'style' && typeof value === 'object') {\n const styleStr = Object.entries(value)\n .filter(([, val]) => val != null && val !== '')\n .map(([key, val]) => `${camelToKebab(key)}: ${val};`)\n .join(' ');\n return styleStr ? ` style=\"${escapeHtml(styleStr)}\"` : '';\n }\n\n return ` ${name}=\"${escapeHtml(String(value))}\"`;\n}\n\n/**\n * Serializes DOM element attributes to HTML string\n */\nfunction serializeAttributes(element: Element): string {\n let result = '';\n\n // Handle polyfill elements with Map-based attributes\n if ('attributes' in element && element.attributes instanceof Map) {\n const el = element as any;\n\n // id — may live on the property rather than in the Map\n if (el.id && !element.attributes.has('id')) {\n result += serializeAttribute('id', el.id);\n }\n\n // class — kept on .className, mirrored to Map only when setAttribute is used\n if (el.className && !element.attributes.has('class')) {\n result += serializeAttribute('class', el.className);\n }\n\n // style — lives on the Proxy, not in the attributes Map.\n // cssText returns camelCase keys (\"backgroundColor: red\"), so convert them.\n if (!element.attributes.has('style') && el.style) {\n const rawCssText: string = el.style.cssText || '';\n if (rawCssText) {\n const kebabStyle = rawCssText\n .split(';')\n .map((decl: string) => decl.trim())\n .filter(Boolean)\n .map((decl: string) => {\n const colonIdx = decl.indexOf(':');\n if (colonIdx === -1) return decl;\n const key = decl.slice(0, colonIdx).trim();\n const val = decl.slice(colonIdx + 1).trim();\n // Skip declarations with empty values (e.g. reactive styles that resolved to undefined)\n if (!val) return '';\n return `${camelToKebab(key)}: ${val};`;\n })\n .filter(Boolean)\n .join(' ');\n if (kebabStyle) {\n result += ` style=\"${escapeHtml(kebabStyle)}\"`;\n }\n }\n }\n\n // All remaining attributes from the Map\n // Convert camelCase ARIA/HTML attribute names (e.g. ariaLabel → aria-label) that the\n // polyfill stores as-is because NucloElement lacks the browser property mappings.\n // SVG attributes that are natively camelCase (e.g. viewBox, preserveAspectRatio) must\n // NOT be converted — they are stored via setAttribute() as-is.\n for (const [name, value] of element.attributes) {\n const htmlName = SVG_PRESERVE_CASE_ATTRS.has(name) ? name : camelToKebab(name);\n result += serializeAttribute(htmlName, value);\n }\n return result;\n }\n\n // Handle browser elements with NamedNodeMap attributes\n if (element.attributes && element.attributes.length) {\n for (let i = 0; i < element.attributes.length; i++) {\n const attr = element.attributes[i];\n if (attr && attr.name) {\n result += serializeAttribute(attr.name, attr.value);\n }\n }\n }\n\n return result;\n}\n\n/**\n * Self-closing HTML tags that don't have closing tags\n */\nconst VOID_ELEMENTS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'param', 'source', 'track', 'wbr'\n]);\n\n/**\n * Get child nodes from a node (handles both browser and polyfill elements).\n *\n * NucloElement stores ALL children (elements, text, comments) in a plain Array\n * called `children` — it is the authoritative list. DocumentFragments in the\n * polyfill also have a `children` array, but it only holds Element children;\n * their full set of nodes lives in `childNodes`. We therefore only prefer\n * `children` when the node is an actual element (has `tagName`).\n */\nfunction getChildNodes(node: Node): ArrayLike<Node> {\n // NucloElement: tagName exists and children is a plain Array containing every node type\n if ('tagName' in node && 'children' in node) {\n const children = (node as any).children;\n if (Array.isArray(children)) {\n return children as ArrayLike<Node>;\n }\n }\n // DocumentFragments and browser elements: use childNodes\n if ('childNodes' in node) {\n const childNodes = (node as any).childNodes;\n if (childNodes && (Array.isArray(childNodes) || childNodes.length !== undefined)) {\n return childNodes;\n }\n }\n return [] as ArrayLike<Node>;\n}\n\n/**\n * Serializes a DOM node to HTML string\n */\nfunction serializeNode(node: Node): string {\n // Text node — only & < > need escaping; quotes are safe in text content\n if (node.nodeType === 3) { // Node.TEXT_NODE\n return escapeText(node.textContent || '');\n }\n\n // Comment node\n if (node.nodeType === 8) { // Node.COMMENT_NODE\n return `<!--${node.textContent || ''}-->`;\n }\n\n // Element node\n if (node.nodeType === 1) { // Node.ELEMENT_NODE\n const element = node as Element;\n const tagName = element.tagName.toLowerCase();\n const attributes = serializeAttributes(element);\n\n // Self-closing tags\n if (VOID_ELEMENTS.has(tagName)) {\n return `<${tagName}${attributes} />`;\n }\n\n // Regular elements with children\n let childrenHtml = '';\n const childNodes = getChildNodes(element);\n if (childNodes && childNodes.length > 0) {\n for (let i = 0; i < childNodes.length; i++) {\n const child = childNodes[i];\n if (child) {\n childrenHtml += serializeNode(child);\n }\n }\n } else {\n // Fallback: textContent set directly on the element (e.g. el.textContent = \"...\")\n const tc = (node as any).textContent;\n if (typeof tc === 'string' && tc) {\n childrenHtml = escapeText(tc);\n }\n }\n\n return `<${tagName}${attributes}>${childrenHtml}</${tagName}>`;\n }\n\n // Document fragment\n if (node.nodeType === 11) { // Node.DOCUMENT_FRAGMENT_NODE\n let result = '';\n const childNodes = getChildNodes(node);\n if (childNodes && childNodes.length > 0) {\n for (let i = 0; i < childNodes.length; i++) {\n const child = childNodes[i];\n if (child) {\n result += serializeNode(child);\n }\n }\n }\n return result;\n }\n\n return '';\n}\n\n/**\n * Renders a Nuclo component to an HTML string for server-side rendering\n *\n * @param input - A Nuclo component function, DOM element, or node\n * @returns HTML string representation of the component\n *\n * @example\n * ```ts\n * import { renderToString } from 'nuclo/ssr';\n * import { div } from 'nuclo';\n *\n * const html = renderToString(\n * div(\"Hello, World!\")\n * );\n * // Returns: '<div>Hello, World!</div>'\n * ```\n */\nexport function renderToString(input: RenderableInput): string {\n if (!input) return '';\n\n if (typeof input === 'function') {\n try {\n const container = createElement('div');\n if (!container) throw new Error('Document is not available. Make sure polyfills are loaded.');\n const element = input(container as ExpandedElement<ElementTagName>, 0);\n return element && typeof element === 'object' && 'nodeType' in element ? serializeNode(element as Node) : '';\n } catch (error) {\n console.error('Error rendering component to string:', error);\n return '';\n }\n }\n\n if ('nodeType' in input) {\n return serializeNode(input as Node);\n }\n\n return '';\n}\n\n/**\n * Renders multiple Nuclo components to HTML strings\n *\n * @param inputs - Array of Nuclo components\n * @returns Array of HTML strings\n */\nexport function renderManyToString(inputs: RenderableInput[]): string[] {\n return inputs.map(input => renderToString(input));\n}\n\n/**\n * Renders a Nuclo component and wraps it in a container element\n *\n * @param input - A Nuclo component\n * @param containerTag - The tag name for the container (default: 'div')\n * @param containerAttrs - Attributes for the container element\n * @returns HTML string with container wrapper\n */\nexport function renderToStringWithContainer(\n input: RenderableInput,\n containerTag: string = 'div',\n containerAttrs: Record<string, string> = {}\n): string {\n const content = renderToString(input);\n const attrs = Object.entries(containerAttrs)\n .map(([key, value]) => serializeAttribute(key, value))\n .join('');\n\n return `<${containerTag}${attrs}>${content}</${containerTag}>`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"nuclo.ssr.cjs","names":[],"sources":["../../src/utility/stringUtils.ts","../../src/utility/dom.ts","../../src/ssr/renderToString.ts","../../src/style/cssGenerator.ts"],"sourcesContent":["/**\n * String utility functions\n */\n\n/**\n * Converts camelCase to kebab-case (optimized for performance)\n * Uses direct character code manipulation for maximum speed\n * @example camelToKebab('backgroundColor') => 'background-color'\n */\nexport function camelToKebab(str: string): string {\n let result = '';\n for (let i = 0; i < str.length; i++) {\n const code = str.charCodeAt(i);\n // A-Z is 65-90, a-z is 97-122 (difference of 32)\n if (code >= 65 && code <= 90) {\n if (i > 0) result += '-';\n result += String.fromCharCode(code + 32);\n } else {\n result += str[i];\n }\n }\n return result;\n}\n\n/**\n * Escapes HTML special characters in attribute values (includes \" and ')\n */\nexport function escapeHtml(text: string): string {\n const map: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": '''\n };\n return text.replace(/[&<>\"']/g, (char) => map[char]);\n}\n\n/**\n * Escapes HTML special characters in text node content.\n * Only &, < and > need escaping — quotes are safe inside text nodes.\n */\nexport function escapeText(text: string): string {\n return text.replace(/[&<>]/g, (char) => (char === '&' ? '&' : char === '<' ? '<' : '>'));\n}\n","import { logError } from \"./errorHandler\";\nimport { removeAllListeners } from \"./on\";\nimport { cleanupReactiveTextNode, cleanupReactiveElement } from \"../core/reactiveCleanup\";\nimport { unregisterConditionalNode } from \"./conditionalInfo\";\n\nexport const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';\n\n/**\n * Creates an HTML element.\n * Wrapper for document.createElement with type safety.\n */\nexport function createElement<K extends keyof HTMLElementTagNameMap>(\n tagName: K\n): HTMLElementTagNameMap[K] | null;\nexport function createElement(tagName: string): ExpandedElement | null;\nexport function createElement(tagName: string): ExpandedElement | null {\n return globalThis.document ? document.createElement(tagName) as ExpandedElement : null;\n}\n\n/**\n * Creates an element in the given namespace (typically for SVG elements).\n * Wrapper for document.createElementNS with type safety.\n */\nexport function createElementNS(\n namespace: string,\n tagName: string\n): ExpandedElement | null {\n return globalThis.document ? document.createElementNS(namespace, tagName) as ExpandedElement : null;\n}\n\n/**\n * Creates a text node with the given content.\n * Wrapper for document.createTextNode.\n */\nexport function createTextNode(text: string): Text | null {\n return globalThis.document ? document.createTextNode(text) : null;\n}\n\n/**\n * Creates a document fragment.\n * Wrapper for document.createDocumentFragment.\n */\nexport function createDocumentFragment(): DocumentFragment | null {\n return globalThis.document ? document.createDocumentFragment() : null;\n}\n\nfunction safeAppendChild(parent: Element | Node, child: Node): boolean {\n try {\n parent.appendChild(child);\n return true;\n } catch (error) {\n logError('Failed to append child node', error);\n return false;\n }\n}\n\n/**\n * Recursively removes all event listeners and reactive subscriptions from a node and its descendants\n * to prevent memory leaks when elements are removed from the DOM.\n */\nfunction cleanupEventListeners(node: Node): void {\n // Clean up the node itself based on its type\n if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as HTMLElement;\n // Remove all event listeners\n removeAllListeners(element);\n // Remove reactive attribute resolvers\n cleanupReactiveElement(element);\n // Remove conditional info\n unregisterConditionalNode(element);\n } else if (node.nodeType === Node.TEXT_NODE) {\n // Remove reactive text node info\n cleanupReactiveTextNode(node as Text);\n } else if (node.nodeType === Node.COMMENT_NODE) {\n // Remove conditional info from comment nodes (used by when/list)\n unregisterConditionalNode(node);\n }\n \n // Recursively clean up all child nodes\n if (node.childNodes && node.childNodes.length > 0) {\n for (let i = 0; i < node.childNodes.length; i++) {\n cleanupEventListeners(node.childNodes[i]);\n }\n }\n}\n\nexport function safeRemoveChild(child: Node): boolean {\n if (!child?.parentNode) return false;\n try {\n // Clean up all event listeners before removing the element\n cleanupEventListeners(child);\n child.parentNode.removeChild(child);\n return true;\n } catch (error) {\n logError('Failed to remove child node', error);\n return false;\n }\n}\n\nfunction safeInsertBefore(parent: Node, newNode: Node, referenceNode: Node | null): boolean {\n try {\n parent.insertBefore(newNode, referenceNode);\n return true;\n } catch (error) {\n logError('Failed to insert node before reference', error);\n return false;\n }\n}\n\nexport function createComment(text: string): Comment | null {\n return globalThis.document ? globalThis.document.createComment(text) : null;\n}\n\nexport function createConditionalComment(tagName: string, suffix = \"hidden\"): Comment | null {\n return globalThis.document ? globalThis.document.createComment(`conditional-${tagName}-${suffix}`) : null;\n}\n\nexport function createMarkerPair(prefix: string, id: number | string): { start: Comment; end: Comment } {\n const endComment = createComment(`${prefix}-end`);\n if (!endComment) throw new Error(\"Failed to create comment: document not available\");\n const startComment = createComment(`${prefix}-start-${id}`);\n if (!startComment) throw new Error(\"Failed to create comment: document not available\");\n return { start: startComment, end: endComment };\n}\n\nexport function clearBetweenMarkers(startMarker: Comment, endMarker: Comment): void {\n let current = startMarker.nextSibling;\n while (current && current !== endMarker) {\n const next = current.nextSibling;\n safeRemoveChild(current);\n current = next;\n }\n}\n\nexport function insertNodesBefore(nodes: Node[], referenceNode: Node): void {\n const parent = referenceNode.parentNode;\n if (parent) {\n for (let i = 0; i < nodes.length; i++) {\n safeInsertBefore(parent, nodes[i], referenceNode);\n }\n }\n}\n\nexport function appendChildren(\n parent: Element | Node,\n ...children: Array<Element | Node | string | null | undefined>\n): Element | Node {\n if (!parent) return parent;\n\n for (let i = 0; i < children.length; i++) {\n const child = children[i];\n if (child != null) {\n let nodeToAppend: Node;\n\n if (typeof child === \"string\") {\n const textNode = createTextNode(child);\n if (textNode) {\n nodeToAppend = textNode;\n } else {\n continue;\n }\n } else {\n nodeToAppend = child as Node;\n }\n\n safeAppendChild(parent, nodeToAppend);\n }\n }\n\n return parent;\n}\n\nexport function isNodeConnected(node: Node | null | undefined): boolean {\n if (!node) return false;\n return node.isConnected === true;\n}\n\n/**\n * Safely replaces an old node with a new node in the DOM.\n * Returns true on success, false on failure (and logs the error).\n */\nexport function replaceNodeSafely(oldNode: Node, newNode: Node): boolean {\n if (!oldNode?.parentNode) return false;\n try {\n oldNode.parentNode.replaceChild(newNode, oldNode);\n return true;\n } catch (error) {\n logError(\"Error replacing conditional node\", error);\n return false;\n }\n}\n","/**\n * Server-Side Rendering (SSR) utilities for Nuclo\n * Renders Nuclo components to HTML strings in Node.js environment\n */\n\nimport { escapeHtml, escapeText, camelToKebab } from '../utility/stringUtils';\nimport { createElement } from '../utility/dom';\n\ntype RenderableInput =\n | NodeModFn<ElementTagName>\n | Element\n | Node\n | null\n | undefined;\n\n/**\n * SVG attributes that are natively camelCase and must NOT be converted to kebab-case.\n * Most SVG attributes are already kebab-case (stroke-width, fill-rule, etc.), but a\n * handful are defined as camelCase in the SVG spec and must be preserved.\n */\nconst SVG_PRESERVE_CASE_ATTRS = new Set([\n 'viewBox', 'preserveAspectRatio', 'markerWidth', 'markerHeight',\n 'gradientTransform', 'patternTransform', 'clipPathUnits', 'gradientUnits',\n 'patternUnits', 'pathLength', 'refX', 'refY', 'stdDeviation',\n 'baseFrequency', 'numOctaves', 'kernelMatrix', 'tableValues',\n 'targetX', 'targetY', 'specularExponent', 'specularConstant',\n 'diffuseConstant', 'surfaceScale', 'xChannelSelector', 'yChannelSelector',\n 'edgeMode', 'stitchTiles', 'spreadMethod', 'patternContentUnits',\n 'markerUnits', 'startOffset', 'textLength', 'lengthAdjust',\n]);\n\n/**\n * HTML boolean attributes — presence means true, absence means false.\n * When the stored value is the string \"true\" or \"false\" (from setAttribute),\n * we must re-apply boolean semantics instead of outputting the raw string.\n */\nconst HTML_BOOLEAN_ATTRIBUTES = new Set([\n 'allowfullscreen', 'async', 'autofocus', 'autoplay', 'checked', 'controls',\n 'default', 'defer', 'disabled', 'formnovalidate', 'hidden', 'ismap', 'loop',\n 'multiple', 'muted', 'nomodule', 'novalidate', 'open', 'readonly', 'required',\n 'reversed', 'selected',\n]);\n\n/**\n * Serializes a DOM attribute value\n */\nfunction serializeAttribute(name: string, value: unknown): string {\n if (value === null || value === undefined || value === false) {\n return '';\n }\n\n if (value === true) {\n return ` ${name}`;\n }\n\n // Boolean attributes stored as strings via setAttribute() need special handling\n if (HTML_BOOLEAN_ATTRIBUTES.has(name)) {\n if (value === 'false') return '';\n if (value === 'true' || value === '' || value === name) return ` ${name}`;\n }\n\n if (name === 'style' && typeof value === 'object') {\n const styleStr = Object.entries(value)\n .filter(([, val]) => val != null && val !== '')\n .map(([key, val]) => `${camelToKebab(key)}: ${val};`)\n .join(' ');\n return styleStr ? ` style=\"${escapeHtml(styleStr)}\"` : '';\n }\n\n return ` ${name}=\"${escapeHtml(String(value))}\"`;\n}\n\n/**\n * Serializes DOM element attributes to HTML string\n */\nfunction serializeAttributes(element: Element): string {\n let result = '';\n\n // Handle polyfill elements with Map-based attributes\n if ('attributes' in element && element.attributes instanceof Map) {\n const el = element as any;\n\n // id — may live on the property rather than in the Map\n if (el.id && !element.attributes.has('id')) {\n result += serializeAttribute('id', el.id);\n }\n\n // class — kept on .className, mirrored to Map only when setAttribute is used\n if (el.className && !element.attributes.has('class')) {\n result += serializeAttribute('class', el.className);\n }\n\n // style — lives on the Proxy, not in the attributes Map.\n // cssText returns camelCase keys (\"backgroundColor: red\"), so convert them.\n if (!element.attributes.has('style') && el.style) {\n const rawCssText: string = el.style.cssText || '';\n if (rawCssText) {\n const kebabStyle = rawCssText\n .split(';')\n .map((decl: string) => decl.trim())\n .filter(Boolean)\n .map((decl: string) => {\n const colonIdx = decl.indexOf(':');\n if (colonIdx === -1) return decl;\n const key = decl.slice(0, colonIdx).trim();\n const val = decl.slice(colonIdx + 1).trim();\n // Skip declarations with empty values (e.g. reactive styles that resolved to undefined)\n if (!val) return '';\n return `${camelToKebab(key)}: ${val};`;\n })\n .filter(Boolean)\n .join(' ');\n if (kebabStyle) {\n result += ` style=\"${escapeHtml(kebabStyle)}\"`;\n }\n }\n }\n\n // All remaining attributes from the Map\n // Convert camelCase ARIA/HTML attribute names (e.g. ariaLabel → aria-label) that the\n // polyfill stores as-is because NucloElement lacks the browser property mappings.\n // SVG attributes that are natively camelCase (e.g. viewBox, preserveAspectRatio) must\n // NOT be converted — they are stored via setAttribute() as-is.\n for (const [name, value] of element.attributes) {\n const htmlName = SVG_PRESERVE_CASE_ATTRS.has(name) ? name : camelToKebab(name);\n result += serializeAttribute(htmlName, value);\n }\n return result;\n }\n\n // Handle browser elements with NamedNodeMap attributes\n if (element.attributes && element.attributes.length) {\n for (let i = 0; i < element.attributes.length; i++) {\n const attr = element.attributes[i];\n if (attr && attr.name) {\n result += serializeAttribute(attr.name, attr.value);\n }\n }\n }\n\n return result;\n}\n\n/**\n * Self-closing HTML tags that don't have closing tags\n */\nconst VOID_ELEMENTS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'param', 'source', 'track', 'wbr'\n]);\n\n/**\n * Get child nodes from a node (handles both browser and polyfill elements).\n *\n * NucloElement stores ALL children (elements, text, comments) in a plain Array\n * called `children` — it is the authoritative list. DocumentFragments in the\n * polyfill also have a `children` array, but it only holds Element children;\n * their full set of nodes lives in `childNodes`. We therefore only prefer\n * `children` when the node is an actual element (has `tagName`).\n */\nfunction getChildNodes(node: Node): ArrayLike<Node> {\n // NucloElement: tagName exists and children is a plain Array containing every node type\n if ('tagName' in node && 'children' in node) {\n const children = (node as any).children;\n if (Array.isArray(children)) {\n return children as ArrayLike<Node>;\n }\n }\n // DocumentFragments and browser elements: use childNodes\n if ('childNodes' in node) {\n const childNodes = (node as any).childNodes;\n if (childNodes && (Array.isArray(childNodes) || childNodes.length !== undefined)) {\n return childNodes;\n }\n }\n return [] as ArrayLike<Node>;\n}\n\n/**\n * Serializes a DOM node to HTML string\n */\nfunction serializeNode(node: Node): string {\n // Text node — only & < > need escaping; quotes are safe in text content\n if (node.nodeType === 3) { // Node.TEXT_NODE\n return escapeText(node.textContent || '');\n }\n\n // Comment node\n if (node.nodeType === 8) { // Node.COMMENT_NODE\n return `<!--${node.textContent || ''}-->`;\n }\n\n // Element node\n if (node.nodeType === 1) { // Node.ELEMENT_NODE\n const element = node as Element;\n const tagName = element.tagName.toLowerCase();\n const attributes = serializeAttributes(element);\n\n // Self-closing tags\n if (VOID_ELEMENTS.has(tagName)) {\n return `<${tagName}${attributes} />`;\n }\n\n // Regular elements with children\n let childrenHtml = '';\n const childNodes = getChildNodes(element);\n if (childNodes && childNodes.length > 0) {\n for (let i = 0; i < childNodes.length; i++) {\n const child = childNodes[i];\n if (child) {\n childrenHtml += serializeNode(child);\n }\n }\n } else {\n // Fallback: textContent set directly on the element (e.g. el.textContent = \"...\")\n const tc = (node as any).textContent;\n if (typeof tc === 'string' && tc) {\n childrenHtml = escapeText(tc);\n }\n }\n\n return `<${tagName}${attributes}>${childrenHtml}</${tagName}>`;\n }\n\n // Document fragment\n if (node.nodeType === 11) { // Node.DOCUMENT_FRAGMENT_NODE\n let result = '';\n const childNodes = getChildNodes(node);\n if (childNodes && childNodes.length > 0) {\n for (let i = 0; i < childNodes.length; i++) {\n const child = childNodes[i];\n if (child) {\n result += serializeNode(child);\n }\n }\n }\n return result;\n }\n\n return '';\n}\n\n/**\n * Renders a Nuclo component to an HTML string for server-side rendering\n *\n * @param input - A Nuclo component function, DOM element, or node\n * @returns HTML string representation of the component\n *\n * @example\n * ```ts\n * import { renderToString } from 'nuclo/ssr';\n * import { div } from 'nuclo';\n *\n * const html = renderToString(\n * div(\"Hello, World!\")\n * );\n * // Returns: '<div>Hello, World!</div>'\n * ```\n */\nexport function renderToString(input: RenderableInput): string {\n if (!input) return '';\n\n if (typeof input === 'function') {\n try {\n const container = createElement('div');\n if (!container) throw new Error('Document is not available. Make sure polyfills are loaded.');\n const element = input(container as ExpandedElement<ElementTagName>, 0);\n return element && typeof element === 'object' && 'nodeType' in element ? serializeNode(element as Node) : '';\n } catch (error) {\n console.error('Error rendering component to string:', error);\n return '';\n }\n }\n\n if ('nodeType' in input) {\n return serializeNode(input as Node);\n }\n\n return '';\n}\n\n/**\n * Renders multiple Nuclo components to HTML strings\n *\n * @param inputs - Array of Nuclo components\n * @returns Array of HTML strings\n */\nexport function renderManyToString(inputs: RenderableInput[]): string[] {\n return inputs.map(input => renderToString(input));\n}\n\n/**\n * Renders a Nuclo component and wraps it in a container element\n *\n * @param input - A Nuclo component\n * @param containerTag - The tag name for the container (default: 'div')\n * @param containerAttrs - Attributes for the container element\n * @returns HTML string with container wrapper\n */\nexport function renderToStringWithContainer(\n input: RenderableInput,\n containerTag: string = 'div',\n containerAttrs: Record<string, string> = {}\n): string {\n const content = renderToString(input);\n const attrs = Object.entries(containerAttrs)\n .map(([key, value]) => serializeAttribute(key, value))\n .join('');\n\n return `<${containerTag}${attrs}>${content}</${containerTag}>`;\n}\n","import { createElement } from \"../utility/dom\";\nimport { isBrowser } from \"../utility/environment\";\n\ntype AtRuleType = 'media' | 'container' | 'supports' | 'style' | 'pseudo';\n\n// SSR collector — a single function reference installed once at server startup.\n// Keeping this as a plain function slot (no global Set, no ALS) means the nuclo\n// package stays browser-safe and free of Node-specific imports. The server is\n// responsible for wiring in whatever collection strategy it wants (e.g. an\n// AsyncLocalStorage-backed dispatcher). null = no-op (browser default).\ntype SSRCollector = (rule: string) => void;\nlet _ssrCollector: SSRCollector | null = null;\n\n/**\n * Install a CSS rule collector for SSR. Call once at server startup with a\n * function that receives every rule string emitted by cn() / createStyleQueries.\n * Pass null to remove the collector (not normally needed).\n */\nexport function setSSRCollector(fn: SSRCollector | null): void {\n\t_ssrCollector = fn;\n}\n\nfunction isAtRule(rule: CSSRule): boolean {\n\treturn rule instanceof CSSMediaRule ||\n\t\trule instanceof CSSContainerRule ||\n\t\trule instanceof CSSSupportsRule;\n}\n\nfunction matchesAtRule(rule: CSSRule, atRuleType: AtRuleType, condition: string): boolean {\n\tif (atRuleType === 'media' && rule instanceof CSSMediaRule) {\n\t\treturn rule.media.mediaText === condition;\n\t}\n\tif (atRuleType === 'container' && rule instanceof CSSContainerRule) {\n\t\treturn rule.conditionText === condition;\n\t}\n\tif (atRuleType === 'supports' && rule instanceof CSSSupportsRule) {\n\t\treturn rule.conditionText === condition;\n\t}\n\treturn false;\n}\n\nfunction findStyleRule(rules: CSSRuleList, selector: string): CSSStyleRule | null {\n\tfor (let i = 0; i < rules.length; i++) {\n\t\tconst rule = rules[i];\n\t\tif (rule instanceof CSSStyleRule && rule.selectorText === selector) {\n\t\t\treturn rule;\n\t\t}\n\t}\n\treturn null;\n}\n\nfunction updateRuleStyles(rule: CSSStyleRule, styles: Record<string, string>): void {\n\trule.style.cssText = '';\n\tfor (const [property, value] of Object.entries(styles)) {\n\t\trule.style.setProperty(property, value);\n\t}\n}\n\nfunction getStyleSheet(): HTMLStyleElement | null {\n\tif (!isBrowser) return null;\n\tlet el = document.querySelector(\"#nuclo-styles\") as HTMLStyleElement;\n\tif (!el) {\n\t\tel = createElement(\"style\") as HTMLStyleElement;\n\t\tel.id = \"nuclo-styles\";\n\t\tdocument.head.appendChild(el);\n\t}\n\treturn el;\n}\n\nfunction buildRulesString(styles: Record<string, string>): string {\n\tconst entries = Object.entries(styles);\n\tlet result = '';\n\tfor (let i = 0; i < entries.length; i++) {\n\t\tif (i > 0) result += '; ';\n\t\tresult += `${entries[i][0]}: ${entries[i][1]}`;\n\t}\n\treturn result;\n}\n\nexport function classExistsInDOM(className: string, condition?: string, atRuleType: AtRuleType = 'media', pseudoClass?: string): boolean {\n\tif (!isBrowser) return false;\n\tconst styleSheet = document.querySelector(\"#nuclo-styles\") as HTMLStyleElement;\n\tif (!styleSheet?.sheet) return false;\n\n\tconst rules = styleSheet.sheet.cssRules;\n\n\tif (pseudoClass) {\n\t\treturn findStyleRule(rules, `.${className}${pseudoClass}`) !== null;\n\t}\n\n\tif (condition) {\n\t\tfor (let i = 0; i < rules.length; i++) {\n\t\t\tif (matchesAtRule(rules[i], atRuleType, condition)) {\n\t\t\t\treturn findStyleRule((rules[i] as CSSGroupingRule).cssRules, `.${className}`) !== null;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\treturn findStyleRule(rules, `.${className}`) !== null;\n}\n\nexport function createCSSClassWithStyles(\n\tclassName: string,\n\tstyles: Record<string, string>,\n\tcondition?: string,\n\tatRuleType: AtRuleType = 'media',\n\tpseudoClass?: string\n): void {\n\tif (!isBrowser) {\n\t\tconst rulesStr = buildRulesString(styles);\n\t\tlet rule: string;\n\t\tif (pseudoClass) {\n\t\t\trule = `.${className}${pseudoClass} { ${rulesStr} }`;\n\t\t} else if (condition) {\n\t\t\tconst prefix = atRuleType === 'container' ? '@container'\n\t\t\t\t: atRuleType === 'supports' ? '@supports'\n\t\t\t\t: '@media';\n\t\t\trule = `${prefix} ${condition} { .${className} { ${rulesStr} } }`;\n\t\t} else {\n\t\t\trule = `.${className} { ${rulesStr} }`;\n\t\t}\n\t\t_ssrCollector?.(rule);\n\t\treturn;\n\t}\n\n\tconst styleSheet = getStyleSheet();\n\tif (!styleSheet) return;\n\n\tconst sheet = styleSheet.sheet;\n\tif (!sheet) return;\n\n\tconst rulesStr = buildRulesString(styles);\n\n\tif (pseudoClass) {\n\t\tconst selector = `.${className}${pseudoClass}`;\n\t\tconst allRules = sheet.cssRules;\n\t\tconst rulesLength = allRules.length;\n\n\t\tlet existingRule: CSSStyleRule | null = null;\n\t\tlet insertIndex = rulesLength;\n\n\t\tfor (let i = 0; i < rulesLength; i++) {\n\t\t\tconst rule = allRules[i];\n\t\t\tif (rule instanceof CSSStyleRule) {\n\t\t\t\tif (rule.selectorText === selector) {\n\t\t\t\t\texistingRule = rule;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (!rule.selectorText.includes(':')) {\n\t\t\t\t\tinsertIndex = i + 1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (existingRule) {\n\t\t\tupdateRuleStyles(existingRule, styles);\n\t\t} else {\n\t\t\tsheet.insertRule(`${selector} { ${rulesStr} }`, insertIndex);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (condition) {\n\t\tconst existingRules = sheet.cssRules;\n\t\tconst rulesLength = existingRules.length;\n\t\tlet groupingRule: CSSGroupingRule | null = null;\n\n\t\tfor (let i = 0; i < rulesLength; i++) {\n\t\t\tif (matchesAtRule(existingRules[i], atRuleType, condition)) {\n\t\t\t\tgroupingRule = existingRules[i] as CSSGroupingRule;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (!groupingRule) {\n\t\t\tlet insertIndex = rulesLength;\n\t\t\tfor (let i = rulesLength - 1; i >= 0; i--) {\n\t\t\t\tif (isAtRule(existingRules[i]) || existingRules[i] instanceof CSSStyleRule) {\n\t\t\t\t\tinsertIndex = i + 1;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst atRulePrefix = atRuleType === 'media' ? '@media' :\n\t\t\t\tatRuleType === 'container' ? '@container' :\n\t\t\t\tatRuleType === 'supports' ? '@supports' :\n\t\t\t\t'@media';\n\n\t\t\tsheet.insertRule(`${atRulePrefix} ${condition} {}`, insertIndex);\n\t\t\tgroupingRule = sheet.cssRules[insertIndex] as CSSGroupingRule;\n\t\t}\n\n\t\tconst existingRule = findStyleRule(groupingRule.cssRules, `.${className}`);\n\t\tif (existingRule) {\n\t\t\tupdateRuleStyles(existingRule, styles);\n\t\t} else {\n\t\t\tgroupingRule.insertRule(`.${className} { ${rulesStr} }`, groupingRule.cssRules.length);\n\t\t}\n\t} else {\n\t\tlet existingRule: CSSStyleRule | null = null;\n\t\tlet insertIndex = 0;\n\n\t\tconst allRules = sheet.cssRules;\n\t\tconst rulesLength = allRules.length;\n\t\tfor (let i = 0; i < rulesLength; i++) {\n\t\t\tconst rule = allRules[i];\n\t\t\tif (rule instanceof CSSStyleRule && rule.selectorText === `.${className}`) {\n\t\t\t\texistingRule = rule;\n\t\t\t\tinsertIndex = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (!isAtRule(rule)) {\n\t\t\t\tinsertIndex = i + 1;\n\t\t\t}\n\t\t}\n\n\t\tif (existingRule) {\n\t\t\tupdateRuleStyles(existingRule, styles);\n\t\t} else {\n\t\t\tsheet.insertRule(`.${className} { ${rulesStr} }`, insertIndex);\n\t\t}\n\t}\n}\n\nexport function createCSSClass(className: string, styles: Record<string, string>): void {\n\tcreateCSSClassWithStyles(className, styles);\n}\n"],"mappings":"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"}
|
package/dist/ssr/nuclo.ssr.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
function e(e){let t=``;for(let n=0;n<e.length;n++){let r=e.charCodeAt(n);r>=65&&r<=90?(n>0&&(t+=`-`),t+=String.fromCharCode(r+32)):t+=e[n]}return t}function t(e){let t={"&":`&`,"<":`<`,">":`>`,'"':`"`,"'":`'`};return e.replace(/[&<>"']/g,e=>t[e])}function n(e){return e.replace(/[&<>]/g,e=>e===`&`?`&`:e===`<`?`<`:`>`)}function r(e){return globalThis.document?document.createElement(e):null}const i=new Set(`viewBox.preserveAspectRatio.markerWidth.markerHeight.gradientTransform.patternTransform.clipPathUnits.gradientUnits.patternUnits.pathLength.refX.refY.stdDeviation.baseFrequency.numOctaves.kernelMatrix.tableValues.targetX.targetY.specularExponent.specularConstant.diffuseConstant.surfaceScale.xChannelSelector.yChannelSelector.edgeMode.stitchTiles.spreadMethod.patternContentUnits.markerUnits.startOffset.textLength.lengthAdjust`.split(`.`)),a=new Set([`allowfullscreen`,`async`,`autofocus`,`autoplay`,`checked`,`controls`,`default`,`defer`,`disabled`,`formnovalidate`,`hidden`,`ismap`,`loop`,`multiple`,`muted`,`nomodule`,`novalidate`,`open`,`readonly`,`required`,`reversed`,`selected`]);function o(n,r){if(r==null||r===!1)return``;if(r===!0)return` ${n}`;if(a.has(n)){if(r===`false`)return``;if(r===`true`||r===``||r===n)return` ${n}`}if(n===`style`&&typeof r==`object`){let n=Object.entries(r).filter(([,e])=>e!=null&&e!==``).map(([t,n])=>`${e(t)}: ${n};`).join(` `);return n?` style="${t(n)}"`:``}return` ${n}="${t(String(r))}"`}function s(n){let r=``;if(`attributes`in n&&n.attributes instanceof Map){let a=n;if(a.id&&!n.attributes.has(`id`)&&(r+=o(`id`,a.id)),a.className&&!n.attributes.has(`class`)&&(r+=o(`class`,a.className)),!n.attributes.has(`style`)&&a.style){let n=a.style.cssText||``;if(n){let i=n.split(`;`).map(e=>e.trim()).filter(Boolean).map(t=>{let n=t.indexOf(`:`);if(n===-1)return t;let r=t.slice(0,n).trim(),i=t.slice(n+1).trim();return i?`${e(r)}: ${i};`:``}).filter(Boolean).join(` `);i&&(r+=` style="${t(i)}"`)}}for(let[t,a]of n.attributes){let n=i.has(t)?t:e(t);r+=o(n,a)}return r}if(n.attributes&&n.attributes.length)for(let e=0;e<n.attributes.length;e++){let t=n.attributes[e];t&&t.name&&(r+=o(t.name,t.value))}return r}const c=new Set([`area`,`base`,`br`,`col`,`embed`,`hr`,`img`,`input`,`link`,`meta`,`param`,`source`,`track`,`wbr`]);function l(e){if(`tagName`in e&&`children`in e){let t=e.children;if(Array.isArray(t))return t}if(`childNodes`in e){let t=e.childNodes;if(t&&(Array.isArray(t)||t.length!==void 0))return t}return[]}function u(e){if(e.nodeType===3)return n(e.textContent||``);if(e.nodeType===8)return`<!--${e.textContent||``}-->`;if(e.nodeType===1){let t=e,r=t.tagName.toLowerCase(),i=s(t);if(c.has(r))return`<${r}${i} />`;let a=``,o=l(t);if(o&&o.length>0)for(let e=0;e<o.length;e++){let t=o[e];t&&(a+=u(t))}else{let t=e.textContent;typeof t==`string`&&t&&(a=n(t))}return`<${r}${i}>${a}</${r}>`}if(e.nodeType===11){let t=``,n=l(e);if(n&&n.length>0)for(let e=0;e<n.length;e++){let r=n[e];r&&(t+=u(r))}return t}return``}function d(e){if(!e)return``;if(typeof e==`function`)try{let t=r(`div`);if(!t)throw Error(`Document is not available. Make sure polyfills are loaded.`);let n=e(t,0);return n&&typeof n==`object`&&`nodeType`in n?u(n):``}catch(e){return console.error(`Error rendering component to string:`,e),``}return`nodeType`in e?u(e):``}function f(e){return e.map(e=>d(e))}function p(e,t=`div`,n={}){let r=d(e);return`<${t}${Object.entries(n).map(([e,t])=>o(e,t)).join(``)}>${r}</${t}>`}export{f as renderManyToString,d as renderToString,p as renderToStringWithContainer};
|
|
1
|
+
function e(e){let t=``;for(let n=0;n<e.length;n++){let r=e.charCodeAt(n);r>=65&&r<=90?(n>0&&(t+=`-`),t+=String.fromCharCode(r+32)):t+=e[n]}return t}function t(e){let t={"&":`&`,"<":`<`,">":`>`,'"':`"`,"'":`'`};return e.replace(/[&<>"']/g,e=>t[e])}function n(e){return e.replace(/[&<>]/g,e=>e===`&`?`&`:e===`<`?`<`:`>`)}function r(e){return globalThis.document?document.createElement(e):null}const i=new Set(`viewBox.preserveAspectRatio.markerWidth.markerHeight.gradientTransform.patternTransform.clipPathUnits.gradientUnits.patternUnits.pathLength.refX.refY.stdDeviation.baseFrequency.numOctaves.kernelMatrix.tableValues.targetX.targetY.specularExponent.specularConstant.diffuseConstant.surfaceScale.xChannelSelector.yChannelSelector.edgeMode.stitchTiles.spreadMethod.patternContentUnits.markerUnits.startOffset.textLength.lengthAdjust`.split(`.`)),a=new Set([`allowfullscreen`,`async`,`autofocus`,`autoplay`,`checked`,`controls`,`default`,`defer`,`disabled`,`formnovalidate`,`hidden`,`ismap`,`loop`,`multiple`,`muted`,`nomodule`,`novalidate`,`open`,`readonly`,`required`,`reversed`,`selected`]);function o(n,r){if(r==null||r===!1)return``;if(r===!0)return` ${n}`;if(a.has(n)){if(r===`false`)return``;if(r===`true`||r===``||r===n)return` ${n}`}if(n===`style`&&typeof r==`object`){let n=Object.entries(r).filter(([,e])=>e!=null&&e!==``).map(([t,n])=>`${e(t)}: ${n};`).join(` `);return n?` style="${t(n)}"`:``}return` ${n}="${t(String(r))}"`}function s(n){let r=``;if(`attributes`in n&&n.attributes instanceof Map){let a=n;if(a.id&&!n.attributes.has(`id`)&&(r+=o(`id`,a.id)),a.className&&!n.attributes.has(`class`)&&(r+=o(`class`,a.className)),!n.attributes.has(`style`)&&a.style){let n=a.style.cssText||``;if(n){let i=n.split(`;`).map(e=>e.trim()).filter(Boolean).map(t=>{let n=t.indexOf(`:`);if(n===-1)return t;let r=t.slice(0,n).trim(),i=t.slice(n+1).trim();return i?`${e(r)}: ${i};`:``}).filter(Boolean).join(` `);i&&(r+=` style="${t(i)}"`)}}for(let[t,a]of n.attributes){let n=i.has(t)?t:e(t);r+=o(n,a)}return r}if(n.attributes&&n.attributes.length)for(let e=0;e<n.attributes.length;e++){let t=n.attributes[e];t&&t.name&&(r+=o(t.name,t.value))}return r}const c=new Set([`area`,`base`,`br`,`col`,`embed`,`hr`,`img`,`input`,`link`,`meta`,`param`,`source`,`track`,`wbr`]);function l(e){if(`tagName`in e&&`children`in e){let t=e.children;if(Array.isArray(t))return t}if(`childNodes`in e){let t=e.childNodes;if(t&&(Array.isArray(t)||t.length!==void 0))return t}return[]}function u(e){if(e.nodeType===3)return n(e.textContent||``);if(e.nodeType===8)return`<!--${e.textContent||``}-->`;if(e.nodeType===1){let t=e,r=t.tagName.toLowerCase(),i=s(t);if(c.has(r))return`<${r}${i} />`;let a=``,o=l(t);if(o&&o.length>0)for(let e=0;e<o.length;e++){let t=o[e];t&&(a+=u(t))}else{let t=e.textContent;typeof t==`string`&&t&&(a=n(t))}return`<${r}${i}>${a}</${r}>`}if(e.nodeType===11){let t=``,n=l(e);if(n&&n.length>0)for(let e=0;e<n.length;e++){let r=n[e];r&&(t+=u(r))}return t}return``}function d(e){if(!e)return``;if(typeof e==`function`)try{let t=r(`div`);if(!t)throw Error(`Document is not available. Make sure polyfills are loaded.`);let n=e(t,0);return n&&typeof n==`object`&&`nodeType`in n?u(n):``}catch(e){return console.error(`Error rendering component to string:`,e),``}return`nodeType`in e?u(e):``}function f(e){return e.map(e=>d(e))}function p(e,t=`div`,n={}){let r=d(e);return`<${t}${Object.entries(n).map(([e,t])=>o(e,t)).join(``)}>${r}</${t}>`}function m(e){}export{f as renderManyToString,d as renderToString,p as renderToStringWithContainer,m as setSSRCollector};
|
|
2
2
|
//# sourceMappingURL=nuclo.ssr.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nuclo.ssr.mjs","names":[],"sources":["../../src/utility/stringUtils.ts","../../src/utility/dom.ts","../../src/ssr/renderToString.ts"],"sourcesContent":["/**\n * String utility functions\n */\n\n/**\n * Converts camelCase to kebab-case (optimized for performance)\n * Uses direct character code manipulation for maximum speed\n * @example camelToKebab('backgroundColor') => 'background-color'\n */\nexport function camelToKebab(str: string): string {\n let result = '';\n for (let i = 0; i < str.length; i++) {\n const code = str.charCodeAt(i);\n // A-Z is 65-90, a-z is 97-122 (difference of 32)\n if (code >= 65 && code <= 90) {\n if (i > 0) result += '-';\n result += String.fromCharCode(code + 32);\n } else {\n result += str[i];\n }\n }\n return result;\n}\n\n/**\n * Escapes HTML special characters in attribute values (includes \" and ')\n */\nexport function escapeHtml(text: string): string {\n const map: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": '''\n };\n return text.replace(/[&<>\"']/g, (char) => map[char]);\n}\n\n/**\n * Escapes HTML special characters in text node content.\n * Only &, < and > need escaping — quotes are safe inside text nodes.\n */\nexport function escapeText(text: string): string {\n return text.replace(/[&<>]/g, (char) => (char === '&' ? '&' : char === '<' ? '<' : '>'));\n}\n","import { logError } from \"./errorHandler\";\nimport { removeAllListeners } from \"./on\";\nimport { cleanupReactiveTextNode, cleanupReactiveElement } from \"../core/reactiveCleanup\";\nimport { unregisterConditionalNode } from \"./conditionalInfo\";\n\nexport const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';\n\n/**\n * Creates an HTML element.\n * Wrapper for document.createElement with type safety.\n */\nexport function createElement<K extends keyof HTMLElementTagNameMap>(\n tagName: K\n): HTMLElementTagNameMap[K] | null;\nexport function createElement(tagName: string): ExpandedElement | null;\nexport function createElement(tagName: string): ExpandedElement | null {\n return globalThis.document ? document.createElement(tagName) as ExpandedElement : null;\n}\n\n/**\n * Creates an element in the given namespace (typically for SVG elements).\n * Wrapper for document.createElementNS with type safety.\n */\nexport function createElementNS(\n namespace: string,\n tagName: string\n): ExpandedElement | null {\n return globalThis.document ? document.createElementNS(namespace, tagName) as ExpandedElement : null;\n}\n\n/**\n * Creates a text node with the given content.\n * Wrapper for document.createTextNode.\n */\nexport function createTextNode(text: string): Text | null {\n return globalThis.document ? document.createTextNode(text) : null;\n}\n\n/**\n * Creates a document fragment.\n * Wrapper for document.createDocumentFragment.\n */\nexport function createDocumentFragment(): DocumentFragment | null {\n return globalThis.document ? document.createDocumentFragment() : null;\n}\n\nfunction safeAppendChild(parent: Element | Node, child: Node): boolean {\n try {\n parent.appendChild(child);\n return true;\n } catch (error) {\n logError('Failed to append child node', error);\n return false;\n }\n}\n\n/**\n * Recursively removes all event listeners and reactive subscriptions from a node and its descendants\n * to prevent memory leaks when elements are removed from the DOM.\n */\nfunction cleanupEventListeners(node: Node): void {\n // Clean up the node itself based on its type\n if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as HTMLElement;\n // Remove all event listeners\n removeAllListeners(element);\n // Remove reactive attribute resolvers\n cleanupReactiveElement(element);\n // Remove conditional info\n unregisterConditionalNode(element);\n } else if (node.nodeType === Node.TEXT_NODE) {\n // Remove reactive text node info\n cleanupReactiveTextNode(node as Text);\n } else if (node.nodeType === Node.COMMENT_NODE) {\n // Remove conditional info from comment nodes (used by when/list)\n unregisterConditionalNode(node);\n }\n \n // Recursively clean up all child nodes\n if (node.childNodes && node.childNodes.length > 0) {\n for (let i = 0; i < node.childNodes.length; i++) {\n cleanupEventListeners(node.childNodes[i]);\n }\n }\n}\n\nexport function safeRemoveChild(child: Node): boolean {\n if (!child?.parentNode) return false;\n try {\n // Clean up all event listeners before removing the element\n cleanupEventListeners(child);\n child.parentNode.removeChild(child);\n return true;\n } catch (error) {\n logError('Failed to remove child node', error);\n return false;\n }\n}\n\nfunction safeInsertBefore(parent: Node, newNode: Node, referenceNode: Node | null): boolean {\n try {\n parent.insertBefore(newNode, referenceNode);\n return true;\n } catch (error) {\n logError('Failed to insert node before reference', error);\n return false;\n }\n}\n\nexport function createComment(text: string): Comment | null {\n return globalThis.document ? globalThis.document.createComment(text) : null;\n}\n\nexport function createConditionalComment(tagName: string, suffix = \"hidden\"): Comment | null {\n return globalThis.document ? globalThis.document.createComment(`conditional-${tagName}-${suffix}`) : null;\n}\n\nexport function createMarkerPair(prefix: string, id: number | string): { start: Comment; end: Comment } {\n const endComment = createComment(`${prefix}-end`);\n if (!endComment) throw new Error(\"Failed to create comment: document not available\");\n const startComment = createComment(`${prefix}-start-${id}`);\n if (!startComment) throw new Error(\"Failed to create comment: document not available\");\n return { start: startComment, end: endComment };\n}\n\nexport function clearBetweenMarkers(startMarker: Comment, endMarker: Comment): void {\n let current = startMarker.nextSibling;\n while (current && current !== endMarker) {\n const next = current.nextSibling;\n safeRemoveChild(current);\n current = next;\n }\n}\n\nexport function insertNodesBefore(nodes: Node[], referenceNode: Node): void {\n const parent = referenceNode.parentNode;\n if (parent) {\n for (let i = 0; i < nodes.length; i++) {\n safeInsertBefore(parent, nodes[i], referenceNode);\n }\n }\n}\n\nexport function appendChildren(\n parent: Element | Node,\n ...children: Array<Element | Node | string | null | undefined>\n): Element | Node {\n if (!parent) return parent;\n\n for (let i = 0; i < children.length; i++) {\n const child = children[i];\n if (child != null) {\n let nodeToAppend: Node;\n\n if (typeof child === \"string\") {\n const textNode = createTextNode(child);\n if (textNode) {\n nodeToAppend = textNode;\n } else {\n continue;\n }\n } else {\n nodeToAppend = child as Node;\n }\n\n safeAppendChild(parent, nodeToAppend);\n }\n }\n\n return parent;\n}\n\nexport function isNodeConnected(node: Node | null | undefined): boolean {\n if (!node) return false;\n return node.isConnected === true;\n}\n\n/**\n * Safely replaces an old node with a new node in the DOM.\n * Returns true on success, false on failure (and logs the error).\n */\nexport function replaceNodeSafely(oldNode: Node, newNode: Node): boolean {\n if (!oldNode?.parentNode) return false;\n try {\n oldNode.parentNode.replaceChild(newNode, oldNode);\n return true;\n } catch (error) {\n logError(\"Error replacing conditional node\", error);\n return false;\n }\n}\n","/**\n * Server-Side Rendering (SSR) utilities for Nuclo\n * Renders Nuclo components to HTML strings in Node.js environment\n */\n\nimport { escapeHtml, escapeText, camelToKebab } from '../utility/stringUtils';\nimport { createElement } from '../utility/dom';\n\ntype RenderableInput =\n | NodeModFn<ElementTagName>\n | Element\n | Node\n | null\n | undefined;\n\n/**\n * SVG attributes that are natively camelCase and must NOT be converted to kebab-case.\n * Most SVG attributes are already kebab-case (stroke-width, fill-rule, etc.), but a\n * handful are defined as camelCase in the SVG spec and must be preserved.\n */\nconst SVG_PRESERVE_CASE_ATTRS = new Set([\n 'viewBox', 'preserveAspectRatio', 'markerWidth', 'markerHeight',\n 'gradientTransform', 'patternTransform', 'clipPathUnits', 'gradientUnits',\n 'patternUnits', 'pathLength', 'refX', 'refY', 'stdDeviation',\n 'baseFrequency', 'numOctaves', 'kernelMatrix', 'tableValues',\n 'targetX', 'targetY', 'specularExponent', 'specularConstant',\n 'diffuseConstant', 'surfaceScale', 'xChannelSelector', 'yChannelSelector',\n 'edgeMode', 'stitchTiles', 'spreadMethod', 'patternContentUnits',\n 'markerUnits', 'startOffset', 'textLength', 'lengthAdjust',\n]);\n\n/**\n * HTML boolean attributes — presence means true, absence means false.\n * When the stored value is the string \"true\" or \"false\" (from setAttribute),\n * we must re-apply boolean semantics instead of outputting the raw string.\n */\nconst HTML_BOOLEAN_ATTRIBUTES = new Set([\n 'allowfullscreen', 'async', 'autofocus', 'autoplay', 'checked', 'controls',\n 'default', 'defer', 'disabled', 'formnovalidate', 'hidden', 'ismap', 'loop',\n 'multiple', 'muted', 'nomodule', 'novalidate', 'open', 'readonly', 'required',\n 'reversed', 'selected',\n]);\n\n/**\n * Serializes a DOM attribute value\n */\nfunction serializeAttribute(name: string, value: unknown): string {\n if (value === null || value === undefined || value === false) {\n return '';\n }\n\n if (value === true) {\n return ` ${name}`;\n }\n\n // Boolean attributes stored as strings via setAttribute() need special handling\n if (HTML_BOOLEAN_ATTRIBUTES.has(name)) {\n if (value === 'false') return '';\n if (value === 'true' || value === '' || value === name) return ` ${name}`;\n }\n\n if (name === 'style' && typeof value === 'object') {\n const styleStr = Object.entries(value)\n .filter(([, val]) => val != null && val !== '')\n .map(([key, val]) => `${camelToKebab(key)}: ${val};`)\n .join(' ');\n return styleStr ? ` style=\"${escapeHtml(styleStr)}\"` : '';\n }\n\n return ` ${name}=\"${escapeHtml(String(value))}\"`;\n}\n\n/**\n * Serializes DOM element attributes to HTML string\n */\nfunction serializeAttributes(element: Element): string {\n let result = '';\n\n // Handle polyfill elements with Map-based attributes\n if ('attributes' in element && element.attributes instanceof Map) {\n const el = element as any;\n\n // id — may live on the property rather than in the Map\n if (el.id && !element.attributes.has('id')) {\n result += serializeAttribute('id', el.id);\n }\n\n // class — kept on .className, mirrored to Map only when setAttribute is used\n if (el.className && !element.attributes.has('class')) {\n result += serializeAttribute('class', el.className);\n }\n\n // style — lives on the Proxy, not in the attributes Map.\n // cssText returns camelCase keys (\"backgroundColor: red\"), so convert them.\n if (!element.attributes.has('style') && el.style) {\n const rawCssText: string = el.style.cssText || '';\n if (rawCssText) {\n const kebabStyle = rawCssText\n .split(';')\n .map((decl: string) => decl.trim())\n .filter(Boolean)\n .map((decl: string) => {\n const colonIdx = decl.indexOf(':');\n if (colonIdx === -1) return decl;\n const key = decl.slice(0, colonIdx).trim();\n const val = decl.slice(colonIdx + 1).trim();\n // Skip declarations with empty values (e.g. reactive styles that resolved to undefined)\n if (!val) return '';\n return `${camelToKebab(key)}: ${val};`;\n })\n .filter(Boolean)\n .join(' ');\n if (kebabStyle) {\n result += ` style=\"${escapeHtml(kebabStyle)}\"`;\n }\n }\n }\n\n // All remaining attributes from the Map\n // Convert camelCase ARIA/HTML attribute names (e.g. ariaLabel → aria-label) that the\n // polyfill stores as-is because NucloElement lacks the browser property mappings.\n // SVG attributes that are natively camelCase (e.g. viewBox, preserveAspectRatio) must\n // NOT be converted — they are stored via setAttribute() as-is.\n for (const [name, value] of element.attributes) {\n const htmlName = SVG_PRESERVE_CASE_ATTRS.has(name) ? name : camelToKebab(name);\n result += serializeAttribute(htmlName, value);\n }\n return result;\n }\n\n // Handle browser elements with NamedNodeMap attributes\n if (element.attributes && element.attributes.length) {\n for (let i = 0; i < element.attributes.length; i++) {\n const attr = element.attributes[i];\n if (attr && attr.name) {\n result += serializeAttribute(attr.name, attr.value);\n }\n }\n }\n\n return result;\n}\n\n/**\n * Self-closing HTML tags that don't have closing tags\n */\nconst VOID_ELEMENTS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'param', 'source', 'track', 'wbr'\n]);\n\n/**\n * Get child nodes from a node (handles both browser and polyfill elements).\n *\n * NucloElement stores ALL children (elements, text, comments) in a plain Array\n * called `children` — it is the authoritative list. DocumentFragments in the\n * polyfill also have a `children` array, but it only holds Element children;\n * their full set of nodes lives in `childNodes`. We therefore only prefer\n * `children` when the node is an actual element (has `tagName`).\n */\nfunction getChildNodes(node: Node): ArrayLike<Node> {\n // NucloElement: tagName exists and children is a plain Array containing every node type\n if ('tagName' in node && 'children' in node) {\n const children = (node as any).children;\n if (Array.isArray(children)) {\n return children as ArrayLike<Node>;\n }\n }\n // DocumentFragments and browser elements: use childNodes\n if ('childNodes' in node) {\n const childNodes = (node as any).childNodes;\n if (childNodes && (Array.isArray(childNodes) || childNodes.length !== undefined)) {\n return childNodes;\n }\n }\n return [] as ArrayLike<Node>;\n}\n\n/**\n * Serializes a DOM node to HTML string\n */\nfunction serializeNode(node: Node): string {\n // Text node — only & < > need escaping; quotes are safe in text content\n if (node.nodeType === 3) { // Node.TEXT_NODE\n return escapeText(node.textContent || '');\n }\n\n // Comment node\n if (node.nodeType === 8) { // Node.COMMENT_NODE\n return `<!--${node.textContent || ''}-->`;\n }\n\n // Element node\n if (node.nodeType === 1) { // Node.ELEMENT_NODE\n const element = node as Element;\n const tagName = element.tagName.toLowerCase();\n const attributes = serializeAttributes(element);\n\n // Self-closing tags\n if (VOID_ELEMENTS.has(tagName)) {\n return `<${tagName}${attributes} />`;\n }\n\n // Regular elements with children\n let childrenHtml = '';\n const childNodes = getChildNodes(element);\n if (childNodes && childNodes.length > 0) {\n for (let i = 0; i < childNodes.length; i++) {\n const child = childNodes[i];\n if (child) {\n childrenHtml += serializeNode(child);\n }\n }\n } else {\n // Fallback: textContent set directly on the element (e.g. el.textContent = \"...\")\n const tc = (node as any).textContent;\n if (typeof tc === 'string' && tc) {\n childrenHtml = escapeText(tc);\n }\n }\n\n return `<${tagName}${attributes}>${childrenHtml}</${tagName}>`;\n }\n\n // Document fragment\n if (node.nodeType === 11) { // Node.DOCUMENT_FRAGMENT_NODE\n let result = '';\n const childNodes = getChildNodes(node);\n if (childNodes && childNodes.length > 0) {\n for (let i = 0; i < childNodes.length; i++) {\n const child = childNodes[i];\n if (child) {\n result += serializeNode(child);\n }\n }\n }\n return result;\n }\n\n return '';\n}\n\n/**\n * Renders a Nuclo component to an HTML string for server-side rendering\n *\n * @param input - A Nuclo component function, DOM element, or node\n * @returns HTML string representation of the component\n *\n * @example\n * ```ts\n * import { renderToString } from 'nuclo/ssr';\n * import { div } from 'nuclo';\n *\n * const html = renderToString(\n * div(\"Hello, World!\")\n * );\n * // Returns: '<div>Hello, World!</div>'\n * ```\n */\nexport function renderToString(input: RenderableInput): string {\n if (!input) return '';\n\n if (typeof input === 'function') {\n try {\n const container = createElement('div');\n if (!container) throw new Error('Document is not available. Make sure polyfills are loaded.');\n const element = input(container as ExpandedElement<ElementTagName>, 0);\n return element && typeof element === 'object' && 'nodeType' in element ? serializeNode(element as Node) : '';\n } catch (error) {\n console.error('Error rendering component to string:', error);\n return '';\n }\n }\n\n if ('nodeType' in input) {\n return serializeNode(input as Node);\n }\n\n return '';\n}\n\n/**\n * Renders multiple Nuclo components to HTML strings\n *\n * @param inputs - Array of Nuclo components\n * @returns Array of HTML strings\n */\nexport function renderManyToString(inputs: RenderableInput[]): string[] {\n return inputs.map(input => renderToString(input));\n}\n\n/**\n * Renders a Nuclo component and wraps it in a container element\n *\n * @param input - A Nuclo component\n * @param containerTag - The tag name for the container (default: 'div')\n * @param containerAttrs - Attributes for the container element\n * @returns HTML string with container wrapper\n */\nexport function renderToStringWithContainer(\n input: RenderableInput,\n containerTag: string = 'div',\n containerAttrs: Record<string, string> = {}\n): string {\n const content = renderToString(input);\n const attrs = Object.entries(containerAttrs)\n .map(([key, value]) => serializeAttribute(key, value))\n .join('');\n\n return `<${containerTag}${attrs}>${content}</${containerTag}>`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"nuclo.ssr.mjs","names":[],"sources":["../../src/utility/stringUtils.ts","../../src/utility/dom.ts","../../src/ssr/renderToString.ts","../../src/style/cssGenerator.ts"],"sourcesContent":["/**\n * String utility functions\n */\n\n/**\n * Converts camelCase to kebab-case (optimized for performance)\n * Uses direct character code manipulation for maximum speed\n * @example camelToKebab('backgroundColor') => 'background-color'\n */\nexport function camelToKebab(str: string): string {\n let result = '';\n for (let i = 0; i < str.length; i++) {\n const code = str.charCodeAt(i);\n // A-Z is 65-90, a-z is 97-122 (difference of 32)\n if (code >= 65 && code <= 90) {\n if (i > 0) result += '-';\n result += String.fromCharCode(code + 32);\n } else {\n result += str[i];\n }\n }\n return result;\n}\n\n/**\n * Escapes HTML special characters in attribute values (includes \" and ')\n */\nexport function escapeHtml(text: string): string {\n const map: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": '''\n };\n return text.replace(/[&<>\"']/g, (char) => map[char]);\n}\n\n/**\n * Escapes HTML special characters in text node content.\n * Only &, < and > need escaping — quotes are safe inside text nodes.\n */\nexport function escapeText(text: string): string {\n return text.replace(/[&<>]/g, (char) => (char === '&' ? '&' : char === '<' ? '<' : '>'));\n}\n","import { logError } from \"./errorHandler\";\nimport { removeAllListeners } from \"./on\";\nimport { cleanupReactiveTextNode, cleanupReactiveElement } from \"../core/reactiveCleanup\";\nimport { unregisterConditionalNode } from \"./conditionalInfo\";\n\nexport const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';\n\n/**\n * Creates an HTML element.\n * Wrapper for document.createElement with type safety.\n */\nexport function createElement<K extends keyof HTMLElementTagNameMap>(\n tagName: K\n): HTMLElementTagNameMap[K] | null;\nexport function createElement(tagName: string): ExpandedElement | null;\nexport function createElement(tagName: string): ExpandedElement | null {\n return globalThis.document ? document.createElement(tagName) as ExpandedElement : null;\n}\n\n/**\n * Creates an element in the given namespace (typically for SVG elements).\n * Wrapper for document.createElementNS with type safety.\n */\nexport function createElementNS(\n namespace: string,\n tagName: string\n): ExpandedElement | null {\n return globalThis.document ? document.createElementNS(namespace, tagName) as ExpandedElement : null;\n}\n\n/**\n * Creates a text node with the given content.\n * Wrapper for document.createTextNode.\n */\nexport function createTextNode(text: string): Text | null {\n return globalThis.document ? document.createTextNode(text) : null;\n}\n\n/**\n * Creates a document fragment.\n * Wrapper for document.createDocumentFragment.\n */\nexport function createDocumentFragment(): DocumentFragment | null {\n return globalThis.document ? document.createDocumentFragment() : null;\n}\n\nfunction safeAppendChild(parent: Element | Node, child: Node): boolean {\n try {\n parent.appendChild(child);\n return true;\n } catch (error) {\n logError('Failed to append child node', error);\n return false;\n }\n}\n\n/**\n * Recursively removes all event listeners and reactive subscriptions from a node and its descendants\n * to prevent memory leaks when elements are removed from the DOM.\n */\nfunction cleanupEventListeners(node: Node): void {\n // Clean up the node itself based on its type\n if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as HTMLElement;\n // Remove all event listeners\n removeAllListeners(element);\n // Remove reactive attribute resolvers\n cleanupReactiveElement(element);\n // Remove conditional info\n unregisterConditionalNode(element);\n } else if (node.nodeType === Node.TEXT_NODE) {\n // Remove reactive text node info\n cleanupReactiveTextNode(node as Text);\n } else if (node.nodeType === Node.COMMENT_NODE) {\n // Remove conditional info from comment nodes (used by when/list)\n unregisterConditionalNode(node);\n }\n \n // Recursively clean up all child nodes\n if (node.childNodes && node.childNodes.length > 0) {\n for (let i = 0; i < node.childNodes.length; i++) {\n cleanupEventListeners(node.childNodes[i]);\n }\n }\n}\n\nexport function safeRemoveChild(child: Node): boolean {\n if (!child?.parentNode) return false;\n try {\n // Clean up all event listeners before removing the element\n cleanupEventListeners(child);\n child.parentNode.removeChild(child);\n return true;\n } catch (error) {\n logError('Failed to remove child node', error);\n return false;\n }\n}\n\nfunction safeInsertBefore(parent: Node, newNode: Node, referenceNode: Node | null): boolean {\n try {\n parent.insertBefore(newNode, referenceNode);\n return true;\n } catch (error) {\n logError('Failed to insert node before reference', error);\n return false;\n }\n}\n\nexport function createComment(text: string): Comment | null {\n return globalThis.document ? globalThis.document.createComment(text) : null;\n}\n\nexport function createConditionalComment(tagName: string, suffix = \"hidden\"): Comment | null {\n return globalThis.document ? globalThis.document.createComment(`conditional-${tagName}-${suffix}`) : null;\n}\n\nexport function createMarkerPair(prefix: string, id: number | string): { start: Comment; end: Comment } {\n const endComment = createComment(`${prefix}-end`);\n if (!endComment) throw new Error(\"Failed to create comment: document not available\");\n const startComment = createComment(`${prefix}-start-${id}`);\n if (!startComment) throw new Error(\"Failed to create comment: document not available\");\n return { start: startComment, end: endComment };\n}\n\nexport function clearBetweenMarkers(startMarker: Comment, endMarker: Comment): void {\n let current = startMarker.nextSibling;\n while (current && current !== endMarker) {\n const next = current.nextSibling;\n safeRemoveChild(current);\n current = next;\n }\n}\n\nexport function insertNodesBefore(nodes: Node[], referenceNode: Node): void {\n const parent = referenceNode.parentNode;\n if (parent) {\n for (let i = 0; i < nodes.length; i++) {\n safeInsertBefore(parent, nodes[i], referenceNode);\n }\n }\n}\n\nexport function appendChildren(\n parent: Element | Node,\n ...children: Array<Element | Node | string | null | undefined>\n): Element | Node {\n if (!parent) return parent;\n\n for (let i = 0; i < children.length; i++) {\n const child = children[i];\n if (child != null) {\n let nodeToAppend: Node;\n\n if (typeof child === \"string\") {\n const textNode = createTextNode(child);\n if (textNode) {\n nodeToAppend = textNode;\n } else {\n continue;\n }\n } else {\n nodeToAppend = child as Node;\n }\n\n safeAppendChild(parent, nodeToAppend);\n }\n }\n\n return parent;\n}\n\nexport function isNodeConnected(node: Node | null | undefined): boolean {\n if (!node) return false;\n return node.isConnected === true;\n}\n\n/**\n * Safely replaces an old node with a new node in the DOM.\n * Returns true on success, false on failure (and logs the error).\n */\nexport function replaceNodeSafely(oldNode: Node, newNode: Node): boolean {\n if (!oldNode?.parentNode) return false;\n try {\n oldNode.parentNode.replaceChild(newNode, oldNode);\n return true;\n } catch (error) {\n logError(\"Error replacing conditional node\", error);\n return false;\n }\n}\n","/**\n * Server-Side Rendering (SSR) utilities for Nuclo\n * Renders Nuclo components to HTML strings in Node.js environment\n */\n\nimport { escapeHtml, escapeText, camelToKebab } from '../utility/stringUtils';\nimport { createElement } from '../utility/dom';\n\ntype RenderableInput =\n | NodeModFn<ElementTagName>\n | Element\n | Node\n | null\n | undefined;\n\n/**\n * SVG attributes that are natively camelCase and must NOT be converted to kebab-case.\n * Most SVG attributes are already kebab-case (stroke-width, fill-rule, etc.), but a\n * handful are defined as camelCase in the SVG spec and must be preserved.\n */\nconst SVG_PRESERVE_CASE_ATTRS = new Set([\n 'viewBox', 'preserveAspectRatio', 'markerWidth', 'markerHeight',\n 'gradientTransform', 'patternTransform', 'clipPathUnits', 'gradientUnits',\n 'patternUnits', 'pathLength', 'refX', 'refY', 'stdDeviation',\n 'baseFrequency', 'numOctaves', 'kernelMatrix', 'tableValues',\n 'targetX', 'targetY', 'specularExponent', 'specularConstant',\n 'diffuseConstant', 'surfaceScale', 'xChannelSelector', 'yChannelSelector',\n 'edgeMode', 'stitchTiles', 'spreadMethod', 'patternContentUnits',\n 'markerUnits', 'startOffset', 'textLength', 'lengthAdjust',\n]);\n\n/**\n * HTML boolean attributes — presence means true, absence means false.\n * When the stored value is the string \"true\" or \"false\" (from setAttribute),\n * we must re-apply boolean semantics instead of outputting the raw string.\n */\nconst HTML_BOOLEAN_ATTRIBUTES = new Set([\n 'allowfullscreen', 'async', 'autofocus', 'autoplay', 'checked', 'controls',\n 'default', 'defer', 'disabled', 'formnovalidate', 'hidden', 'ismap', 'loop',\n 'multiple', 'muted', 'nomodule', 'novalidate', 'open', 'readonly', 'required',\n 'reversed', 'selected',\n]);\n\n/**\n * Serializes a DOM attribute value\n */\nfunction serializeAttribute(name: string, value: unknown): string {\n if (value === null || value === undefined || value === false) {\n return '';\n }\n\n if (value === true) {\n return ` ${name}`;\n }\n\n // Boolean attributes stored as strings via setAttribute() need special handling\n if (HTML_BOOLEAN_ATTRIBUTES.has(name)) {\n if (value === 'false') return '';\n if (value === 'true' || value === '' || value === name) return ` ${name}`;\n }\n\n if (name === 'style' && typeof value === 'object') {\n const styleStr = Object.entries(value)\n .filter(([, val]) => val != null && val !== '')\n .map(([key, val]) => `${camelToKebab(key)}: ${val};`)\n .join(' ');\n return styleStr ? ` style=\"${escapeHtml(styleStr)}\"` : '';\n }\n\n return ` ${name}=\"${escapeHtml(String(value))}\"`;\n}\n\n/**\n * Serializes DOM element attributes to HTML string\n */\nfunction serializeAttributes(element: Element): string {\n let result = '';\n\n // Handle polyfill elements with Map-based attributes\n if ('attributes' in element && element.attributes instanceof Map) {\n const el = element as any;\n\n // id — may live on the property rather than in the Map\n if (el.id && !element.attributes.has('id')) {\n result += serializeAttribute('id', el.id);\n }\n\n // class — kept on .className, mirrored to Map only when setAttribute is used\n if (el.className && !element.attributes.has('class')) {\n result += serializeAttribute('class', el.className);\n }\n\n // style — lives on the Proxy, not in the attributes Map.\n // cssText returns camelCase keys (\"backgroundColor: red\"), so convert them.\n if (!element.attributes.has('style') && el.style) {\n const rawCssText: string = el.style.cssText || '';\n if (rawCssText) {\n const kebabStyle = rawCssText\n .split(';')\n .map((decl: string) => decl.trim())\n .filter(Boolean)\n .map((decl: string) => {\n const colonIdx = decl.indexOf(':');\n if (colonIdx === -1) return decl;\n const key = decl.slice(0, colonIdx).trim();\n const val = decl.slice(colonIdx + 1).trim();\n // Skip declarations with empty values (e.g. reactive styles that resolved to undefined)\n if (!val) return '';\n return `${camelToKebab(key)}: ${val};`;\n })\n .filter(Boolean)\n .join(' ');\n if (kebabStyle) {\n result += ` style=\"${escapeHtml(kebabStyle)}\"`;\n }\n }\n }\n\n // All remaining attributes from the Map\n // Convert camelCase ARIA/HTML attribute names (e.g. ariaLabel → aria-label) that the\n // polyfill stores as-is because NucloElement lacks the browser property mappings.\n // SVG attributes that are natively camelCase (e.g. viewBox, preserveAspectRatio) must\n // NOT be converted — they are stored via setAttribute() as-is.\n for (const [name, value] of element.attributes) {\n const htmlName = SVG_PRESERVE_CASE_ATTRS.has(name) ? name : camelToKebab(name);\n result += serializeAttribute(htmlName, value);\n }\n return result;\n }\n\n // Handle browser elements with NamedNodeMap attributes\n if (element.attributes && element.attributes.length) {\n for (let i = 0; i < element.attributes.length; i++) {\n const attr = element.attributes[i];\n if (attr && attr.name) {\n result += serializeAttribute(attr.name, attr.value);\n }\n }\n }\n\n return result;\n}\n\n/**\n * Self-closing HTML tags that don't have closing tags\n */\nconst VOID_ELEMENTS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'param', 'source', 'track', 'wbr'\n]);\n\n/**\n * Get child nodes from a node (handles both browser and polyfill elements).\n *\n * NucloElement stores ALL children (elements, text, comments) in a plain Array\n * called `children` — it is the authoritative list. DocumentFragments in the\n * polyfill also have a `children` array, but it only holds Element children;\n * their full set of nodes lives in `childNodes`. We therefore only prefer\n * `children` when the node is an actual element (has `tagName`).\n */\nfunction getChildNodes(node: Node): ArrayLike<Node> {\n // NucloElement: tagName exists and children is a plain Array containing every node type\n if ('tagName' in node && 'children' in node) {\n const children = (node as any).children;\n if (Array.isArray(children)) {\n return children as ArrayLike<Node>;\n }\n }\n // DocumentFragments and browser elements: use childNodes\n if ('childNodes' in node) {\n const childNodes = (node as any).childNodes;\n if (childNodes && (Array.isArray(childNodes) || childNodes.length !== undefined)) {\n return childNodes;\n }\n }\n return [] as ArrayLike<Node>;\n}\n\n/**\n * Serializes a DOM node to HTML string\n */\nfunction serializeNode(node: Node): string {\n // Text node — only & < > need escaping; quotes are safe in text content\n if (node.nodeType === 3) { // Node.TEXT_NODE\n return escapeText(node.textContent || '');\n }\n\n // Comment node\n if (node.nodeType === 8) { // Node.COMMENT_NODE\n return `<!--${node.textContent || ''}-->`;\n }\n\n // Element node\n if (node.nodeType === 1) { // Node.ELEMENT_NODE\n const element = node as Element;\n const tagName = element.tagName.toLowerCase();\n const attributes = serializeAttributes(element);\n\n // Self-closing tags\n if (VOID_ELEMENTS.has(tagName)) {\n return `<${tagName}${attributes} />`;\n }\n\n // Regular elements with children\n let childrenHtml = '';\n const childNodes = getChildNodes(element);\n if (childNodes && childNodes.length > 0) {\n for (let i = 0; i < childNodes.length; i++) {\n const child = childNodes[i];\n if (child) {\n childrenHtml += serializeNode(child);\n }\n }\n } else {\n // Fallback: textContent set directly on the element (e.g. el.textContent = \"...\")\n const tc = (node as any).textContent;\n if (typeof tc === 'string' && tc) {\n childrenHtml = escapeText(tc);\n }\n }\n\n return `<${tagName}${attributes}>${childrenHtml}</${tagName}>`;\n }\n\n // Document fragment\n if (node.nodeType === 11) { // Node.DOCUMENT_FRAGMENT_NODE\n let result = '';\n const childNodes = getChildNodes(node);\n if (childNodes && childNodes.length > 0) {\n for (let i = 0; i < childNodes.length; i++) {\n const child = childNodes[i];\n if (child) {\n result += serializeNode(child);\n }\n }\n }\n return result;\n }\n\n return '';\n}\n\n/**\n * Renders a Nuclo component to an HTML string for server-side rendering\n *\n * @param input - A Nuclo component function, DOM element, or node\n * @returns HTML string representation of the component\n *\n * @example\n * ```ts\n * import { renderToString } from 'nuclo/ssr';\n * import { div } from 'nuclo';\n *\n * const html = renderToString(\n * div(\"Hello, World!\")\n * );\n * // Returns: '<div>Hello, World!</div>'\n * ```\n */\nexport function renderToString(input: RenderableInput): string {\n if (!input) return '';\n\n if (typeof input === 'function') {\n try {\n const container = createElement('div');\n if (!container) throw new Error('Document is not available. Make sure polyfills are loaded.');\n const element = input(container as ExpandedElement<ElementTagName>, 0);\n return element && typeof element === 'object' && 'nodeType' in element ? serializeNode(element as Node) : '';\n } catch (error) {\n console.error('Error rendering component to string:', error);\n return '';\n }\n }\n\n if ('nodeType' in input) {\n return serializeNode(input as Node);\n }\n\n return '';\n}\n\n/**\n * Renders multiple Nuclo components to HTML strings\n *\n * @param inputs - Array of Nuclo components\n * @returns Array of HTML strings\n */\nexport function renderManyToString(inputs: RenderableInput[]): string[] {\n return inputs.map(input => renderToString(input));\n}\n\n/**\n * Renders a Nuclo component and wraps it in a container element\n *\n * @param input - A Nuclo component\n * @param containerTag - The tag name for the container (default: 'div')\n * @param containerAttrs - Attributes for the container element\n * @returns HTML string with container wrapper\n */\nexport function renderToStringWithContainer(\n input: RenderableInput,\n containerTag: string = 'div',\n containerAttrs: Record<string, string> = {}\n): string {\n const content = renderToString(input);\n const attrs = Object.entries(containerAttrs)\n .map(([key, value]) => serializeAttribute(key, value))\n .join('');\n\n return `<${containerTag}${attrs}>${content}</${containerTag}>`;\n}\n","import { createElement } from \"../utility/dom\";\nimport { isBrowser } from \"../utility/environment\";\n\ntype AtRuleType = 'media' | 'container' | 'supports' | 'style' | 'pseudo';\n\n// SSR collector — a single function reference installed once at server startup.\n// Keeping this as a plain function slot (no global Set, no ALS) means the nuclo\n// package stays browser-safe and free of Node-specific imports. The server is\n// responsible for wiring in whatever collection strategy it wants (e.g. an\n// AsyncLocalStorage-backed dispatcher). null = no-op (browser default).\ntype SSRCollector = (rule: string) => void;\nlet _ssrCollector: SSRCollector | null = null;\n\n/**\n * Install a CSS rule collector for SSR. Call once at server startup with a\n * function that receives every rule string emitted by cn() / createStyleQueries.\n * Pass null to remove the collector (not normally needed).\n */\nexport function setSSRCollector(fn: SSRCollector | null): void {\n\t_ssrCollector = fn;\n}\n\nfunction isAtRule(rule: CSSRule): boolean {\n\treturn rule instanceof CSSMediaRule ||\n\t\trule instanceof CSSContainerRule ||\n\t\trule instanceof CSSSupportsRule;\n}\n\nfunction matchesAtRule(rule: CSSRule, atRuleType: AtRuleType, condition: string): boolean {\n\tif (atRuleType === 'media' && rule instanceof CSSMediaRule) {\n\t\treturn rule.media.mediaText === condition;\n\t}\n\tif (atRuleType === 'container' && rule instanceof CSSContainerRule) {\n\t\treturn rule.conditionText === condition;\n\t}\n\tif (atRuleType === 'supports' && rule instanceof CSSSupportsRule) {\n\t\treturn rule.conditionText === condition;\n\t}\n\treturn false;\n}\n\nfunction findStyleRule(rules: CSSRuleList, selector: string): CSSStyleRule | null {\n\tfor (let i = 0; i < rules.length; i++) {\n\t\tconst rule = rules[i];\n\t\tif (rule instanceof CSSStyleRule && rule.selectorText === selector) {\n\t\t\treturn rule;\n\t\t}\n\t}\n\treturn null;\n}\n\nfunction updateRuleStyles(rule: CSSStyleRule, styles: Record<string, string>): void {\n\trule.style.cssText = '';\n\tfor (const [property, value] of Object.entries(styles)) {\n\t\trule.style.setProperty(property, value);\n\t}\n}\n\nfunction getStyleSheet(): HTMLStyleElement | null {\n\tif (!isBrowser) return null;\n\tlet el = document.querySelector(\"#nuclo-styles\") as HTMLStyleElement;\n\tif (!el) {\n\t\tel = createElement(\"style\") as HTMLStyleElement;\n\t\tel.id = \"nuclo-styles\";\n\t\tdocument.head.appendChild(el);\n\t}\n\treturn el;\n}\n\nfunction buildRulesString(styles: Record<string, string>): string {\n\tconst entries = Object.entries(styles);\n\tlet result = '';\n\tfor (let i = 0; i < entries.length; i++) {\n\t\tif (i > 0) result += '; ';\n\t\tresult += `${entries[i][0]}: ${entries[i][1]}`;\n\t}\n\treturn result;\n}\n\nexport function classExistsInDOM(className: string, condition?: string, atRuleType: AtRuleType = 'media', pseudoClass?: string): boolean {\n\tif (!isBrowser) return false;\n\tconst styleSheet = document.querySelector(\"#nuclo-styles\") as HTMLStyleElement;\n\tif (!styleSheet?.sheet) return false;\n\n\tconst rules = styleSheet.sheet.cssRules;\n\n\tif (pseudoClass) {\n\t\treturn findStyleRule(rules, `.${className}${pseudoClass}`) !== null;\n\t}\n\n\tif (condition) {\n\t\tfor (let i = 0; i < rules.length; i++) {\n\t\t\tif (matchesAtRule(rules[i], atRuleType, condition)) {\n\t\t\t\treturn findStyleRule((rules[i] as CSSGroupingRule).cssRules, `.${className}`) !== null;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\treturn findStyleRule(rules, `.${className}`) !== null;\n}\n\nexport function createCSSClassWithStyles(\n\tclassName: string,\n\tstyles: Record<string, string>,\n\tcondition?: string,\n\tatRuleType: AtRuleType = 'media',\n\tpseudoClass?: string\n): void {\n\tif (!isBrowser) {\n\t\tconst rulesStr = buildRulesString(styles);\n\t\tlet rule: string;\n\t\tif (pseudoClass) {\n\t\t\trule = `.${className}${pseudoClass} { ${rulesStr} }`;\n\t\t} else if (condition) {\n\t\t\tconst prefix = atRuleType === 'container' ? '@container'\n\t\t\t\t: atRuleType === 'supports' ? '@supports'\n\t\t\t\t: '@media';\n\t\t\trule = `${prefix} ${condition} { .${className} { ${rulesStr} } }`;\n\t\t} else {\n\t\t\trule = `.${className} { ${rulesStr} }`;\n\t\t}\n\t\t_ssrCollector?.(rule);\n\t\treturn;\n\t}\n\n\tconst styleSheet = getStyleSheet();\n\tif (!styleSheet) return;\n\n\tconst sheet = styleSheet.sheet;\n\tif (!sheet) return;\n\n\tconst rulesStr = buildRulesString(styles);\n\n\tif (pseudoClass) {\n\t\tconst selector = `.${className}${pseudoClass}`;\n\t\tconst allRules = sheet.cssRules;\n\t\tconst rulesLength = allRules.length;\n\n\t\tlet existingRule: CSSStyleRule | null = null;\n\t\tlet insertIndex = rulesLength;\n\n\t\tfor (let i = 0; i < rulesLength; i++) {\n\t\t\tconst rule = allRules[i];\n\t\t\tif (rule instanceof CSSStyleRule) {\n\t\t\t\tif (rule.selectorText === selector) {\n\t\t\t\t\texistingRule = rule;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (!rule.selectorText.includes(':')) {\n\t\t\t\t\tinsertIndex = i + 1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (existingRule) {\n\t\t\tupdateRuleStyles(existingRule, styles);\n\t\t} else {\n\t\t\tsheet.insertRule(`${selector} { ${rulesStr} }`, insertIndex);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (condition) {\n\t\tconst existingRules = sheet.cssRules;\n\t\tconst rulesLength = existingRules.length;\n\t\tlet groupingRule: CSSGroupingRule | null = null;\n\n\t\tfor (let i = 0; i < rulesLength; i++) {\n\t\t\tif (matchesAtRule(existingRules[i], atRuleType, condition)) {\n\t\t\t\tgroupingRule = existingRules[i] as CSSGroupingRule;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (!groupingRule) {\n\t\t\tlet insertIndex = rulesLength;\n\t\t\tfor (let i = rulesLength - 1; i >= 0; i--) {\n\t\t\t\tif (isAtRule(existingRules[i]) || existingRules[i] instanceof CSSStyleRule) {\n\t\t\t\t\tinsertIndex = i + 1;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst atRulePrefix = atRuleType === 'media' ? '@media' :\n\t\t\t\tatRuleType === 'container' ? '@container' :\n\t\t\t\tatRuleType === 'supports' ? '@supports' :\n\t\t\t\t'@media';\n\n\t\t\tsheet.insertRule(`${atRulePrefix} ${condition} {}`, insertIndex);\n\t\t\tgroupingRule = sheet.cssRules[insertIndex] as CSSGroupingRule;\n\t\t}\n\n\t\tconst existingRule = findStyleRule(groupingRule.cssRules, `.${className}`);\n\t\tif (existingRule) {\n\t\t\tupdateRuleStyles(existingRule, styles);\n\t\t} else {\n\t\t\tgroupingRule.insertRule(`.${className} { ${rulesStr} }`, groupingRule.cssRules.length);\n\t\t}\n\t} else {\n\t\tlet existingRule: CSSStyleRule | null = null;\n\t\tlet insertIndex = 0;\n\n\t\tconst allRules = sheet.cssRules;\n\t\tconst rulesLength = allRules.length;\n\t\tfor (let i = 0; i < rulesLength; i++) {\n\t\t\tconst rule = allRules[i];\n\t\t\tif (rule instanceof CSSStyleRule && rule.selectorText === `.${className}`) {\n\t\t\t\texistingRule = rule;\n\t\t\t\tinsertIndex = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (!isAtRule(rule)) {\n\t\t\t\tinsertIndex = i + 1;\n\t\t\t}\n\t\t}\n\n\t\tif (existingRule) {\n\t\t\tupdateRuleStyles(existingRule, styles);\n\t\t} else {\n\t\t\tsheet.insertRule(`.${className} { ${rulesStr} }`, insertIndex);\n\t\t}\n\t}\n}\n\nexport function createCSSClass(className: string, styles: Record<string, string>): void {\n\tcreateCSSClassWithStyles(className, styles);\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
type AtRuleType = 'media' | 'container' | 'supports' | 'style' | 'pseudo';
|
|
2
|
+
type SSRCollector = (rule: string) => void;
|
|
3
|
+
/**
|
|
4
|
+
* Install a CSS rule collector for SSR. Call once at server startup with a
|
|
5
|
+
* function that receives every rule string emitted by cn() / createStyleQueries.
|
|
6
|
+
* Pass null to remove the collector (not normally needed).
|
|
7
|
+
*/
|
|
8
|
+
export declare function setSSRCollector(fn: SSRCollector | null): void;
|
|
2
9
|
export declare function classExistsInDOM(className: string, condition?: string, atRuleType?: AtRuleType, pseudoClass?: string): boolean;
|
|
3
10
|
export declare function createCSSClassWithStyles(className: string, styles: Record<string, string>, condition?: string, atRuleType?: AtRuleType, pseudoClass?: string): void;
|
|
4
11
|
export declare function createCSSClass(className: string, styles: Record<string, string>): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cssGenerator.d.ts","sourceRoot":"","sources":["../../src/style/cssGenerator.ts"],"names":[],"mappings":"AAGA,KAAK,UAAU,GAAG,OAAO,GAAG,WAAW,GAAG,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"cssGenerator.d.ts","sourceRoot":"","sources":["../../src/style/cssGenerator.ts"],"names":[],"mappings":"AAGA,KAAK,UAAU,GAAG,OAAO,GAAG,WAAW,GAAG,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC;AAO1E,KAAK,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AAG3C;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI,CAE7D;AA2DD,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,UAAU,GAAE,UAAoB,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAqBvI;AAED,wBAAgB,wBAAwB,CACvC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,SAAS,CAAC,EAAE,MAAM,EAClB,UAAU,GAAE,UAAoB,EAChC,WAAW,CAAC,EAAE,MAAM,GAClB,IAAI,CAmHN;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAEtF"}
|
package/dist/utility/render.d.ts
CHANGED
|
@@ -7,4 +7,27 @@
|
|
|
7
7
|
* @returns The rendered element
|
|
8
8
|
*/
|
|
9
9
|
export declare function render<TTagName extends ElementTagName = ElementTagName>(nodeModFn: NodeModFn<TTagName>, parent?: Element, index?: number): ExpandedElement<TTagName>;
|
|
10
|
+
/**
|
|
11
|
+
* Hydrates an existing server-rendered DOM tree by walking it in parallel with
|
|
12
|
+
* the component tree, reusing existing nodes and re-attaching reactivity.
|
|
13
|
+
*
|
|
14
|
+
* Instead of clearing the container and creating new DOM nodes (like render()),
|
|
15
|
+
* hydrate() claims existing nodes from the SSR output and registers reactive
|
|
16
|
+
* text nodes, attributes, event listeners, list runtimes, and when runtimes
|
|
17
|
+
* on them.
|
|
18
|
+
*
|
|
19
|
+
* @param nodeModFn The NodeModFn to hydrate (same component used for SSR)
|
|
20
|
+
* @param parent The parent element containing the SSR HTML (defaults to document.body)
|
|
21
|
+
* @returns The hydrated root element
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* // Server: const html = renderToString(div(h1("Hello")));
|
|
26
|
+
* // Client:
|
|
27
|
+
* const app = document.getElementById("app")!;
|
|
28
|
+
* // app.innerHTML already contains the SSR HTML
|
|
29
|
+
* hydrate(div(h1("Hello")), app);
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare function hydrate<TTagName extends ElementTagName = ElementTagName>(nodeModFn: NodeModFn<TTagName>, parent?: Element): ExpandedElement<TTagName>;
|
|
10
33
|
//# sourceMappingURL=render.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/utility/render.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/utility/render.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,QAAQ,SAAS,cAAc,GAAG,cAAc,EACrE,SAAS,EAAE,SAAS,CAAC,QAAQ,CAAC,EAC9B,MAAM,CAAC,EAAE,OAAO,EAChB,KAAK,GAAE,MAAU,GAChB,eAAe,CAAC,QAAQ,CAAC,CAK3B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,OAAO,CAAC,QAAQ,SAAS,cAAc,GAAG,cAAc,EACtE,SAAS,EAAE,SAAS,CAAC,QAAQ,CAAC,EAC9B,MAAM,CAAC,EAAE,OAAO,GACf,eAAe,CAAC,QAAQ,CAAC,CAS3B"}
|
package/dist/when/builder.d.ts
CHANGED
|
@@ -6,6 +6,9 @@ declare class WhenBuilderImpl<TTagName extends ElementTagName = ElementTagName>
|
|
|
6
6
|
when(condition: WhenCondition, ...content: WhenContent<TTagName>[]): WhenBuilderImpl<TTagName>;
|
|
7
7
|
else(...content: WhenContent<TTagName>[]): WhenBuilderImpl<TTagName>;
|
|
8
8
|
render(host: ExpandedElement<TTagName>, index: number): Node | null;
|
|
9
|
+
private freshRender;
|
|
10
|
+
private hydrateRender;
|
|
11
|
+
private createRuntimeFromMarkers;
|
|
9
12
|
}
|
|
10
13
|
export declare function createWhenBuilderFunction<TTagName extends ElementTagName>(builder: WhenBuilderImpl<TTagName>): WhenBuilder<TTagName>;
|
|
11
14
|
export { WhenBuilderImpl };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../src/when/builder.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAA0B,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../src/when/builder.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAA0B,MAAM,WAAW,CAAC;AAMpF,cAAM,eAAe,CAAC,QAAQ,SAAS,cAAc,GAAG,cAAc;IACpE,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,WAAW,CAA+B;gBAEtC,gBAAgB,EAAE,aAAa,EAAE,GAAG,OAAO,EAAE,WAAW,CAAC,QAAQ,CAAC,EAAE;IAIhF,IAAI,CAAC,SAAS,EAAE,aAAa,EAAE,GAAG,OAAO,EAAE,WAAW,CAAC,QAAQ,CAAC,EAAE,GAAG,eAAe,CAAC,QAAQ,CAAC;IAK9F,IAAI,CAAC,GAAG,OAAO,EAAE,WAAW,CAAC,QAAQ,CAAC,EAAE,GAAG,eAAe,CAAC,QAAQ,CAAC;IAKpE,MAAM,CAAC,IAAI,EAAE,eAAe,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAYnE,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,aAAa;IA4CrB,OAAO,CAAC,wBAAwB;CAwBjC;AAED,wBAAgB,yBAAyB,CAAC,QAAQ,SAAS,cAAc,EACvE,OAAO,EAAE,eAAe,CAAC,QAAQ,CAAC,GACjC,WAAW,CAAC,QAAQ,CAAC,CAevB;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
|
package/dist/when/runtime.d.ts
CHANGED
|
@@ -21,6 +21,11 @@ export interface WhenRuntime<TTagName extends ElementTagName = ElementTagName> {
|
|
|
21
21
|
activeIndex: number | -1 | null;
|
|
22
22
|
update(): void;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Evaluates which condition branch should be active.
|
|
26
|
+
* Returns the index of the first truthy condition, -1 for else branch, or null for no match.
|
|
27
|
+
*/
|
|
28
|
+
export declare function evaluateActiveCondition<TTagName extends ElementTagName>(groups: ReadonlyArray<WhenGroup<TTagName>>, elseContent: ReadonlyArray<WhenContent<TTagName>>): number | -1 | null;
|
|
24
29
|
/**
|
|
25
30
|
* Main render function for when/else conditionals.
|
|
26
31
|
* Evaluates conditions, clears old content, and renders the active branch.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/when/runtime.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,CAAC;AACtD,MAAM,MAAM,WAAW,CAAC,QAAQ,SAAS,cAAc,GAAG,cAAc,IACtE,OAAO,CAAC,QAAQ,CAAC,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,MAAM,WAAW,SAAS,CAAC,QAAQ,SAAS,cAAc,GAAG,cAAc;IACzE,SAAS,EAAE,aAAa,CAAC;IACzB,OAAO,EAAE,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,WAAW,CAAC,QAAQ,SAAS,cAAc,GAAG,cAAc;IAC3E,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;IAC9B,WAAW,EAAE,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;IACrC;;;;;OAKG;IACH,WAAW,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC;IAChC,MAAM,IAAI,IAAI,CAAC;CAChB;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/when/runtime.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,CAAC;AACtD,MAAM,MAAM,WAAW,CAAC,QAAQ,SAAS,cAAc,GAAG,cAAc,IACtE,OAAO,CAAC,QAAQ,CAAC,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,MAAM,WAAW,SAAS,CAAC,QAAQ,SAAS,cAAc,GAAG,cAAc;IACzE,SAAS,EAAE,aAAa,CAAC;IACzB,OAAO,EAAE,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,WAAW,CAAC,QAAQ,SAAS,cAAc,GAAG,cAAc;IAC3E,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;IAC9B,WAAW,EAAE,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;IACrC;;;;;OAKG;IACH,WAAW,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC;IAChC,MAAM,IAAI,IAAI,CAAC;CAChB;AAaD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,SAAS,cAAc,EACrE,MAAM,EAAE,aAAa,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,EAC1C,WAAW,EAAE,aAAa,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,GAChD,MAAM,GAAG,CAAC,CAAC,GAAG,IAAI,CAOpB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,SAAS,cAAc,EAC/D,OAAO,EAAE,WAAW,CAAC,QAAQ,CAAC,GAC7B,IAAI,CAoBN;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,SAAS,cAAc,EACjE,OAAO,EAAE,WAAW,CAAC,QAAQ,CAAC,GAC7B,IAAI,CAQN;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,IAAI,CAiC5D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
|
package/package.json
CHANGED
|
@@ -1,64 +1,64 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
2
|
+
"name": "nuclo",
|
|
3
|
+
"private": false,
|
|
4
|
+
"version": "0.1.115",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/nuclo.cjs",
|
|
7
|
+
"module": "./dist/nuclo.js",
|
|
8
|
+
"browser": "./dist/nuclo.umd.js",
|
|
9
|
+
"types": "./types/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/nuclo.js",
|
|
14
|
+
"require": "./dist/nuclo.cjs"
|
|
15
|
+
},
|
|
16
|
+
"./ssr": {
|
|
17
|
+
"types": "./dist/ssr/index.d.ts",
|
|
18
|
+
"import": "./dist/ssr/nuclo.ssr.js",
|
|
19
|
+
"require": "./dist/ssr/nuclo.ssr.cjs"
|
|
20
|
+
},
|
|
21
|
+
"./types": {
|
|
22
|
+
"types": "./types/index.d.ts"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"types"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsdown && tsc --emitDeclarationOnly",
|
|
31
|
+
"dev": "tsdown --watch",
|
|
32
|
+
"dev:all": "bun dev & cd examples/basic && bun run dev",
|
|
33
|
+
"build:watch": "tsdown --watch",
|
|
34
|
+
"build:types": "tsc --emitDeclarationOnly",
|
|
35
|
+
"clean": "rm -rf dist *.tsbuildinfo",
|
|
36
|
+
"test": "vitest run --coverage",
|
|
37
|
+
"test:watch": "vitest --watch --coverage",
|
|
38
|
+
"lint": "eslint . --ext .ts,.js,.json,.jsonc,.json5,.md,.css",
|
|
39
|
+
"lint:fix": "eslint . --ext .ts,.js,.json,.jsonc,.json5,.md,.css --fix"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@eslint/css": "^1.1.0",
|
|
43
|
+
"@eslint/js": "^10.0.1",
|
|
44
|
+
"@eslint/json": "^1.2.0",
|
|
45
|
+
"@eslint/markdown": "^8.0.1",
|
|
46
|
+
"@types/node": "^25.6.0",
|
|
47
|
+
"@vitest/coverage-v8": "4.1.4",
|
|
48
|
+
"eslint": "^10.2.0",
|
|
49
|
+
"globals": "^17.4.0",
|
|
50
|
+
"jsdom": "^29.0.2",
|
|
51
|
+
"tsdown": "^0.21.7",
|
|
52
|
+
"typescript": "^6.0.2",
|
|
53
|
+
"typescript-eslint": "^8.58.1",
|
|
54
|
+
"vitest": "^4.1.4"
|
|
55
|
+
},
|
|
56
|
+
"repository": {
|
|
57
|
+
"type": "git",
|
|
58
|
+
"url": "git+https://github.com/dan2dev/nuclo.git"
|
|
59
|
+
},
|
|
60
|
+
"bugs": {
|
|
61
|
+
"url": "https://github.com/dan2dev/nuclo/issues"
|
|
62
|
+
},
|
|
63
|
+
"homepage": "https://nuclo.dan2.dev/"
|
|
64
|
+
}
|
|
@@ -23,6 +23,27 @@ declare global {
|
|
|
23
23
|
parent?: Element,
|
|
24
24
|
index?: number
|
|
25
25
|
): ExpandedElement<TTagName>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Hydrates an existing server-rendered DOM tree by walking it in parallel with
|
|
29
|
+
* the component tree, reusing existing nodes and re-attaching reactivity.
|
|
30
|
+
*
|
|
31
|
+
* @param nodeModFn The NodeModFn to hydrate (same component used for SSR)
|
|
32
|
+
* @param parent The parent element containing the SSR HTML (defaults to document.body)
|
|
33
|
+
* @returns The hydrated root element
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* // Server: const html = renderToString(div(h1("Hello")));
|
|
38
|
+
* // Client:
|
|
39
|
+
* const app = document.getElementById("app")!;
|
|
40
|
+
* hydrate(div(h1("Hello")), app);
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
function hydrate<TTagName extends ElementTagName = ElementTagName>(
|
|
44
|
+
nodeModFn: NodeModFn<TTagName>,
|
|
45
|
+
parent?: Element,
|
|
46
|
+
): ExpandedElement<TTagName>;
|
|
26
47
|
}
|
|
27
48
|
|
|
28
49
|
export {};
|