@solidjs/html 2.0.0-rc.2 → 2.0.0-rc.3
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/README.md +1 -1
- package/dist/html.cjs +520 -13
- package/dist/html.js +521 -14
- package/package.json +11 -11
- package/types/index.d.ts +1 -3
- package/types/parse.d.ts +67 -0
- package/types/tagged-jsx.d.ts +4 -12567
- package/types/tokenize.d.ts +43 -0
- package/types/types.d.ts +51 -0
- package/types-cjs/index.d.cts +1 -3
- package/types-cjs/parse.d.cts +67 -0
- package/types-cjs/tagged-jsx.d.cts +4 -12567
- package/types-cjs/tokenize.d.cts +43 -0
- package/types-cjs/types.d.cts +51 -0
- package/LICENSE +0 -21
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ This sub module provides a Tagged Template Literal `html` method for Solid. This
|
|
|
4
4
|
|
|
5
5
|
`html` uses `${}` to escape into JavaScript expressions. Components are closed with `<//>`.
|
|
6
6
|
|
|
7
|
-
Since Solid 2.0, `html` is
|
|
7
|
+
Since Solid 2.0, `html` is an AST-based tagged-template runtime in this package. Templates are parsed at runtime (no `new Function` / `eval`, so it is CSP-safe) and reactive bindings are installed against the resulting DOM.
|
|
8
8
|
|
|
9
9
|
For editor support, the [Tagged JSX Tools VS Code extension](https://marketplace.visualstudio.com/items?itemName=DanielRKling.tagged-jsx-vscode) provides syntax highlighting, formatting, conversion commands, and TypeScript diagnostics for JSX inside tagged template literals.
|
|
10
10
|
|
package/dist/html.cjs
CHANGED
|
@@ -2,19 +2,526 @@
|
|
|
2
2
|
|
|
3
3
|
var web = require('@solidjs/web');
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
|
|
5
|
+
const OPEN_TAG_TOKEN = 0;
|
|
6
|
+
const CLOSE_TAG_TOKEN = 1;
|
|
7
|
+
const SLASH_TOKEN = 2;
|
|
8
|
+
const IDENTIFIER_TOKEN = 3;
|
|
9
|
+
const EQUALS_TOKEN = 4;
|
|
10
|
+
const STRING_TOKEN = 5;
|
|
11
|
+
const TEXT_TOKEN = 6;
|
|
12
|
+
const EXPRESSION_TOKEN = 7;
|
|
13
|
+
const SPREAD_TOKEN = 8;
|
|
14
|
+
const isIdentifierChar = code => {
|
|
15
|
+
return isIdentifierStart(code) || code >= 48 && code <= 58 ||
|
|
16
|
+
code === 46 ||
|
|
17
|
+
code === 45
|
|
18
|
+
;
|
|
19
|
+
};
|
|
20
|
+
const isIdentifierStart = code => {
|
|
21
|
+
return code >= 65 && code <= 90 ||
|
|
22
|
+
code >= 97 && code <= 122 ||
|
|
23
|
+
code === 95 ||
|
|
24
|
+
code === 36
|
|
25
|
+
;
|
|
26
|
+
};
|
|
27
|
+
const isWhitespace = code => {
|
|
28
|
+
return code >= 9 && code <= 13 || code === 32;
|
|
29
|
+
};
|
|
30
|
+
const STATE_TEXT = 0;
|
|
31
|
+
const STATE_TAG = 1;
|
|
32
|
+
const STATE_RAW_TEXT = 2;
|
|
33
|
+
const STATE_COMMENT = 3;
|
|
34
|
+
const STATE_LINE_COMMENT = 4;
|
|
35
|
+
const STATE_BLOCK_COMMENT = 5;
|
|
36
|
+
const tokenize = (strings, rawTextElements) => {
|
|
37
|
+
const tokens = [];
|
|
38
|
+
let state = STATE_TEXT;
|
|
39
|
+
let lastTagName = "";
|
|
40
|
+
let cursor = 0;
|
|
41
|
+
for (let i = 0; i < strings.length; i++) {
|
|
42
|
+
const str = strings[i];
|
|
43
|
+
const len = str.length;
|
|
44
|
+
cursor = 0;
|
|
45
|
+
while (cursor < len) {
|
|
46
|
+
switch (state) {
|
|
47
|
+
case STATE_TEXT:
|
|
48
|
+
{
|
|
49
|
+
lastTagName = "";
|
|
50
|
+
const nextTag = str.indexOf("<", cursor);
|
|
51
|
+
if (nextTag === -1) {
|
|
52
|
+
if (cursor < len) tokens.push({
|
|
53
|
+
type: TEXT_TOKEN,
|
|
54
|
+
value: str.slice(cursor)
|
|
55
|
+
});
|
|
56
|
+
cursor = len;
|
|
57
|
+
} else {
|
|
58
|
+
if (nextTag > cursor) tokens.push({
|
|
59
|
+
type: TEXT_TOKEN,
|
|
60
|
+
value: str.slice(cursor, nextTag)
|
|
61
|
+
});
|
|
62
|
+
if (str[nextTag + 1] === "!" && str[nextTag + 2] === "-" && str[nextTag + 3] === "-") {
|
|
63
|
+
state = STATE_COMMENT;
|
|
64
|
+
cursor = nextTag + 4;
|
|
65
|
+
} else {
|
|
66
|
+
tokens.push({
|
|
67
|
+
type: OPEN_TAG_TOKEN
|
|
68
|
+
});
|
|
69
|
+
state = STATE_TAG;
|
|
70
|
+
cursor = nextTag + 1;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
case STATE_TAG:
|
|
76
|
+
{
|
|
77
|
+
const code = str.charCodeAt(cursor);
|
|
78
|
+
if (isWhitespace(code)) {
|
|
79
|
+
cursor++;
|
|
80
|
+
} else if (code === 62) {
|
|
81
|
+
if (rawTextElements.has(lastTagName) && tokens[tokens.length - 2]?.type !== SLASH_TOKEN) {
|
|
82
|
+
state = STATE_RAW_TEXT;
|
|
83
|
+
} else {
|
|
84
|
+
state = STATE_TEXT;
|
|
85
|
+
lastTagName = "";
|
|
86
|
+
}
|
|
87
|
+
tokens.push({
|
|
88
|
+
type: CLOSE_TAG_TOKEN
|
|
89
|
+
});
|
|
90
|
+
cursor++;
|
|
91
|
+
} else if (code === 61) {
|
|
92
|
+
tokens.push({
|
|
93
|
+
type: EQUALS_TOKEN
|
|
94
|
+
});
|
|
95
|
+
cursor++;
|
|
96
|
+
} else if (code === 47) {
|
|
97
|
+
const next = str.charCodeAt(cursor + 1);
|
|
98
|
+
const nextNonWhitespace = str.slice(cursor + 2).search(/\S/);
|
|
99
|
+
const isShorthandClosingTag = next === 47 && tokens[tokens.length - 1]?.type === OPEN_TAG_TOKEN && nextNonWhitespace !== -1 && str[cursor + 2 + nextNonWhitespace] === ">";
|
|
100
|
+
if (next === 47 && !isShorthandClosingTag) {
|
|
101
|
+
state = STATE_LINE_COMMENT;
|
|
102
|
+
} else if (next === 42) {
|
|
103
|
+
state = STATE_BLOCK_COMMENT;
|
|
104
|
+
} else {
|
|
105
|
+
tokens.push({
|
|
106
|
+
type: SLASH_TOKEN
|
|
107
|
+
});
|
|
108
|
+
cursor++;
|
|
109
|
+
}
|
|
110
|
+
} else if (code === 34 || code === 39) {
|
|
111
|
+
const char = str[cursor];
|
|
112
|
+
const endQuoteIndex = str.indexOf(char, cursor + 1);
|
|
113
|
+
if (endQuoteIndex === -1) {
|
|
114
|
+
throw new Error(`Unterminated string at ${i}:${cursor}`);
|
|
115
|
+
}
|
|
116
|
+
tokens.push({
|
|
117
|
+
type: STRING_TOKEN,
|
|
118
|
+
value: str.slice(cursor + 1, endQuoteIndex),
|
|
119
|
+
quote: char
|
|
120
|
+
});
|
|
121
|
+
cursor = endQuoteIndex + 1;
|
|
122
|
+
} else if (isIdentifierStart(code)) {
|
|
123
|
+
const start = cursor;
|
|
124
|
+
while (cursor < len && isIdentifierChar(str.charCodeAt(cursor))) cursor++;
|
|
125
|
+
const value = str.slice(start, cursor);
|
|
126
|
+
if (lastTagName === "") {
|
|
127
|
+
lastTagName = value;
|
|
128
|
+
}
|
|
129
|
+
tokens.push({
|
|
130
|
+
type: IDENTIFIER_TOKEN,
|
|
131
|
+
value
|
|
132
|
+
});
|
|
133
|
+
} else if (code === 46 && str[cursor + 1] === "." && str[cursor + 2] === ".") {
|
|
134
|
+
tokens.push({
|
|
135
|
+
type: SPREAD_TOKEN
|
|
136
|
+
});
|
|
137
|
+
cursor += 3;
|
|
138
|
+
} else {
|
|
139
|
+
throw new Error(`Unexpected Character: ${str[cursor]} at ${i}:${cursor}`);
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case STATE_RAW_TEXT:
|
|
144
|
+
{
|
|
145
|
+
const closeTagRegex = new RegExp(`<\\s*/\\s*${lastTagName}\\s*>`, "g");
|
|
146
|
+
closeTagRegex.lastIndex = cursor;
|
|
147
|
+
const match = closeTagRegex.exec(str);
|
|
148
|
+
if (match) {
|
|
149
|
+
const endOfRawIdx = match.index;
|
|
150
|
+
if (endOfRawIdx > cursor) {
|
|
151
|
+
tokens.push({
|
|
152
|
+
type: TEXT_TOKEN,
|
|
153
|
+
value: str.slice(cursor, endOfRawIdx)
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
state = STATE_TEXT;
|
|
157
|
+
cursor = endOfRawIdx;
|
|
158
|
+
lastTagName = "";
|
|
159
|
+
} else {
|
|
160
|
+
tokens.push({
|
|
161
|
+
type: TEXT_TOKEN,
|
|
162
|
+
value: str.slice(cursor)
|
|
163
|
+
});
|
|
164
|
+
cursor = len;
|
|
165
|
+
}
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
case STATE_COMMENT:
|
|
169
|
+
case STATE_LINE_COMMENT:
|
|
170
|
+
case STATE_BLOCK_COMMENT:
|
|
171
|
+
{
|
|
172
|
+
const commentEnd = state === STATE_LINE_COMMENT ? "\n" : state === STATE_BLOCK_COMMENT ? "*/" : "-->";
|
|
173
|
+
const commentEndIndex = str.indexOf(commentEnd, cursor);
|
|
174
|
+
if (commentEndIndex === -1) {
|
|
175
|
+
cursor = len;
|
|
176
|
+
} else {
|
|
177
|
+
state = state === STATE_COMMENT ? STATE_TEXT : STATE_TAG;
|
|
178
|
+
cursor = commentEndIndex + commentEnd.length;
|
|
179
|
+
}
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (i < strings.length - 1) {
|
|
185
|
+
if (state === STATE_TEXT || state === STATE_TAG || state === STATE_RAW_TEXT) {
|
|
186
|
+
tokens.push({
|
|
187
|
+
type: EXPRESSION_TOKEN,
|
|
188
|
+
value: i
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return tokens;
|
|
194
|
+
};
|
|
7
195
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
196
|
+
const isComponentNode = name => {
|
|
197
|
+
const char = name.charCodeAt(0);
|
|
198
|
+
return char >= 65 && char <= 90
|
|
199
|
+
;
|
|
200
|
+
};
|
|
201
|
+
const ROOT_NODE = 0;
|
|
202
|
+
const ELEMENT_NODE = 1;
|
|
203
|
+
const COMPONENT_NODE = 2;
|
|
204
|
+
const TEXT_NODE = 3;
|
|
205
|
+
const EXPRESSION_NODE = 4;
|
|
206
|
+
const BOOLEAN_PROP = 0;
|
|
207
|
+
const STATIC_PROP = 1;
|
|
208
|
+
const EXPRESSION_PROP = 2;
|
|
209
|
+
const SPREAD_PROP = 3;
|
|
210
|
+
const parse = (tokens, voidElements) => {
|
|
211
|
+
const root = {
|
|
212
|
+
type: ROOT_NODE,
|
|
213
|
+
children: []
|
|
214
|
+
};
|
|
215
|
+
const stack = [root];
|
|
216
|
+
let pos = 0;
|
|
217
|
+
const len = tokens.length;
|
|
218
|
+
while (pos < len) {
|
|
219
|
+
const token = tokens[pos];
|
|
220
|
+
const parent = stack[stack.length - 1];
|
|
221
|
+
switch (token.type) {
|
|
222
|
+
case TEXT_TOKEN:
|
|
223
|
+
{
|
|
224
|
+
const value = token.value;
|
|
225
|
+
if (value.trim() === "") {
|
|
226
|
+
const prevType = tokens[pos - 1]?.type;
|
|
227
|
+
const nextType = tokens[pos + 1]?.type;
|
|
228
|
+
if (prevType === CLOSE_TAG_TOKEN || nextType === OPEN_TAG_TOKEN) {
|
|
229
|
+
pos++;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
parent.children.push({
|
|
234
|
+
type: TEXT_NODE,
|
|
235
|
+
value
|
|
236
|
+
});
|
|
237
|
+
pos++;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
case EXPRESSION_TOKEN:
|
|
241
|
+
{
|
|
242
|
+
parent.children.push({
|
|
243
|
+
type: EXPRESSION_NODE,
|
|
244
|
+
value: token.value
|
|
245
|
+
});
|
|
246
|
+
pos++;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
case OPEN_TAG_TOKEN:
|
|
250
|
+
{
|
|
251
|
+
const nextToken = tokens[++pos];
|
|
252
|
+
if (nextToken.type === SLASH_TOKEN) {
|
|
253
|
+
const nameToken = tokens[++pos];
|
|
254
|
+
const closeToken = tokens[++pos];
|
|
255
|
+
const currentParent = stack[stack.length - 1];
|
|
256
|
+
if (stack.length > 1 && closeToken.type === CLOSE_TAG_TOKEN && (nameToken?.type === IDENTIFIER_TOKEN && currentParent.name === nameToken.value || (nameToken?.type === EXPRESSION_TOKEN || nameToken.type === SLASH_TOKEN) && typeof currentParent.name === "number")) {
|
|
257
|
+
const node = stack.pop();
|
|
258
|
+
if (node?.type === ELEMENT_NODE && voidElements.has(node.name)) {
|
|
259
|
+
node.children = [];
|
|
260
|
+
}
|
|
261
|
+
pos++;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
throw new Error(`Mismatched closing tag for <${currentParent.name}>`);
|
|
265
|
+
}
|
|
266
|
+
if (nextToken.type === IDENTIFIER_TOKEN || nextToken.type === EXPRESSION_TOKEN) {
|
|
267
|
+
const tagName = nextToken.value;
|
|
268
|
+
const node = {
|
|
269
|
+
type: typeof tagName === "number" || isComponentNode(tagName) ? COMPONENT_NODE : ELEMENT_NODE,
|
|
270
|
+
name: tagName,
|
|
271
|
+
props: [],
|
|
272
|
+
children: []
|
|
273
|
+
};
|
|
274
|
+
parent.children.push(node);
|
|
275
|
+
pos++;
|
|
276
|
+
while (pos < len) {
|
|
277
|
+
const attrToken = tokens[pos];
|
|
278
|
+
if (attrToken.type === CLOSE_TAG_TOKEN || attrToken.type === SLASH_TOKEN) {
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
if (attrToken.type === SPREAD_TOKEN) {
|
|
282
|
+
const expr = tokens[pos + 1];
|
|
283
|
+
if (expr?.type === EXPRESSION_TOKEN) {
|
|
284
|
+
node.props.push({
|
|
285
|
+
type: SPREAD_PROP,
|
|
286
|
+
value: expr.value
|
|
287
|
+
});
|
|
288
|
+
pos += 2;
|
|
289
|
+
} else {
|
|
290
|
+
throw new Error(`Spread operator in <${node.name}> must be followed by an expression`);
|
|
291
|
+
}
|
|
292
|
+
} else if (attrToken.type === IDENTIFIER_TOKEN) {
|
|
293
|
+
const name = attrToken.value;
|
|
294
|
+
const next = tokens[pos + 1];
|
|
295
|
+
if (next?.type === EQUALS_TOKEN) {
|
|
296
|
+
pos += 2;
|
|
297
|
+
const valToken = tokens[pos];
|
|
298
|
+
if (valToken.type === EXPRESSION_TOKEN) {
|
|
299
|
+
node.props.push({
|
|
300
|
+
name,
|
|
301
|
+
type: EXPRESSION_PROP,
|
|
302
|
+
value: valToken.value
|
|
303
|
+
});
|
|
304
|
+
pos++;
|
|
305
|
+
} else if (valToken.type === STRING_TOKEN) {
|
|
306
|
+
const quote = valToken.quote;
|
|
307
|
+
node.props.push({
|
|
308
|
+
name,
|
|
309
|
+
value: valToken.value,
|
|
310
|
+
quote,
|
|
311
|
+
type: STATIC_PROP
|
|
312
|
+
});
|
|
313
|
+
pos++;
|
|
314
|
+
} else {
|
|
315
|
+
throw new Error(`Attribute value for "${name}" in <${node.name}> must be an expression or a string`);
|
|
316
|
+
}
|
|
317
|
+
} else {
|
|
318
|
+
node.props.push({
|
|
319
|
+
type: BOOLEAN_PROP,
|
|
320
|
+
name,
|
|
321
|
+
value: true
|
|
322
|
+
});
|
|
323
|
+
pos++;
|
|
324
|
+
}
|
|
325
|
+
} else {
|
|
326
|
+
throw new Error(`Invalid attribute in <${node.name}>`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const endToken = tokens[pos];
|
|
330
|
+
if (endToken.type === SLASH_TOKEN) {
|
|
331
|
+
pos += 2;
|
|
332
|
+
} else if (endToken.type === CLOSE_TAG_TOKEN) {
|
|
333
|
+
pos++;
|
|
334
|
+
stack.push(node);
|
|
335
|
+
}
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
default:
|
|
340
|
+
throw new Error(`Unexpected token: ${JSON.stringify(token)} after <${stack[stack.length - 1].name}>`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (stack.length > 1) {
|
|
344
|
+
throw new Error(`Unclosed tag for <${stack[stack.length - 1].name}>`);
|
|
345
|
+
}
|
|
346
|
+
return root;
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const flat = arr => {
|
|
350
|
+
return arr.length === 1 ? arr[0] : arr;
|
|
351
|
+
};
|
|
352
|
+
function createHtml() {
|
|
353
|
+
const cache = new WeakMap();
|
|
354
|
+
const rawTextElements = new Set(web.RawTextElements);
|
|
355
|
+
rawTextElements.delete("template");
|
|
356
|
+
const walker = document.createTreeWalker(document, 129);
|
|
357
|
+
const createElement = name => {
|
|
358
|
+
return web.SVGElements.has(name) ? document.createElementNS("http://www.w3.org/2000/svg", name) : web.MathMLElements.has(name) ? document.createElementNS("http://www.w3.org/1998/Math/MathML", name) : document.createElement(name);
|
|
359
|
+
};
|
|
360
|
+
const createTaggedJSX = components => {
|
|
361
|
+
const tag = (strings, ...values) => {
|
|
362
|
+
const root = getCachedRoot(strings);
|
|
363
|
+
return renderChildren(root, values, components);
|
|
364
|
+
};
|
|
365
|
+
tag.components = components;
|
|
366
|
+
tag.jsx = tag;
|
|
367
|
+
tag.define = newComponents => {
|
|
368
|
+
return createTaggedJSX({
|
|
369
|
+
...components,
|
|
370
|
+
...newComponents
|
|
371
|
+
});
|
|
372
|
+
};
|
|
373
|
+
return tag;
|
|
374
|
+
};
|
|
375
|
+
const getCachedRoot = strings => {
|
|
376
|
+
let root = cache.get(strings);
|
|
377
|
+
if (!root) {
|
|
378
|
+
root = parse(tokenize(strings, rawTextElements), web.VoidElements);
|
|
379
|
+
buildTemplate(root, false);
|
|
380
|
+
cache.set(strings, root);
|
|
381
|
+
}
|
|
382
|
+
return root;
|
|
383
|
+
};
|
|
384
|
+
const buildTemplate = (node, insideTemplate) => {
|
|
385
|
+
if (node.type === ELEMENT_NODE) {
|
|
386
|
+
if (!insideTemplate) {
|
|
387
|
+
const template = document.createElement("template");
|
|
388
|
+
template.content.appendChild(buildNodes(node));
|
|
389
|
+
node.template = template;
|
|
390
|
+
insideTemplate = true;
|
|
391
|
+
}
|
|
392
|
+
node.children.forEach(child => buildTemplate(child, insideTemplate));
|
|
393
|
+
} else if (node.type === COMPONENT_NODE || node.type === ROOT_NODE) {
|
|
394
|
+
node.children.forEach(child => buildTemplate(child, false));
|
|
395
|
+
} else if (node.type === TEXT_NODE && !insideTemplate) {
|
|
396
|
+
textTemplate.innerHTML = node.value;
|
|
397
|
+
node.value = textTemplate.content.textContent ?? "";
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
const textTemplate = document.createElement("template");
|
|
401
|
+
const buildNodes = node => {
|
|
402
|
+
switch (node.type) {
|
|
403
|
+
case TEXT_NODE:
|
|
404
|
+
textTemplate.innerHTML = node.value;
|
|
405
|
+
return document.createTextNode(textTemplate.content.textContent ?? "");
|
|
406
|
+
case EXPRESSION_NODE:
|
|
407
|
+
return document.createComment("+");
|
|
408
|
+
case COMPONENT_NODE:
|
|
409
|
+
return document.createComment(node.name);
|
|
410
|
+
case ELEMENT_NODE:
|
|
411
|
+
let hasSpread = false;
|
|
412
|
+
const elem = createElement(node.name);
|
|
413
|
+
const claimAttr = node.name === "a" ? "href" : node.name === "form" ? "action" : undefined;
|
|
414
|
+
node.props = node.props.filter(prop => {
|
|
415
|
+
if (prop.type === STATIC_PROP) {
|
|
416
|
+
if (prop.name.startsWith("prop:")) return true;
|
|
417
|
+
elem.setAttribute(prop.name, prop.value);
|
|
418
|
+
if (!hasSpread && prop.name === claimAttr) node.claim = true;
|
|
419
|
+
return hasSpread;
|
|
420
|
+
} else if (prop.type === BOOLEAN_PROP) {
|
|
421
|
+
elem.setAttribute(prop.name, "");
|
|
422
|
+
if (!hasSpread && prop.name === claimAttr) node.claim = true;
|
|
423
|
+
return hasSpread;
|
|
424
|
+
} else if (prop.type === SPREAD_PROP) {
|
|
425
|
+
hasSpread = true;
|
|
426
|
+
return hasSpread;
|
|
427
|
+
}
|
|
428
|
+
return true;
|
|
429
|
+
});
|
|
430
|
+
const childRoot = node.name === "template" ? elem.content : elem;
|
|
431
|
+
childRoot.append(...node.children.map(buildNodes));
|
|
432
|
+
return elem;
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
const renderNode = (node, values, components) => {
|
|
436
|
+
switch (node.type) {
|
|
437
|
+
case TEXT_NODE:
|
|
438
|
+
return node.value;
|
|
439
|
+
case EXPRESSION_NODE:
|
|
440
|
+
return values[node.value];
|
|
441
|
+
case COMPONENT_NODE:
|
|
442
|
+
const component = typeof node.name === "string" ? components[node.name] : values[node.name];
|
|
443
|
+
if (component && typeof component === "function") {
|
|
444
|
+
return web.createComponent(component, gatherProps(node, values, components));
|
|
445
|
+
} else {
|
|
446
|
+
throw new Error(`Component "${node.name}" not found in registry`);
|
|
447
|
+
}
|
|
448
|
+
case ELEMENT_NODE:
|
|
449
|
+
const element = renderChildren(node, values, components);
|
|
450
|
+
const props = gatherProps(node, values, components);
|
|
451
|
+
web.spread(element, props, true);
|
|
452
|
+
if (node.claim) web.claimElement(element);
|
|
453
|
+
return element;
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
const renderChildren = (node, values, components) => {
|
|
457
|
+
if (node.type !== ELEMENT_NODE || !node.template) {
|
|
458
|
+
return flat(node.children.map(n => renderNode(n, values, components)));
|
|
459
|
+
}
|
|
460
|
+
const element = node.template.content.firstChild.cloneNode(true);
|
|
461
|
+
walker.currentNode = element;
|
|
462
|
+
const walkNodes = (nodes, walker) => {
|
|
463
|
+
for (const node of nodes) {
|
|
464
|
+
if (node.type === ELEMENT_NODE || node.type === EXPRESSION_NODE || node.type === COMPONENT_NODE) {
|
|
465
|
+
const domNode = walker.nextNode();
|
|
466
|
+
if (node.type === EXPRESSION_NODE || node.type === COMPONENT_NODE) {
|
|
467
|
+
web.insert(domNode.parentNode, renderNode(node, values, components), domNode);
|
|
468
|
+
walker.currentNode = domNode;
|
|
469
|
+
} else {
|
|
470
|
+
if (node.props.length) {
|
|
471
|
+
const props = gatherProps(node, values, components);
|
|
472
|
+
web.spread(domNode, props, true);
|
|
473
|
+
}
|
|
474
|
+
if (node.claim) web.claimElement(domNode);
|
|
475
|
+
walkNodes(node.children, node.name === "template" ? document.createTreeWalker(domNode.content, 129) : walker);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
walkNodes(node.children, node.name === "template" ? document.createTreeWalker(element.content, 129) : walker);
|
|
481
|
+
return element;
|
|
482
|
+
};
|
|
483
|
+
const gatherProps = (node, values, components, props = {}) => {
|
|
484
|
+
for (const prop of node.props) {
|
|
485
|
+
switch (prop.type) {
|
|
486
|
+
case BOOLEAN_PROP:
|
|
487
|
+
props[prop.name] = true;
|
|
488
|
+
break;
|
|
489
|
+
case STATIC_PROP:
|
|
490
|
+
props[prop.name] = prop.value;
|
|
491
|
+
break;
|
|
492
|
+
case EXPRESSION_PROP:
|
|
493
|
+
applyGetter(props, prop.name, values[prop.value]);
|
|
494
|
+
break;
|
|
495
|
+
case SPREAD_PROP:
|
|
496
|
+
const spreadValue = values[prop.value];
|
|
497
|
+
if (!spreadValue || typeof spreadValue !== "object") throw new Error("Can only spread objects");
|
|
498
|
+
props = web.mergeProps(props, spreadValue);
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (node.type === COMPONENT_NODE && node.children.length) {
|
|
503
|
+
Object.defineProperty(props, "children", {
|
|
504
|
+
get() {
|
|
505
|
+
return renderChildren(node, values, components);
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
return props;
|
|
510
|
+
};
|
|
511
|
+
const applyGetter = (props, name, value) => {
|
|
512
|
+
if (typeof value === "function" && value.length === 0 && name !== "ref" && !name.startsWith("on")) {
|
|
513
|
+
Object.defineProperty(props, name, {
|
|
514
|
+
get() {
|
|
515
|
+
return value();
|
|
516
|
+
},
|
|
517
|
+
enumerable: true
|
|
518
|
+
});
|
|
519
|
+
} else {
|
|
520
|
+
props[name] = value;
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
return createTaggedJSX({});
|
|
524
|
+
}
|
|
525
|
+
const html = createHtml();
|
|
19
526
|
|
|
20
527
|
module.exports = html;
|