UNPKG

flexium

Version:

A lightweight, signals-based UI framework with cross-platform renderers

1 lines 7.18 kB
{"version":3,"sources":["../src/renderers/dom/render.ts"],"names":["render","vnode","container","renderReactive","createRoot","createReactiveRoot"],"mappings":"uCA0CO,SAASA,EACdC,GAAAA,CACAC,CAAAA,CACa,CAEb,OAAOC,EAAeF,GAAAA,CAAOC,CAAS,CACxC,CAsLO,SAASE,CAAAA,CAAWF,CAAAA,CAAwB,CAEjD,OAAOG,CAAAA,CAAmBH,CAAS,CACrC","file":"chunk-DKSTVAO2.mjs","sourcesContent":["/**\n * DOM Render Function\n *\n * This module provides the main render function for mounting components to the DOM.\n * It includes simple reconciliation logic for mounting and unmounting components.\n *\n * Note: For reactive rendering with automatic signal tracking, use renderReactive\n * or createReactiveRoot from './reactive'.\n */\n\nimport type { VNode } from '../../core/renderer';\nimport { domRenderer } from './index';\nimport { isVNode } from './h';\nimport { renderReactive, createReactiveRoot } from './reactive';\n\n/**\n * Internal node data stored on DOM nodes\n */\ninterface NodeData {\n vnode: VNode | null;\n props: Record<string, any>;\n}\n\nconst NODE_DATA = new WeakMap<Node, NodeData>();\n\n/**\n * Render a component to a DOM container with automatic reactivity\n *\n * This function uses reactive rendering by default, which means:\n * - Signals passed as children automatically update the DOM\n * - Signals in props automatically update element properties\n * - Component functions automatically re-render when signals change\n *\n * @param vnode - Virtual node to render\n * @param container - DOM element to render into\n * @returns The rendered DOM node\n *\n * @example\n * const count = signal(0);\n * render(h('div', {}, [count]), document.body);\n * // The div will automatically update when count changes\n */\nexport function render(\n vnode: VNode | string | number | null | undefined | Function,\n container: HTMLElement\n): Node | null {\n // Use reactive rendering for automatic signal tracking\n return renderReactive(vnode, container);\n}\n\n/**\n * Mount a virtual node to create a DOM node\n */\nfunction mount(vnode: VNode | string | number | null | undefined | Function): Node | null {\n // Handle null/undefined\n if (vnode === null || vnode === undefined) {\n return null;\n }\n\n // Handle functions (lazy components)\n if (typeof vnode === 'function') {\n return mount(vnode());\n }\n\n // Handle text nodes\n if (typeof vnode === 'string' || typeof vnode === 'number') {\n return domRenderer.createTextNode(String(vnode));\n }\n\n // Handle VNodes\n if (isVNode(vnode)) {\n // Handle function components\n if (typeof vnode.type === 'function') {\n const component = vnode.type as Function;\n const result = component({ ...vnode.props, children: vnode.children });\n return mount(result);\n }\n\n // Handle fragments\n if (vnode.type === 'fragment') {\n const fragment = document.createDocumentFragment();\n for (const child of vnode.children) {\n const childNode = mount(child);\n if (childNode) {\n fragment.appendChild(childNode);\n }\n }\n return fragment;\n }\n\n // Handle built-in elements\n const node = domRenderer.createNode(vnode.type as string, vnode.props);\n\n // Store node data for future reconciliation\n NODE_DATA.set(node, {\n vnode,\n props: vnode.props,\n });\n\n // Mount children\n for (const child of vnode.children) {\n const childNode = mount(child);\n if (childNode) {\n domRenderer.appendChild(node, childNode);\n }\n }\n\n return node;\n }\n\n return null;\n}\n\n/**\n * Unmount a DOM node and clean up\n */\nfunction unmount(node: Node): void {\n // Clean up children first\n const childNodes = Array.from(node.childNodes);\n for (const child of childNodes) {\n unmount(child);\n }\n\n // Remove from parent\n if (node.parentNode) {\n node.parentNode.removeChild(node);\n }\n\n // Clean up stored data\n NODE_DATA.delete(node);\n}\n\n/**\n * Update an existing DOM node with a new virtual node\n * (Simple implementation - will be enhanced with proper reconciliation later)\n */\nexport function update(\n node: HTMLElement,\n oldVNode: VNode,\n newVNode: VNode\n): void {\n // If types don't match, replace the node\n if (oldVNode.type !== newVNode.type) {\n const newNode = mount(newVNode);\n if (newNode && node.parentNode) {\n node.parentNode.replaceChild(newNode, node);\n }\n unmount(node);\n return;\n }\n\n // Update props\n domRenderer.updateNode(node, oldVNode.props, newVNode.props);\n\n // Update stored data\n NODE_DATA.set(node, {\n vnode: newVNode,\n props: newVNode.props,\n });\n\n // Update children (simple approach for now)\n const oldChildren = oldVNode.children;\n const newChildren = newVNode.children;\n\n // Simple reconciliation: update/add/remove based on index\n const maxLength = Math.max(oldChildren.length, newChildren.length);\n\n for (let i = 0; i < maxLength; i++) {\n const oldChild = oldChildren[i];\n const newChild = newChildren[i];\n const childNode = node.childNodes[i];\n\n if (!newChild) {\n // Remove old child\n if (childNode) {\n unmount(childNode);\n }\n } else if (!oldChild) {\n // Add new child\n const newChildNode = mount(newChild);\n if (newChildNode) {\n domRenderer.appendChild(node, newChildNode);\n }\n } else if (typeof oldChild === 'string' || typeof oldChild === 'number') {\n // Update text node\n if (typeof newChild === 'string' || typeof newChild === 'number') {\n if (oldChild !== newChild && childNode) {\n domRenderer.updateTextNode(childNode as Text, String(newChild));\n }\n } else {\n // Replace text with element\n const newChildNode = mount(newChild);\n if (newChildNode && childNode) {\n node.replaceChild(newChildNode, childNode);\n unmount(childNode);\n }\n }\n } else if (isVNode(oldChild)) {\n if (typeof newChild === 'string' || typeof newChild === 'number') {\n // Replace element with text\n const newChildNode = mount(newChild);\n if (newChildNode && childNode) {\n node.replaceChild(newChildNode, childNode);\n unmount(childNode);\n }\n } else if (isVNode(newChild)) {\n // Update element\n if (childNode instanceof HTMLElement) {\n update(childNode, oldChild, newChild);\n }\n }\n }\n }\n}\n\n/**\n * Create a root for rendering with automatic reactivity\n *\n * This creates a root that supports fine-grained reactive updates.\n * Signals are automatically tracked and only the affected DOM nodes are updated.\n *\n * @param container - DOM element to render into\n * @returns Root object with render and unmount methods\n *\n * @example\n * const root = createRoot(document.body);\n * const count = signal(0);\n * root.render(h('div', {}, [count]));\n * // Later: count.value++ will automatically update the DOM\n */\nexport function createRoot(container: HTMLElement) {\n // Use reactive root for automatic signal tracking\n return createReactiveRoot(container);\n}\n"]}