@dom-expressions/tagged-jsx 0.50.0-next.15

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/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ const e=e=>t(e)||e>=48&&e<=58||e===46||e===45,t=e=>e>=65&&e<=90||e>=97&&e<=122||e===95||e===36,n=e=>e>=9&&e<=13||e===32,r=(r,i)=>{let a=[],o=0,s=``,c=0;for(let l=0;l<r.length;l++){let u=r[l],d=u.length;for(c=0;c<d;)switch(o){case 0:{s=``;let e=u.indexOf(`<`,c);e===-1?(c<d&&a.push({type:6,value:u.slice(c)}),c=d):(e>c&&a.push({type:6,value:u.slice(c,e)}),u[e+1]===`!`&&u[e+2]===`-`&&u[e+3]===`-`?(o=3,c=e+4):(a.push({type:0}),o=1,c=e+1));break}case 1:{let r=u.charCodeAt(c);if(n(r))c++;else if(r===62)i.has(s)&&a[a.length-2]?.type!==2?o=2:(o=0,s=``),a.push({type:1}),c++;else if(r===61)a.push({type:4}),c++;else if(r===47){let e=u.charCodeAt(c+1),t=u.slice(c+2).search(/\S/),n=e===47&&a[a.length-1]?.type===0&&t!==-1&&u[c+2+t]===`>`;e===47&&!n?o=4:e===42?o=5:(a.push({type:2}),c++)}else if(r===34||r===39){let e=u[c],t=u.indexOf(e,c+1);if(t===-1)throw Error(`Unterminated string at ${l}:${c}`);a.push({type:5,value:u.slice(c+1,t),quote:e}),c=t+1}else if(t(r)){let t=c;for(;c<d&&e(u.charCodeAt(c));)c++;let n=u.slice(t,c);s===``&&(s=n),a.push({type:3,value:n})}else if(r===46&&u[c+1]===`.`&&u[c+2]===`.`)a.push({type:8}),c+=3;else throw Error(`Unexpected Character: ${u[c]} at ${l}:${c}`);break}case 2:{let e=RegExp(`<\\s*/\\s*${s}\\s*>`,`g`);e.lastIndex=c;let t=e.exec(u);if(t){let e=t.index;e>c&&a.push({type:6,value:u.slice(c,e)}),o=0,c=e,s=``}else a.push({type:6,value:u.slice(c)}),c=d;break}case 3:case 4:case 5:{let e=o===4?`
2
+ `:o===5?`*/`:`-->`,t=u.indexOf(e,c);t===-1?c=d:(o=o===3?0:1,c=t+e.length);break}}l<r.length-1&&(o===0||o===1||o===2)&&a.push({type:7,value:l})}return a},i=e=>{let t=e.charCodeAt(0);return t>=65&&t<=90},a=(e,t)=>{let n={type:0,children:[]},r=[n],a=0,o=e.length;for(;a<o;){let n=e[a],s=r[r.length-1];switch(n.type){case 6:{let t=n.value;if(t.trim()===``){let t=e[a-1]?.type,n=e[a+1]?.type;if(t===1||n===0){a++;continue}}s.children.push({type:3,value:t}),a++;continue}case 7:s.children.push({type:4,value:n.value}),a++;continue;case 0:{let n=e[++a];if(n.type===2){let n=e[++a],i=e[++a],o=r[r.length-1];if(r.length>1&&i.type===1&&(n?.type===3&&o.name===n.value||(n?.type===7||n.type===2)&&typeof o.name==`number`)){let e=r.pop();e?.type===1&&t.has(e.name)&&(e.children=[]),a++;continue}throw Error(`Mismatched closing tag for <${o.name}>`)}if(n.type===3||n.type===7){let t=n.value,c={type:typeof t==`number`||i(t)?2:1,name:t,props:[],children:[]};for(s.children.push(c),a++;a<o;){let t=e[a];if(t.type===1||t.type===2)break;if(t.type===8){let t=e[a+1];if(t?.type===7)c.props.push({type:3,value:t.value}),a+=2;else throw Error(`Spread operator in <${c.name}> must be followed by an expression`)}else if(t.type===3){let n=t.value;if(e[a+1]?.type===4){a+=2;let t=e[a];if(t.type===7)c.props.push({name:n,type:2,value:t.value}),a++;else if(t.type===5){let e=t.quote;c.props.push({name:n,value:t.value,quote:e,type:1}),a++}else throw Error(`Attribute value for "${n}" in <${c.name}> must be an expression or a string`)}else c.props.push({type:0,name:n,value:!0}),a++}else throw Error(`Invalid attribute in <${c.name}>`)}let l=e[a];l.type===2?a+=2:l.type===1&&(a++,r.push(c));continue}}default:throw Error(`Unexpected token: ${JSON.stringify(n)} after <${r[r.length-1].name}>`)}}if(r.length>1)throw Error(`Unclosed tag for <${r[r.length-1].name}>`);return n},o=e=>e.length===1?e[0]:e;function s(e){let t=new WeakMap,n=new Set(e.RawTextElements);n.delete(`template`);let i=document.createTreeWalker(document,129),s=t=>e.SVGElements.has(t)?document.createElementNS(`http://www.w3.org/2000/svg`,t):e.MathMLElements.has(t)?document.createElementNS(`http://www.w3.org/1998/Math/MathML`,t):document.createElement(t),c=e=>{let t=(t,...n)=>m(l(t),n,e);return t.components=e,t.jsx=t,t.define=t=>c({...e,...t}),t},l=i=>{let o=t.get(i);return o||(o=a(r(i,n),e.VoidElements),u(o,!1),t.set(i,o)),o},u=(e,t)=>{if(e.type===1){if(!t){let n=document.createElement(`template`);n.content.appendChild(f(e)),e.template=n,t=!0}e.children.forEach(e=>u(e,t))}else e.type===2||e.type===0?e.children.forEach(e=>u(e,!1)):e.type===3&&!t&&(d.innerHTML=e.value,e.value=d.content.textContent??``)},d=document.createElement(`template`),f=e=>{switch(e.type){case 3:return d.innerHTML=e.value,document.createTextNode(d.content.textContent??``);case 4:return document.createComment(`+`);case 2:return document.createComment(e.name);case 1:let t=!1,n=s(e.name);return e.props=e.props.filter(e=>e.type===1?e.name.startsWith(`prop:`)?!0:(n.setAttribute(e.name,e.value),t):e.type===0?(n.setAttribute(e.name,``),t):e.type===3?(t=!0,t):!0),(e.name===`template`?n.content:n).append(...e.children.map(f)),n}},p=(t,n,r)=>{switch(t.type){case 3:return t.value;case 4:return n[t.value];case 2:let i=typeof t.name==`string`?r[t.name]:n[t.name];if(i&&typeof i==`function`)return e.createComponent(i,h(t,n,r));throw Error(`Component "${t.name}" not found in registry`);case 1:let a=m(t,n,r),o=h(t,n,r);return e.spread(a,o,!0),a}},m=(t,n,r)=>{if(t.type!==1||!t.template)return o(t.children.map(e=>p(e,n,r)));let a=t.template.content.firstChild.cloneNode(!0);i.currentNode=a;let s=(t,i)=>{for(let a of t)if(a.type===1||a.type===4||a.type===2){let t=i.nextNode();if(a.type===4||a.type===2)e.insert(t.parentNode,p(a,n,r),t),i.currentNode=t;else{if(a.props.length){let i=h(a,n,r);e.spread(t,i,!0)}s(a.children,a.name===`template`?document.createTreeWalker(t.content,129):i)}}};return s(t.children,t.name===`template`?document.createTreeWalker(a.content,129):i),a},h=(t,n,r,i={})=>{for(let r of t.props)switch(r.type){case 0:i[r.name]=!0;break;case 1:i[r.name]=r.value;break;case 2:g(i,r.name,n[r.value]);break;case 3:let t=n[r.value];if(!t||typeof t!=`object`)throw Error(`Can only spread objects`);i=e.mergeProps(i,t);break}return t.type===2&&t.children.length&&Object.defineProperty(i,`children`,{get(){return m(t,n,r)}}),i},g=(e,t,n)=>{typeof n==`function`&&n.length===0&&t!==`ref`&&!t.startsWith(`on`)?Object.defineProperty(e,t,{get(){return n()},enumerable:!0}):e[t]=n};return c({})}export{s as createTaggedJSXRuntime};
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@dom-expressions/tagged-jsx",
3
+ "description": "A Fine-Grained Rendering Runtime API using Tagged Template Literals",
4
+ "version": "0.50.0-next.15",
5
+ "author": "Daniel Kling",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/ryansolid/dom-expressions.git",
10
+ "directory": "packages/tagged-jsx"
11
+ },
12
+ "main": "dist/index.mjs",
13
+ "module": "dist/index.mjs",
14
+ "types": "dist/index.d.mts",
15
+ "sideEffects": false,
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.mts",
19
+ "import": "./dist/index.mjs"
20
+ }
21
+ },
22
+ "devDependencies": {
23
+ "jsdom": "^21.0.0",
24
+ "tsdown": "^0.20.3",
25
+ "vitest": "^4.0.18",
26
+ "@dom-expressions/runtime": "0.50.0-next.15"
27
+ },
28
+ "scripts": {
29
+ "build": "tsdown src/index.ts --format esm --minify",
30
+ "test": "vitest --run"
31
+ }
32
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { createTaggedJSXRuntime } from "./tagged-jsx";
2
+ export type { TaggedJSXInstance, Runtime, ComponentRegistry, FunctionComponent } from "./types";
package/src/parse.ts ADDED
@@ -0,0 +1,257 @@
1
+ import {
2
+ STRING_TOKEN,
3
+ CLOSE_TAG_TOKEN,
4
+ EQUALS_TOKEN,
5
+ EXPRESSION_TOKEN,
6
+ IDENTIFIER_TOKEN,
7
+ OPEN_TAG_TOKEN,
8
+ SLASH_TOKEN,
9
+ SPREAD_TOKEN,
10
+ TEXT_TOKEN,
11
+ Token
12
+ } from "./tokenize";
13
+
14
+ const isComponentNode = (name: string): boolean => {
15
+ const char = name.charCodeAt(0);
16
+ return (
17
+ char >= 65 && char <= 90 // Uppercase A-Z
18
+ );
19
+ };
20
+
21
+ // Node type constants
22
+ export const ROOT_NODE = 0;
23
+ export const ELEMENT_NODE = 1;
24
+ export const COMPONENT_NODE = 2;
25
+ export const TEXT_NODE = 3;
26
+ export const EXPRESSION_NODE = 4;
27
+
28
+ // Prop type constants
29
+ export const BOOLEAN_PROP = 0;
30
+ export const STATIC_PROP = 1;
31
+ export const EXPRESSION_PROP = 2;
32
+ export const SPREAD_PROP = 3;
33
+
34
+ export type NodeType =
35
+ | typeof ROOT_NODE
36
+ | typeof ELEMENT_NODE
37
+ | typeof COMPONENT_NODE
38
+ | typeof TEXT_NODE
39
+ | typeof EXPRESSION_NODE;
40
+ export type PropType =
41
+ | typeof BOOLEAN_PROP
42
+ | typeof STATIC_PROP
43
+ | typeof EXPRESSION_PROP
44
+ | typeof SPREAD_PROP;
45
+
46
+ export type ChildNode = ElementNode | TextNode | ExpressionNode | ComponentNode;
47
+
48
+ export interface RootNode {
49
+ type: typeof ROOT_NODE;
50
+ children: ChildNode[];
51
+ }
52
+
53
+ export interface ElementNode {
54
+ type: typeof ELEMENT_NODE;
55
+ name: string;
56
+ props: PropNode[];
57
+ children: ChildNode[];
58
+ template?: HTMLTemplateElement;
59
+ }
60
+
61
+ export interface ComponentNode {
62
+ type: typeof COMPONENT_NODE;
63
+ name: string | number;
64
+ props: PropNode[];
65
+ children: ChildNode[];
66
+ }
67
+
68
+ export interface TextNode {
69
+ type: typeof TEXT_NODE;
70
+ value: string;
71
+ }
72
+
73
+ export interface ExpressionNode {
74
+ type: typeof EXPRESSION_NODE;
75
+ value: number;
76
+ }
77
+
78
+ export interface BooleanProp {
79
+ name: string;
80
+ type: typeof BOOLEAN_PROP;
81
+ value: boolean;
82
+ }
83
+
84
+ export interface StringProp {
85
+ name: string;
86
+ type: typeof STATIC_PROP;
87
+ value: string;
88
+ quote?: "'" | '"';
89
+ }
90
+
91
+ export interface ExpressionProp {
92
+ name: string;
93
+ type: typeof EXPRESSION_PROP;
94
+ value: number;
95
+ }
96
+
97
+ export interface SpreadProp {
98
+ type: typeof SPREAD_PROP;
99
+ value: number;
100
+ }
101
+
102
+ export type PropNode = BooleanProp | StringProp | ExpressionProp | SpreadProp;
103
+
104
+ export const parse = (tokens: Token[], voidElements: Set<string>): RootNode => {
105
+ const root: RootNode = { type: ROOT_NODE, children: [] };
106
+ const stack: (RootNode | ElementNode | ComponentNode)[] = [root];
107
+ let pos = 0;
108
+ const len = tokens.length;
109
+
110
+ while (pos < len) {
111
+ const token = tokens[pos];
112
+ const parent = stack[stack.length - 1];
113
+
114
+ switch (token.type) {
115
+ case TEXT_TOKEN: {
116
+ // --- TEXT ---
117
+ const value = token.value;
118
+ if (value.trim() === "") {
119
+ const prevType = tokens[pos - 1]?.type;
120
+ const nextType = tokens[pos + 1]?.type;
121
+ // Filter out empty text nodes between tags
122
+ if (prevType === CLOSE_TAG_TOKEN || nextType === OPEN_TAG_TOKEN) {
123
+ pos++;
124
+ continue;
125
+ }
126
+ }
127
+ parent.children.push({ type: TEXT_NODE, value });
128
+ pos++;
129
+ continue;
130
+ }
131
+
132
+ case EXPRESSION_TOKEN: {
133
+ // --- EXPRESSION ---
134
+ parent.children.push({ type: EXPRESSION_NODE, value: token.value });
135
+ pos++;
136
+ continue;
137
+ }
138
+
139
+ case OPEN_TAG_TOKEN: {
140
+ // --- TAG ---
141
+ const nextToken = tokens[++pos];
142
+
143
+ // Handle Closing Tag: </name>
144
+ if (nextToken.type === SLASH_TOKEN) {
145
+ const nameToken = tokens[++pos];
146
+ const closeToken = tokens[++pos];
147
+ const currentParent = stack[stack.length - 1] as ElementNode | ComponentNode;
148
+ if (
149
+ stack.length > 1 &&
150
+ closeToken.type === CLOSE_TAG_TOKEN &&
151
+ ((nameToken?.type === IDENTIFIER_TOKEN && currentParent.name === nameToken.value) ||
152
+ ((nameToken?.type === EXPRESSION_TOKEN || nameToken.type === SLASH_TOKEN) &&
153
+ typeof currentParent.name === "number"))
154
+ ) {
155
+ const node = stack.pop();
156
+ if (node?.type === ELEMENT_NODE && voidElements.has(node.name)) {
157
+ node.children = [];
158
+ }
159
+ pos++;
160
+ continue;
161
+ }
162
+ throw new Error(`Mismatched closing tag for <${currentParent.name}>`);
163
+ }
164
+
165
+ // Handle Opening Tag: <name ...>
166
+ if (nextToken.type === IDENTIFIER_TOKEN || nextToken.type === EXPRESSION_TOKEN) {
167
+ const tagName = nextToken.value;
168
+ const node: ElementNode | ComponentNode = {
169
+ type:
170
+ typeof tagName === "number" || isComponentNode(tagName)
171
+ ? COMPONENT_NODE
172
+ : ELEMENT_NODE,
173
+ name: tagName,
174
+ props: [],
175
+ children: []
176
+ } as ElementNode | ComponentNode;
177
+ parent.children.push(node);
178
+ pos++; // Consume tag name
179
+
180
+ // --- Attribute Parsing Loop ---
181
+ while (pos < len) {
182
+ const attrToken = tokens[pos];
183
+ if (attrToken.type === CLOSE_TAG_TOKEN || attrToken.type === SLASH_TOKEN) {
184
+ break; // End of attributes
185
+ }
186
+
187
+ if (attrToken.type === SPREAD_TOKEN) {
188
+ const expr = tokens[pos + 1];
189
+ if (expr?.type === EXPRESSION_TOKEN) {
190
+ node.props.push({ type: SPREAD_PROP, value: expr.value });
191
+ pos += 2; // Consume '...' and expression
192
+ } else {
193
+ throw new Error(
194
+ `Spread operator in <${node.name}> must be followed by an expression`
195
+ );
196
+ }
197
+ } else if (attrToken.type === IDENTIFIER_TOKEN) {
198
+ const name = attrToken.value;
199
+ const next = tokens[pos + 1];
200
+
201
+ if (next?.type === EQUALS_TOKEN) {
202
+ pos += 2; // Consume name and '='
203
+ const valToken = tokens[pos];
204
+ if (valToken.type === EXPRESSION_TOKEN) {
205
+ node.props.push({ name, type: EXPRESSION_PROP, value: valToken.value });
206
+ pos++;
207
+ } else if (valToken.type === STRING_TOKEN) {
208
+ const quote = valToken.quote;
209
+ node.props.push({
210
+ name,
211
+ value: valToken.value,
212
+ quote,
213
+ type: STATIC_PROP
214
+ } as StringProp);
215
+ pos++;
216
+ } else {
217
+ throw new Error(
218
+ `Attribute value for "${name}" in <${node.name}> must be an expression or a string`
219
+ );
220
+ }
221
+ } else {
222
+ // Boolean prop
223
+ node.props.push({ type: BOOLEAN_PROP, name, value: true });
224
+ pos++;
225
+ }
226
+ } else {
227
+ throw new Error(`Invalid attribute in <${node.name}>`);
228
+ }
229
+ }
230
+
231
+ // --- Tag Closing Logic ---
232
+ const endToken = tokens[pos];
233
+ if (endToken.type === SLASH_TOKEN) {
234
+ // Self-closing: <div/>
235
+ pos += 2; // Consume '/' and '>'
236
+ } else if (endToken.type === CLOSE_TAG_TOKEN) {
237
+ // Opening: <div>
238
+ pos++; // Consume '>'
239
+ stack.push(node);
240
+ }
241
+ continue;
242
+ }
243
+ }
244
+
245
+ default:
246
+ throw new Error(
247
+ `Unexpected token: ${JSON.stringify(token)} after <${(stack[stack.length - 1] as ElementNode | ComponentNode).name}>`
248
+ );
249
+ }
250
+ }
251
+
252
+ if (stack.length > 1) {
253
+ throw new Error(`Unclosed tag for <${(stack[stack.length - 1] as ElementNode).name}>`);
254
+ }
255
+
256
+ return root;
257
+ };
@@ -0,0 +1,247 @@
1
+ import {
2
+ BOOLEAN_PROP,
3
+ COMPONENT_NODE,
4
+ ChildNode,
5
+ ComponentNode,
6
+ ELEMENT_NODE,
7
+ EXPRESSION_NODE,
8
+ EXPRESSION_PROP,
9
+ ElementNode,
10
+ ROOT_NODE,
11
+ RootNode,
12
+ SPREAD_PROP,
13
+ STATIC_PROP,
14
+ TEXT_NODE,
15
+ parse
16
+ } from "./parse";
17
+ import { tokenize } from "./tokenize";
18
+ import { ComponentRegistry, TaggedJSXInstance, Runtime } from "./types";
19
+ import { type JSX } from "../../runtime/src/jsx";
20
+
21
+ const flat = (arr: any[]) => {
22
+ return arr.length === 1 ? arr[0] : arr;
23
+ };
24
+
25
+ export function createTaggedJSXRuntime(r: Runtime) {
26
+ const cache = new WeakMap<TemplateStringsArray, RootNode>();
27
+ const rawTextElements = new Set(r.RawTextElements);
28
+ rawTextElements.delete("template");
29
+
30
+ // Walk over text, comment, and element nodes.
31
+ const walker = document.createTreeWalker(document, 129);
32
+
33
+ const createElement = (name: string) => {
34
+ return r.SVGElements.has(name)
35
+ ? document.createElementNS("http://www.w3.org/2000/svg", name)
36
+ : r.MathMLElements.has(name)
37
+ ? document.createElementNS("http://www.w3.org/1998/Math/MathML", name)
38
+ : document.createElement(name);
39
+ };
40
+
41
+ // Internal: build a tagged JSX tag bound to a component registry.
42
+ const createTaggedJSX = <T extends ComponentRegistry>(components: T): TaggedJSXInstance<T> => {
43
+ const tag = (strings: TemplateStringsArray, ...values: any[]) => {
44
+ const root = getCachedRoot(strings);
45
+
46
+ return renderChildren(root, values, components);
47
+ };
48
+ tag.components = components;
49
+ tag.jsx = tag;
50
+ tag.define = <TNew extends ComponentRegistry>(newComponents: TNew) => {
51
+ return createTaggedJSX({ ...components, ...newComponents });
52
+ };
53
+
54
+ return tag as TaggedJSXInstance<T>;
55
+ };
56
+
57
+ const getCachedRoot = (strings: TemplateStringsArray): RootNode => {
58
+ let root = cache.get(strings);
59
+ if (!root) {
60
+ root = parse(tokenize(strings, rawTextElements), r.VoidElements);
61
+ buildTemplate(root, false);
62
+ cache.set(strings, root);
63
+ }
64
+ return root;
65
+ };
66
+
67
+ // Build template elements with the same shape as the parsed tree so both can be walked in sync.
68
+ const buildTemplate = (node: RootNode | ChildNode, insideTemplate: boolean): void => {
69
+ if (node.type === ELEMENT_NODE) {
70
+ if (!insideTemplate) {
71
+ const template = document.createElement("template");
72
+ template.content.appendChild(buildNodes(node));
73
+ node.template = template;
74
+ insideTemplate = true;
75
+ }
76
+ node.children.forEach(child => buildTemplate(child, insideTemplate));
77
+ } else if (node.type === COMPONENT_NODE || node.type === ROOT_NODE) {
78
+ node.children.forEach(child => buildTemplate(child, false));
79
+ } else if (node.type === TEXT_NODE && !insideTemplate) {
80
+ textTemplate.innerHTML = node.value;
81
+ node.value = textTemplate.content.textContent ?? "";
82
+ }
83
+ };
84
+
85
+ const textTemplate = document.createElement("template");
86
+
87
+ const buildNodes = (node: ChildNode): Node => {
88
+ switch (node.type) {
89
+ case TEXT_NODE:
90
+ textTemplate.innerHTML = node.value;
91
+ return document.createTextNode(textTemplate.content.textContent ?? "");
92
+ case EXPRESSION_NODE:
93
+ return document.createComment("+");
94
+ case COMPONENT_NODE:
95
+ return document.createComment(node.name as string);
96
+ case ELEMENT_NODE:
97
+ let hasSpread = false;
98
+
99
+ const elem = createElement(node.name);
100
+ // Props located after spread need to be applied after spread for possible overrides.
101
+ node.props = node.props.filter(prop => {
102
+ if (prop.type === STATIC_PROP) {
103
+ if (prop.name.startsWith("prop:")) return true;
104
+ elem.setAttribute(prop.name, prop.value);
105
+ return hasSpread;
106
+ } else if (prop.type === BOOLEAN_PROP) {
107
+ elem.setAttribute(prop.name, "");
108
+ return hasSpread;
109
+ } else if (prop.type === SPREAD_PROP) {
110
+ hasSpread = true;
111
+ return hasSpread;
112
+ }
113
+ return true;
114
+ });
115
+ const childRoot = node.name === "template" ? (elem as HTMLTemplateElement).content : elem;
116
+ childRoot.append(...node.children.map(buildNodes));
117
+
118
+ return elem;
119
+ }
120
+ };
121
+
122
+ const renderNode = (node: ChildNode, values: any[], components: ComponentRegistry): any => {
123
+ switch (node.type) {
124
+ case TEXT_NODE:
125
+ return node.value;
126
+ case EXPRESSION_NODE:
127
+ return values[node.value];
128
+ case COMPONENT_NODE:
129
+ const component = typeof node.name === "string" ? components[node.name] : values[node.name];
130
+ if (component && typeof component === "function") {
131
+ return r.createComponent(component, gatherProps(node, values, components));
132
+ } else {
133
+ throw new Error(`Component "${node.name}" not found in registry`);
134
+ }
135
+ case ELEMENT_NODE:
136
+ const element = renderChildren(node, values, components) as Element;
137
+ const props = gatherProps(node, values, components);
138
+
139
+ r.spread(element, props, true);
140
+
141
+ return element;
142
+ }
143
+ };
144
+
145
+ const renderChildren = (
146
+ node: RootNode | ComponentNode | ElementNode,
147
+ values: any[],
148
+ components: ComponentRegistry
149
+ ): JSX.Element => {
150
+ if (node.type !== ELEMENT_NODE || !node.template) {
151
+ return flat(node.children.map(n => renderNode(n, values, components)));
152
+ }
153
+
154
+ const element = node.template.content.firstChild!.cloneNode(true) as Element;
155
+ walker.currentNode = element;
156
+
157
+ const walkNodes = (nodes: ChildNode[], walker: TreeWalker) => {
158
+ for (const node of nodes) {
159
+ if (
160
+ node.type === ELEMENT_NODE ||
161
+ node.type === EXPRESSION_NODE ||
162
+ node.type === COMPONENT_NODE
163
+ ) {
164
+ const domNode = walker.nextNode()!;
165
+ if (node.type === EXPRESSION_NODE || node.type === COMPONENT_NODE) {
166
+ r.insert(domNode.parentNode!, renderNode(node, values, components), domNode);
167
+ walker.currentNode = domNode;
168
+ } else {
169
+ if (node.props.length) {
170
+ const props = gatherProps(node, values, components);
171
+ r.spread(domNode as Element, props, true);
172
+ }
173
+ walkNodes(
174
+ node.children,
175
+ node.name === "template"
176
+ ? document.createTreeWalker((domNode as HTMLTemplateElement).content, 129)
177
+ : walker
178
+ );
179
+ }
180
+ }
181
+ }
182
+ };
183
+ walkNodes(
184
+ node.children,
185
+ node.name === "template"
186
+ ? document.createTreeWalker((element as HTMLTemplateElement).content, 129)
187
+ : walker
188
+ );
189
+ return element;
190
+ };
191
+
192
+ const gatherProps = (
193
+ node: ElementNode | ComponentNode,
194
+ values: any[],
195
+ components: ComponentRegistry,
196
+ props: Record<string, any> = {}
197
+ ) => {
198
+ for (const prop of node.props) {
199
+ switch (prop.type) {
200
+ case BOOLEAN_PROP:
201
+ props[prop.name] = true;
202
+ break;
203
+ case STATIC_PROP:
204
+ props[prop.name] = prop.value;
205
+ break;
206
+ case EXPRESSION_PROP:
207
+ applyGetter(props, prop.name, values[prop.value]);
208
+ break;
209
+ case SPREAD_PROP:
210
+ const spread = values[prop.value];
211
+ if (!spread || typeof spread !== "object") throw new Error("Can only spread objects");
212
+ props = r.mergeProps(props, spread);
213
+ break;
214
+ }
215
+ }
216
+
217
+ // children - childNodes overwrites any props.children
218
+ if (node.type === COMPONENT_NODE && node.children.length) {
219
+ Object.defineProperty(props, "children", {
220
+ get() {
221
+ return renderChildren(node, values, components);
222
+ }
223
+ });
224
+ }
225
+ return props;
226
+ };
227
+
228
+ const applyGetter = (props: Record<string, any>, name: string, value: any) => {
229
+ if (
230
+ typeof value === "function" &&
231
+ value.length === 0 &&
232
+ name !== "ref" &&
233
+ !name.startsWith("on")
234
+ ) {
235
+ Object.defineProperty(props, name, {
236
+ get() {
237
+ return value();
238
+ },
239
+ enumerable: true
240
+ });
241
+ } else {
242
+ props[name] = value;
243
+ }
244
+ };
245
+
246
+ return createTaggedJSX({});
247
+ }