@torpor/view 0.4.18 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +41 -16
  2. package/dist/Cleanup-D0wvGW5d.d.cts +9 -0
  3. package/dist/Cleanup-D0wvGW5d.d.cts.map +1 -0
  4. package/dist/Cleanup-D0wvGW5d.d.mts +9 -0
  5. package/dist/Cleanup-D0wvGW5d.d.mts.map +1 -0
  6. package/dist/Component-0MH0zd-m.d.cts +11 -0
  7. package/dist/Component-0MH0zd-m.d.cts.map +1 -0
  8. package/dist/Component-0MH0zd-m.d.mts +11 -0
  9. package/dist/Component-0MH0zd-m.d.mts.map +1 -0
  10. package/dist/Effect-BTKurOpr.d.mts +381 -0
  11. package/dist/Effect-BTKurOpr.d.mts.map +1 -0
  12. package/dist/Effect-COl3aRJV.d.cts +381 -0
  13. package/dist/Effect-COl3aRJV.d.cts.map +1 -0
  14. package/dist/compile.cjs +1734 -739
  15. package/dist/compile.d.cts +85 -50
  16. package/dist/compile.d.cts.map +1 -1
  17. package/dist/compile.d.mts +85 -50
  18. package/dist/compile.d.mts.map +1 -1
  19. package/dist/compile.mjs +1737 -741
  20. package/dist/compile.mjs.map +1 -1
  21. package/dist/dev.cjs +62 -63
  22. package/dist/dev.d.cts +4 -3
  23. package/dist/dev.d.cts.map +1 -1
  24. package/dist/dev.d.mts +4 -3
  25. package/dist/dev.d.mts.map +1 -1
  26. package/dist/dev.mjs +61 -62
  27. package/dist/dev.mjs.map +1 -1
  28. package/dist/{devContext-B3yskfhA.cjs → devContext-BulTn3U5.cjs} +6 -9
  29. package/dist/{devContext-CQ8RhBX-.mjs → devContext-ZNsPyOgB.mjs} +3 -4
  30. package/dist/devContext-ZNsPyOgB.mjs.map +1 -0
  31. package/dist/fromWebSocket-1QOjcrHE.cjs +182 -0
  32. package/dist/fromWebSocket-B52kN3-W.d.mts +91 -0
  33. package/dist/fromWebSocket-B52kN3-W.d.mts.map +1 -0
  34. package/dist/fromWebSocket-DeB35Q4w.d.cts +91 -0
  35. package/dist/fromWebSocket-DeB35Q4w.d.cts.map +1 -0
  36. package/dist/fromWebSocket-Dxc_uwm1.mjs +137 -0
  37. package/dist/fromWebSocket-Dxc_uwm1.mjs.map +1 -0
  38. package/dist/index.cjs +2109 -494
  39. package/dist/index.d.cts +608 -112
  40. package/dist/index.d.cts.map +1 -1
  41. package/dist/index.d.mts +608 -112
  42. package/dist/index.d.mts.map +1 -1
  43. package/dist/index.mjs +2088 -490
  44. package/dist/index.mjs.map +1 -1
  45. package/dist/ssr.cjs +78 -17
  46. package/dist/ssr.d.cts +64 -11
  47. package/dist/ssr.d.cts.map +1 -1
  48. package/dist/ssr.d.mts +64 -11
  49. package/dist/ssr.d.mts.map +1 -1
  50. package/dist/ssr.mjs +67 -14
  51. package/dist/ssr.mjs.map +1 -1
  52. package/package.json +21 -19
  53. package/dist/Cleanup-CYsytTEc.d.cts +0 -9
  54. package/dist/Cleanup-CYsytTEc.d.cts.map +0 -1
  55. package/dist/Cleanup-CgjyN0lW.d.mts +0 -9
  56. package/dist/Cleanup-CgjyN0lW.d.mts.map +0 -1
  57. package/dist/Component-DmWGMgak.d.mts +0 -11
  58. package/dist/Component-DmWGMgak.d.mts.map +0 -1
  59. package/dist/Component-kGLRFYg2.d.cts +0 -11
  60. package/dist/Component-kGLRFYg2.d.cts.map +0 -1
  61. package/dist/Region-Bb6HAb-F.d.cts +0 -201
  62. package/dist/Region-Bb6HAb-F.d.cts.map +0 -1
  63. package/dist/Region-CTchywkr.d.mts +0 -201
  64. package/dist/Region-CTchywkr.d.mts.map +0 -1
  65. package/dist/devContext-CQ8RhBX-.mjs.map +0 -1
  66. package/dist/formatText-BNP3P38F.d.cts +0 -17
  67. package/dist/formatText-BNP3P38F.d.cts.map +0 -1
  68. package/dist/formatText-D6Ov8Ub0.d.mts +0 -17
  69. package/dist/formatText-D6Ov8Ub0.d.mts.map +0 -1
  70. package/dist/formatText-DTIRmc3X.mjs +0 -61
  71. package/dist/formatText-DTIRmc3X.mjs.map +0 -1
  72. package/dist/formatText-Dhl2O0Ee.cjs +0 -96
package/dist/compile.mjs CHANGED
@@ -1,12 +1,209 @@
1
- //#region src/compile/utils/endOfTemplateString.ts
2
- function endOfTemplateString(content, start) {
3
- let level = 0;
4
- for (let j = start + 1; j < content.length; j++) if (content[j] === "`" && content[j - 1] !== "\\" && level === 0) return j;
5
- else if (content[j] === "{" && (level > 0 || content[j - 1] === "$")) level += 1;
6
- else if (content[j] === "}" && level > 0) level -= 1;
1
+ //#region src/compile/utils/endOfString.ts
2
+ function endOfString(char, content, start) {
3
+ for (let j = start + 1; j < content.length; j++) if (content[j] === char && content[j - 1] !== "\\") return j;
7
4
  return content.length;
8
5
  }
9
-
6
+ //#endregion
7
+ //#region src/compile/utils/codeScanner.ts
8
+ /**
9
+ * Shared scanner for skipping over non-code parts of JavaScript expression
10
+ * text: string literals, template literals (including their interpolations),
11
+ * comments, and regex literals. Used everywhere the compiler scans
12
+ * expression text and needs to tell code from literal text.
13
+ *
14
+ * Note: this is for *code* contexts. For torpor template text (where quotes
15
+ * are literal characters and `{...}` are interpolation delimiters), use this
16
+ * only for the *interiors* of `{...}` expressions -- the template level
17
+ * itself has different rules.
18
+ */
19
+ const REGEX_KEYWORDS = /* @__PURE__ */ new Set([
20
+ "return",
21
+ "typeof",
22
+ "instanceof",
23
+ "in",
24
+ "of",
25
+ "new",
26
+ "delete",
27
+ "void",
28
+ "throw",
29
+ "case",
30
+ "do",
31
+ "else",
32
+ "yield",
33
+ "await"
34
+ ]);
35
+ /**
36
+ * If the character at `start` begins a string literal, a template literal, a
37
+ * comment (line or block), or a regex literal, returns the index just after
38
+ * it. Returns -1 if it doesn't (a `/` that is division, or any other
39
+ * character).
40
+ */
41
+ function skipStringOrComment(value, start) {
42
+ return skipInRange(value, start);
43
+ }
44
+ /**
45
+ * Returns the [start, end) ranges of `value` that are code -- i.e. not the
46
+ * contents of string literals, regex literals, or comments. Template string
47
+ * contents are skipped, except for the contents of `${...}` interpolations,
48
+ * which are code again.
49
+ */
50
+ function codeRanges(value) {
51
+ const ranges = [];
52
+ let codeStart = 0;
53
+ let i = 0;
54
+ while (i < value.length) {
55
+ const next = skipInRange(value, i, ranges, codeStart);
56
+ if (next !== -1) {
57
+ i = next;
58
+ codeStart = i;
59
+ } else i += 1;
60
+ }
61
+ pushRange(ranges, codeStart, value.length);
62
+ return ranges;
63
+ }
64
+ /**
65
+ * `skipStringOrComment` with optional collection of the interrupted code
66
+ * range (for `codeRanges`), so there is a single implementation of the scan
67
+ * order.
68
+ */
69
+ function skipInRange(value, start, ranges, codeStart) {
70
+ const char = value[start];
71
+ if (char === "'" || char === "\"") {
72
+ if (ranges) pushRange(ranges, codeStart, start);
73
+ return endOfString(char, value, start) + 1;
74
+ }
75
+ if (char === "`") {
76
+ if (ranges) pushRange(ranges, codeStart, start);
77
+ return skipTemplateString(value, start, ranges);
78
+ }
79
+ if (char === "/" && value[start + 1] === "/") {
80
+ if (ranges) pushRange(ranges, codeStart, start);
81
+ const end = value.indexOf("\n", start + 2);
82
+ return end === -1 ? value.length : end;
83
+ }
84
+ if (char === "/" && value[start + 1] === "*") {
85
+ if (ranges) pushRange(ranges, codeStart, start);
86
+ const end = value.indexOf("*/", start + 2);
87
+ return end === -1 ? value.length : end + 2;
88
+ }
89
+ if (char === "/" && isRegexPosition(value, start)) {
90
+ if (ranges) pushRange(ranges, codeStart, start);
91
+ return skipRegexLiteral(value, start);
92
+ }
93
+ return -1;
94
+ }
95
+ /**
96
+ * Skips over a template string, given the index of its opening backtick, and
97
+ * returns the index just after the closing backtick. Handles escaped
98
+ * characters, and scans `${...}` interpolations correctly (nested strings,
99
+ * templates, comments and regexes within them can't desync the scan).
100
+ */
101
+ function skipTemplateString(value, start, ranges) {
102
+ let i = start + 1;
103
+ while (i < value.length) {
104
+ const char = value[i];
105
+ if (char === "\\") i += 2;
106
+ else if (char === "`") return i + 1;
107
+ else if (char === "$" && value[i + 1] === "{") i = endOfInterpolation(value, i + 2, ranges) + 1;
108
+ else i += 1;
109
+ }
110
+ return value.length;
111
+ }
112
+ /**
113
+ * Skips over the code inside a `${...}` interpolation, given the index just
114
+ * after the `${`, collecting its code ranges when provided. Returns the
115
+ * index of the matching `}`.
116
+ */
117
+ function endOfInterpolation(value, start, ranges) {
118
+ let level = 0;
119
+ let codeStart = start;
120
+ let i = start;
121
+ while (i < value.length) {
122
+ const char = value[i];
123
+ if (char === "{") {
124
+ level += 1;
125
+ i += 1;
126
+ } else if (char === "}") {
127
+ if (level === 0) {
128
+ pushRange(ranges, codeStart, i);
129
+ return i;
130
+ }
131
+ level -= 1;
132
+ i += 1;
133
+ } else {
134
+ const next = skipInRange(value, i, ranges, codeStart);
135
+ if (next !== -1) {
136
+ i = next;
137
+ codeStart = i;
138
+ } else i += 1;
139
+ }
140
+ }
141
+ pushRange(ranges, codeStart, value.length);
142
+ return value.length;
143
+ }
144
+ /**
145
+ * Decides whether the `/` at `slashIndex` starts a regex literal (rather
146
+ * than being division), by looking at the previous significant character.
147
+ * The standard lexical heuristic -- not a full parse, but correct for
148
+ * real-world code.
149
+ */
150
+ function isRegexPosition(value, slashIndex) {
151
+ let i = slashIndex - 1;
152
+ while (i >= 0) {
153
+ const char = value[i];
154
+ if (char === " " || char === " " || char === "\n" || char === "\r") i -= 1;
155
+ else if (char === "/" && value[i - 1] === "*") {
156
+ const open = value.lastIndexOf("/*", i - 2);
157
+ if (open === -1) return true;
158
+ i = open - 1;
159
+ } else break;
160
+ }
161
+ if (i < 0) return true;
162
+ const char = value[i];
163
+ if (char === ")" || char === "]" || char === "}" || char === "\"" || char === "'" || char === "`") return false;
164
+ if (/[a-z0-9_$]/i.test(char)) {
165
+ let wordStart = i;
166
+ while (wordStart >= 0 && /[a-z0-9_$]/i.test(value[wordStart])) wordStart -= 1;
167
+ return REGEX_KEYWORDS.has(value.substring(wordStart + 1, i + 1));
168
+ }
169
+ if (char === "+" || char === "-") {
170
+ if (value[i - 1] === char) {
171
+ const before = value[i - 2];
172
+ return !(before && /[a-z0-9_$)\]]/i.test(before));
173
+ }
174
+ return true;
175
+ }
176
+ return true;
177
+ }
178
+ /**
179
+ * Skips over a regex literal, given the index of its opening `/`, and
180
+ * returns the index just after its flags. Returns -1 if the literal is
181
+ * unterminated (in which case the `/` is treated as division).
182
+ */
183
+ function skipRegexLiteral(value, start) {
184
+ let i = start + 1;
185
+ let inClass = false;
186
+ while (i < value.length) {
187
+ const char = value[i];
188
+ if (char === "\\") i += 2;
189
+ else if (char === "\n") return -1;
190
+ else if (char === "[") {
191
+ inClass = true;
192
+ i += 1;
193
+ } else if (char === "]") {
194
+ inClass = false;
195
+ i += 1;
196
+ } else if (char === "/" && !inClass) {
197
+ i += 1;
198
+ while (i < value.length && /[a-z]/i.test(value[i])) i += 1;
199
+ return i;
200
+ } else i += 1;
201
+ }
202
+ return -1;
203
+ }
204
+ function pushRange(ranges, start, end) {
205
+ if (ranges && end > start) ranges.push([start, end]);
206
+ }
10
207
  //#endregion
11
208
  //#region src/compile/utils/Builder.ts
12
209
  var Builder = class {
@@ -23,7 +220,7 @@ var Builder = class {
23
220
  append(text) {
24
221
  if (this.#mapped) {
25
222
  this.lineMap.push(this.#text.length);
26
- for (let i$1 = 0; i$1 < text.length; i$1++) if (text[i$1] === "\n") this.lineMap.push(this.#text.length + i$1);
223
+ for (let i = 0; i < text.length; i++) if (text[i] === "\n") this.lineMap.push(this.#text.length + i);
27
224
  this.#text += "\n" + text;
28
225
  return;
29
226
  }
@@ -39,12 +236,12 @@ var Builder = class {
39
236
  } else {
40
237
  if (text[i] === "}" || text[i] === ")" || text[i] === "]") this.#space -= 1;
41
238
  this.#text += " ".repeat(Math.max(0, this.#space));
42
- const start$1 = i;
239
+ const start = i;
43
240
  while (i < text.length && text[i] !== "\n") {
44
- if (text[i] === "`") i = endOfTemplateString(text, i);
241
+ if (text[i] === "`") i = skipStringOrComment(text, i) - 1;
45
242
  i++;
46
243
  }
47
- const line = text.substring(start$1, i + 1);
244
+ const line = text.substring(start, i + 1);
48
245
  if (line.startsWith("*")) this.#text += " ";
49
246
  this.#text += line;
50
247
  if (text[i - 1] === "{" || text[i - 1] === "(" || text[i - 1] === "[") this.#space += 1;
@@ -57,7 +254,99 @@ var Builder = class {
57
254
  return this.#text;
58
255
  }
59
256
  };
60
-
257
+ //#endregion
258
+ //#region src/compile/utils/isParentNode.ts
259
+ function isParentNode(node) {
260
+ return "children" in node;
261
+ }
262
+ //#endregion
263
+ //#region src/compile/utils/collectMarkupExpressions.ts
264
+ /**
265
+ * Walks a markup tree and returns every expression string it contains —
266
+ * `@control (...)` statements, reactive attribute values, and `{...}` text
267
+ * interpolations — concatenated. Used for import detection: `$`-primitives
268
+ * used inside markup (e.g. `@if ($pending(() => $state.data))`,
269
+ * `disabled={$pending(...)}`) must be imported, and the script-only scan in
270
+ * `buildCode`/`buildServerCode` doesn't see them.
271
+ *
272
+ * Static attribute values and literal text outside `{...}` are excluded, so a
273
+ * plain string containing e.g. `$pending` does not produce a spurious import.
274
+ */
275
+ function collectMarkupExpressions(root) {
276
+ let out = "";
277
+ const walk = (node) => {
278
+ if (node.type === "control") out += node.statement + "\n";
279
+ if (node.type === "element" || node.type === "component" || node.type === "special") {
280
+ for (const attribute of node.attributes) if (attribute.reactive && attribute.value !== void 0) out += attribute.value + "\n";
281
+ }
282
+ if (node.type === "text") out += extractInterpolations(node.content) + "\n";
283
+ if (isParentNode(node)) for (const child of node.children) walk(child);
284
+ };
285
+ walk(root);
286
+ return out;
287
+ }
288
+ /**
289
+ * Extracts the `{expr}` interpolation segments from text content (level 0
290
+ * text is prose and is dropped), skipping string literals, template strings,
291
+ * comments and regex literals inside the braces (they're copied into the
292
+ * output verbatim).
293
+ */
294
+ function extractInterpolations(content) {
295
+ let out = "";
296
+ let level = 0;
297
+ for (let i = 0; i < content.length; i++) {
298
+ const char = content[i];
299
+ if (char === "{") {
300
+ if (level === 0) out += "\n";
301
+ level++;
302
+ out += char;
303
+ } else if (char === "}") {
304
+ level--;
305
+ out += char;
306
+ } else if (level > 0) {
307
+ const skipped = skipStringOrComment(content, i);
308
+ if (skipped !== -1) {
309
+ out += content.substring(i, skipped);
310
+ i = skipped - 1;
311
+ continue;
312
+ }
313
+ out += char;
314
+ }
315
+ }
316
+ return out;
317
+ }
318
+ //#endregion
319
+ //#region src/compile/utils/markupRendersComponent.ts
320
+ /**
321
+ * Returns true if `markup` renders anything that forwards the component's
322
+ * `$context` parameter: a child component invocation (`<Child />`), a
323
+ * self-recursive `<@component self>`, or a `<slot>` definition (whose
324
+ * generated render call passes `$context` through to the filling parent).
325
+ *
326
+ * `contextProps` only captures *direct* `$context.x` reads in the script, so
327
+ * this is needed to decide whether the `$context` parameter is actually used:
328
+ * every child-component / slot render call passes `$context` positionally
329
+ * (see buildComponentNode / buildSlotNode and their server counterparts),
330
+ * even when the component never names `$context` explicitly.
331
+ *
332
+ * Bias is towards `true`: when uncertain we keep `$context` named, which is
333
+ * always safe at the cost of a missed unused-parameter cleanup.
334
+ */
335
+ function markupRendersComponent(markup) {
336
+ return nodeRendersComponent(markup);
337
+ }
338
+ function nodeRendersComponent(node) {
339
+ if (node.type === "component") return true;
340
+ if (node.type === "special") {
341
+ const tagName = node.tagName;
342
+ if (tagName === "@component" || tagName === "slot") return true;
343
+ }
344
+ const children = node.children;
345
+ if (children) {
346
+ for (const child of children) if (nodeRendersComponent(child)) return true;
347
+ }
348
+ return false;
349
+ }
61
350
  //#endregion
62
351
  //#region src/compile/build/client/addMappedText.ts
63
352
  function addMappedText(preText, text, postText, span, status, b) {
@@ -75,7 +364,6 @@ function addMappedText(preText, text, postText, span, status, b) {
75
364
  });
76
365
  } else b.append(preText + text + postText);
77
366
  }
78
-
79
367
  //#endregion
80
368
  //#region src/compile/build/client/addPopDevBoundary.ts
81
369
  function addPopDevBoundary(status, b) {
@@ -85,7 +373,6 @@ function addPopDevBoundary(status, b) {
85
373
  b.append(`t_pop_dev_bound();`);
86
374
  }
87
375
  }
88
-
89
376
  //#endregion
90
377
  //#region src/compile/build/client/addPushDevBoundary.ts
91
378
  function addPushDevBoundary(type, name, status, b) {
@@ -99,14 +386,73 @@ function addPushDevBoundary(type, name, status, b) {
99
386
  b.append("");
100
387
  }
101
388
  }
102
-
103
389
  //#endregion
104
390
  //#region src/compile/types/comments.ts
105
- const ANCHOR_COMMENT = "<!>";
106
391
  const HYDRATION_START_COMMENT = "<![>";
107
392
  const HYDRATION_END_COMMENT = "<!]>";
108
393
  const HYDRATION_BREAK_COMMENT = "<!^>";
109
-
394
+ //#endregion
395
+ //#region src/compile/utils/isControlNode.ts
396
+ function isControlNode(node) {
397
+ return node.type === "control";
398
+ }
399
+ //#endregion
400
+ //#region src/compile/utils/isElementNode.ts
401
+ function isElementNode(node) {
402
+ return node.type === "element";
403
+ }
404
+ //#endregion
405
+ //#region src/compile/utils/nonRenderingOperations.ts
406
+ /**
407
+ * Control operations that produce no DOM output within a fragment.
408
+ *
409
+ * Used by both the fragment builder (to find the first child that actually
410
+ * renders) and the whitespace trimmer (so that whitespace adjacent to these
411
+ * nodes is treated as leading/trailing whitespace of the container, rather
412
+ * than as significant inter-sibling whitespace — without this, every row of
413
+ * a `@for { @key = ...; <tr>...</tr> }` would emit a leading whitespace
414
+ * text node, since the whitespace sits between the @key and the <tr>).
415
+ */
416
+ const NON_RENDERING_OPERATIONS = /* @__PURE__ */ new Set([
417
+ "@key",
418
+ "@const",
419
+ "@console",
420
+ "@debugger",
421
+ "@function",
422
+ "@async function"
423
+ ]);
424
+ //#endregion
425
+ //#region src/compile/utils/getSingleElementRoot.ts
426
+ /**
427
+ * Returns the single rendering `Element` root child of `children` if the
428
+ * fragment is "single-root element" — i.e. it has exactly one child that
429
+ * renders to the DOM, that child is an `Element`, and every other child is a
430
+ * non-rendering control node (`@key`, `@const`, …) or a comment. Returns
431
+ * `undefined` otherwise.
432
+ *
433
+ * When this returns an `Element`, the compiler emits the
434
+ * `t_fragment_el` / `t_root_el` / `t_add_element` codegen path, which clones
435
+ * the cached template's `firstElementChild` directly into the parent and
436
+ * skips the per-instance `DocumentFragment` allocation that `getFragment`
437
+ * produces.
438
+ *
439
+ * Text-root fragments (`{row.label}` standalone inside a `@for` body) are
440
+ * intentionally excluded: their root is a `Text`, not an `Element`, so
441
+ * `template.content.firstElementChild` would be `null`. Multi-root fragments
442
+ * (`<tr>...</tr><tr>...</tr>`) are excluded because no single element
443
+ * represents the whole fragment.
444
+ */
445
+ function getSingleElementRoot(children) {
446
+ let renderingRoot;
447
+ for (const child of children) {
448
+ if (child.type === "comment") continue;
449
+ if (isControlNode(child) && NON_RENDERING_OPERATIONS.has(child.operation)) continue;
450
+ if (renderingRoot) return void 0;
451
+ if (!isElementNode(child)) return void 0;
452
+ renderingRoot = child;
453
+ }
454
+ return renderingRoot;
455
+ }
110
456
  //#endregion
111
457
  //#region src/compile/utils/isReactive.ts
112
458
  /**
@@ -119,7 +465,6 @@ const HYDRATION_BREAK_COMMENT = "<!^>";
119
465
  function isReactive(text) {
120
466
  return text.includes("{") && text.includes("}");
121
467
  }
122
-
123
468
  //#endregion
124
469
  //#region src/compile/utils/trimQuotes.ts
125
470
  /**
@@ -138,7 +483,24 @@ function trimQuotes(text) {
138
483
  }
139
484
  return text.substring(start, end);
140
485
  }
141
-
486
+ //#endregion
487
+ //#region src/compile/utils/voidTags.ts
488
+ const voidTags = [
489
+ "area",
490
+ "base",
491
+ "br",
492
+ "col",
493
+ "embed",
494
+ "hr",
495
+ "img",
496
+ "input",
497
+ "link",
498
+ "meta",
499
+ "param",
500
+ "source",
501
+ "track",
502
+ "wbr"
503
+ ];
142
504
  //#endregion
143
505
  //#region src/compile/build/client/buildFragmentText.ts
144
506
  /**
@@ -147,7 +509,10 @@ function trimQuotes(text) {
147
509
  function buildFragmentText(node, status, b) {
148
510
  const fragments = [];
149
511
  buildNodeFragmentText(node, status, fragments);
150
- if (fragments.length) b.append(`const t_fragments: DocumentFragment[] = [];`);
512
+ if (fragments.length) {
513
+ if (fragments.some((f) => !f.singleRootElement && f.usesFragmentCache !== false)) b.append(`const t_fragments: DocumentFragment[] = [];`);
514
+ if (fragments.some((f) => f.singleRootElement)) b.append(`const t_fragment_els: Element[] = [];`);
515
+ }
151
516
  }
152
517
  function buildNodeFragmentText(node, status, fragments, currentFragment) {
153
518
  switch (node.type) {
@@ -184,7 +549,8 @@ function buildRootFragmentText(node, status, fragments) {
184
549
  ns: status.ns,
185
550
  effects: [],
186
551
  events: [],
187
- animations: []
552
+ animations: [],
553
+ singleRootElement: getSingleElementRoot(node.children) !== void 0
188
554
  };
189
555
  fragments.push(node.fragment);
190
556
  for (let child of node.children) buildNodeFragmentText(child, status, fragments, node.fragment);
@@ -195,19 +561,26 @@ function buildControlFragmentText(node, status, fragments, currentFragment) {
195
561
  case "@switch group":
196
562
  case "@for group":
197
563
  case "@await group":
564
+ case "@try group":
198
565
  case "@replace group":
199
566
  case "@html group":
200
- currentFragment.text += ANCHOR_COMMENT;
567
+ currentFragment.text += "<!>";
201
568
  for (let child of node.children) buildNodeFragmentText(child, status, fragments, currentFragment);
202
569
  break;
203
570
  default:
571
+ if (NON_RENDERING_OPERATIONS.has(node.operation)) {
572
+ for (let child of node.children) buildNodeFragmentText(child, status, fragments, currentFragment);
573
+ break;
574
+ }
204
575
  node.fragment = {
205
576
  number: fragments.length,
206
577
  text: "",
207
578
  ns: status.ns,
208
579
  effects: [],
209
580
  events: [],
210
- animations: []
581
+ animations: [],
582
+ singleRootElement: getSingleElementRoot(node.children) !== void 0,
583
+ usesFragmentCache: node.operation !== "@html"
211
584
  };
212
585
  fragments.push(node.fragment);
213
586
  for (let child of node.children) buildNodeFragmentText(child, status, fragments, node.fragment);
@@ -215,7 +588,7 @@ function buildControlFragmentText(node, status, fragments, currentFragment) {
215
588
  }
216
589
  }
217
590
  function buildComponentFragmentText(node, status, fragments, currentFragment) {
218
- currentFragment.text += ANCHOR_COMMENT;
591
+ currentFragment.text += "<!>";
219
592
  if (node.children.length) {
220
593
  node.fragment = {
221
594
  number: fragments.length,
@@ -223,16 +596,18 @@ function buildComponentFragmentText(node, status, fragments, currentFragment) {
223
596
  ns: status.ns,
224
597
  effects: [],
225
598
  events: [],
226
- animations: []
599
+ animations: [],
600
+ singleRootElement: getSingleElementRoot(node.children) !== void 0
227
601
  };
228
602
  fragments.push(node.fragment);
229
603
  for (let child of node.children) buildNodeFragmentText(child, status, fragments, node.fragment);
230
604
  }
231
605
  }
232
606
  function buildElementFragmentText(node, status, fragments, currentFragment) {
607
+ const tagName = node.tagName === "@element" ? "el" : node.tagName;
233
608
  let needsClass = node.scopeStyles;
234
- currentFragment.text += `<${node.tagName}`;
235
- let attributesText = node.attributes.filter((a) => !a.name.startsWith("on")).map((a) => {
609
+ currentFragment.text += `<${tagName}`;
610
+ let attributesText = node.attributes.filter((a) => !a.name.startsWith("on")).filter((a) => !(a.name === "self" && node.tagName === "@element")).map((a) => {
236
611
  if (a.value && a.reactive) {
237
612
  if (a.name.startsWith("data-")) return `${a.name}=""`;
238
613
  } else if (a.name === "class" && node.scopeStyles) {
@@ -244,29 +619,33 @@ function buildElementFragmentText(node, status, fragments, currentFragment) {
244
619
  if (attributesText) currentFragment.text += " " + attributesText;
245
620
  if (needsClass) currentFragment.text += ` class="torp-${status.styleHash}"`;
246
621
  currentFragment.text += ">";
247
- for (let child of node.children) buildNodeFragmentText(child, status, fragments, currentFragment);
248
- currentFragment.text += `</${node.tagName}>`;
622
+ if (!voidTags.includes(node.tagName)) {
623
+ for (let child of node.children) buildNodeFragmentText(child, status, fragments, currentFragment);
624
+ currentFragment.text += `</${tagName}>`;
625
+ }
249
626
  }
250
627
  function buildSpecialFragmentText(node, status, fragments, currentFragment) {
251
628
  switch (node.tagName) {
252
629
  case "slot":
253
- currentFragment.text += ANCHOR_COMMENT;
630
+ currentFragment.text += "<!>";
254
631
  for (let child of node.children) buildNodeFragmentText(child, status, fragments, node.fragment);
255
632
  break;
256
633
  case "fill":
634
+ case "filldef":
257
635
  node.fragment = {
258
636
  number: fragments.length,
259
637
  text: "",
260
638
  ns: status.ns,
261
639
  effects: [],
262
640
  events: [],
263
- animations: []
641
+ animations: [],
642
+ singleRootElement: getSingleElementRoot(node.children) !== void 0
264
643
  };
265
644
  fragments.push(node.fragment);
266
645
  for (let child of node.children) buildNodeFragmentText(child, status, fragments, node.fragment);
267
646
  break;
268
647
  case "@element":
269
- buildComponentFragmentText(node, status, fragments, currentFragment);
648
+ buildElementFragmentText(node, status, fragments, currentFragment);
270
649
  break;
271
650
  case "@component":
272
651
  buildComponentFragmentText(node, status, fragments, currentFragment);
@@ -274,13 +653,67 @@ function buildSpecialFragmentText(node, status, fragments, currentFragment) {
274
653
  default: throw new Error(`Invalid special node: ${node.tagName}`);
275
654
  }
276
655
  }
277
-
656
+ //#endregion
657
+ //#region src/compile/utils/bindingTarget.ts
658
+ /**
659
+ * Builds the statements that write a component's prop value back to the
660
+ * expression it is bound to (e.g. for `&name={$state.user?.name}`).
661
+ *
662
+ * The bound expression may use optional chaining, which can't be assigned to
663
+ * directly. In that case the write is compiled to a guarded assignment: the
664
+ * prop value is read first (so the write re-runs when the child changes it),
665
+ * the target object is read with `$peek` (so that replacing it doesn't re-run
666
+ * the write with a stale prop value), and the value is written through it
667
+ * when it exists:
668
+ *
669
+ * ```
670
+ * bindingTarget("$state.user?.name", `props["name"]`, "name", imports)
671
+ * // => const t_bind_name = props["name"];
672
+ * // const t_target_name = $peek(() => $state.user);
673
+ * // if (t_target_name != null) t_target_name.name = t_bind_name;
674
+ * ```
675
+ *
676
+ * @param value The bound expression
677
+ * @param right The value to assign to it
678
+ * @param name The prop name, used to make the temp variable names unique
679
+ * @param imports The set of runtime imports to add `$peek` to when the
680
+ * expression needs a guarded write
681
+ *
682
+ * @returns The assignment statements
683
+ */
684
+ function bindingTarget(value, right, name, imports) {
685
+ let lastChainStart = -1;
686
+ for (let i = 0; i < value.length; i++) {
687
+ let char = value[i];
688
+ if (char === "\"" || char === "'" || char === "`" || char === "/") {
689
+ const skipped = skipStringOrComment(value, i);
690
+ if (skipped !== -1) {
691
+ i = skipped - 1;
692
+ continue;
693
+ }
694
+ }
695
+ if (char === "?" && value[i + 1] === "." && !/[0-9]/.test(value[i + 2] ?? "")) {
696
+ let next = value[i + 2];
697
+ if (next === "(") throw new Error(`Cannot bind to "${value}": optional calls are not supported in binding expressions`);
698
+ lastChainStart = i;
699
+ if (next === "[") i += 1;
700
+ continue;
701
+ }
702
+ }
703
+ if (lastChainStart === -1) return `${value} = ${right};`;
704
+ let guard = value.substring(0, lastChainStart);
705
+ let next = value[lastChainStart + 2];
706
+ if (next !== "[" && !/[A-Za-z0-9_$]/.test(next ?? "")) throw new Error(`Cannot bind to "${value}": unsupported optional chain in binding expression`);
707
+ let suffix = value.substring(lastChainStart + (next === "[" ? 2 : 1));
708
+ let varSuffix = name.replace(/[^a-zA-Z0-9_$]/g, "_");
709
+ imports?.add("$peek");
710
+ return `const t_bind_${varSuffix} = ${right};\nconst t_target_${varSuffix} = $peek(() => ${guard});\nif (t_target_${varSuffix} != null) t_target_${varSuffix}${suffix} = t_bind_${varSuffix};`;
711
+ }
278
712
  //#endregion
279
713
  //#region src/compile/utils/isSpecialNode.ts
280
714
  function isSpecialNode(node) {
281
715
  return node.type === "special";
282
716
  }
283
-
284
717
  //#endregion
285
718
  //#region src/compile/build/utils/nextVarName.ts
286
719
  /**
@@ -298,7 +731,6 @@ function nextVarName(name, status) {
298
731
  status.varNames[name] += 1;
299
732
  return varName;
300
733
  }
301
-
302
734
  //#endregion
303
735
  //#region src/compile/build/client/addMappedTextWithOffsets.ts
304
736
  function addMappedTextWithOffsets(preText, text, postText, spans, offsets, lengths, status, b) {
@@ -320,35 +752,50 @@ function addMappedTextWithOffsets(preText, text, postText, spans, offsets, lengt
320
752
  }
321
753
  } else b.append(preText + text + postText);
322
754
  }
323
-
324
755
  //#endregion
325
756
  //#region src/compile/build/client/buildAddFragment.ts
326
757
  function buildAddFragment(node, status, b, parentName, anchorName) {
327
758
  if (node.fragment) {
328
759
  const fragment = node.fragment;
329
760
  const fragmentName = `t_fragment_${fragment.number}`;
330
- status.imports.add("t_add_fragment");
331
761
  if (fragment.effects.length > 0) {
332
762
  status.imports.add("$run");
763
+ let forVarMask;
764
+ for (let effect of fragment.effects) if (effect.forVarMask !== void 0) {
765
+ if (forVarMask === void 0) forVarMask = 0;
766
+ forVarMask |= effect.forVarMask;
767
+ }
768
+ let trailing = "";
769
+ if (forVarMask !== void 0) trailing = `, undefined, { forVarMask: ${forVarMask} }`;
333
770
  b.append("$run(() => {");
334
771
  if (status.options.mapped === true) for (let effect of fragment.effects) addMappedTextWithOffsets("", effect.functionBody, "", effect.spans, effect.offsets, effect.lengths, status, b);
335
772
  else b.append(fragment.effects.map((e) => e.functionBody).join("\n"));
336
- b.append(`}${status.options.dev === true ? `, "setAttributes"` : ""});`);
773
+ b.append(`}${status.options.dev === true ? `, "setAttributes"${trailing}` : trailing});`);
337
774
  }
338
- let params = [
339
- fragmentName,
340
- parentName,
341
- anchorName
342
- ];
343
- if (fragment.endVarName) params.push(fragment.endVarName);
344
- b.append(`t_add_fragment(${params.join(", ")});`);
345
- if (fragment.endVarName) {
346
- status.imports.add("t_next");
347
- b.append(`t_next(${fragment.endVarName});`);
775
+ if (fragment.singleRootElement) {
776
+ status.imports.add("t_add_element");
777
+ b.append(`t_add_element(${fragment.endVarName ?? fragmentName}, ${parentName}, ${anchorName});`);
778
+ if (fragment.endVarName) {
779
+ status.imports.add("t_next");
780
+ b.append(`t_next(${fragment.endVarName});`);
781
+ }
782
+ } else {
783
+ status.imports.add("t_add_fragment");
784
+ let params = [
785
+ fragmentName,
786
+ parentName,
787
+ anchorName
788
+ ];
789
+ if (fragment.endVarName) params.push(fragment.endVarName);
790
+ if (fragment.endVarName && fragment.rootVarName) params.push(fragment.rootVarName);
791
+ b.append(`t_add_fragment(${params.join(", ")});`);
792
+ if (fragment.endVarName) {
793
+ status.imports.add("t_next");
794
+ b.append(`t_next(${fragment.endVarName});`);
795
+ }
348
796
  }
349
797
  }
350
798
  }
351
-
352
799
  //#endregion
353
800
  //#region src/compile/parse/utils/isSpaceChar.ts
354
801
  /**
@@ -363,7 +810,6 @@ function isSpaceChar(text, index) {
363
810
  let code = text.charCodeAt(index);
364
811
  return code === 32 || code >= 9 && code <= 13;
365
812
  }
366
-
367
813
  //#endregion
368
814
  //#region src/compile/parse/utils/isSpace.ts
369
815
  /**
@@ -377,25 +823,11 @@ function isSpace(text) {
377
823
  for (let i = 0; i < text.length; i++) if (!isSpaceChar(text, i)) return false;
378
824
  return true;
379
825
  }
380
-
381
- //#endregion
382
- //#region src/compile/utils/isControlNode.ts
383
- function isControlNode(node) {
384
- return node.type === "control";
385
- }
386
-
387
- //#endregion
388
- //#region src/compile/utils/isElementNode.ts
389
- function isElementNode(node) {
390
- return node.type === "element";
391
- }
392
-
393
826
  //#endregion
394
827
  //#region src/compile/utils/isTextNode.ts
395
828
  function isTextNode(node) {
396
829
  return node.type === "text";
397
830
  }
398
-
399
831
  //#endregion
400
832
  //#region src/compile/build/client/buildFragment.ts
401
833
  /**
@@ -420,9 +852,14 @@ function buildFragment(node, status, b, parentName, anchorName) {
420
852
  declareFragmentVars(node.fragment, node, fragmentPath, status, b, parentName, anchorName, varPaths, false, false);
421
853
  b.append(`]);`);
422
854
  } else {
423
- status.imports.add("t_fragment");
424
855
  const fragmentText = fragment.text.replaceAll("`", "\\`").replaceAll(/\s+/g, " ");
425
- b.append(`const ${fragmentName} = t_fragment($parent.ownerDocument!, t_fragments, ${fragment.number}, \`${fragmentText}\`${fragment.ns ? ", true" : ""});`);
856
+ if (fragment.singleRootElement) {
857
+ status.imports.add("t_fragment_el");
858
+ b.append(`const ${fragmentName} = t_fragment_el($parent.ownerDocument!, t_fragment_els, ${fragment.number}, \`${fragmentText}\`${fragment.ns ? ", true" : ""});`);
859
+ } else {
860
+ status.imports.add("t_fragment");
861
+ b.append(`const ${fragmentName} = t_fragment($parent.ownerDocument!, t_fragments, ${fragment.number}, \`${fragmentText}\`${fragment.ns ? ", true" : ""});`);
862
+ }
426
863
  let fragmentPath = {
427
864
  parent: null,
428
865
  type: "fragment",
@@ -436,17 +873,27 @@ function buildFragment(node, status, b, parentName, anchorName) {
436
873
  }
437
874
  function maybeAddRootNodeDeclaration(node, fragment, fragmentName, status, b, varPaths) {
438
875
  if (!node.children.length) return;
439
- let firstNode = node.children[0];
440
- let firstNodeIsLastNode = node.children.length === 1;
441
- if (isControlNode(firstNode) && firstNode.operation.endsWith(" group") || isElementNode(firstNode) && elementNodeNeedsDeclaration(firstNode, true, firstNodeIsLastNode) || isTextNode(firstNode) && textNodeNeedsDeclaration(firstNode, true, firstNodeIsLastNode)) {} else {
442
- status.imports.add("t_root");
443
- const rootName = `t_root_${fragment.number}`;
444
- const params = [fragmentName];
445
- if (firstNode.type === "text") params.push("true");
446
- const rootPath = `t_root(${params.join(", ")})`;
447
- b.append(`const ${rootName} = ${rootPath};`);
448
- varPaths.set(`t_root(${fragmentName})`, rootName);
449
- printDebug(rootName, status, b);
876
+ const rootName = `t_root_${fragment.number}`;
877
+ const firstRendering = firstRenderingChild(node.children);
878
+ const isTextRoot = !!(firstRendering && isTextNode(firstRendering));
879
+ const rootFn = fragment.singleRootElement ? "t_root_el" : "t_root";
880
+ status.imports.add(rootFn);
881
+ const params = [fragmentName];
882
+ if (!fragment.singleRootElement && isTextRoot) params.push("true");
883
+ const rootPath = `${rootFn}(${params.join(", ")})`;
884
+ b.append(`const ${rootName} = ${rootPath};`);
885
+ varPaths.set(`${rootFn}(${fragmentName})`, rootName);
886
+ if (!fragment.singleRootElement) fragment.rootVarName = rootName;
887
+ }
888
+ /**
889
+ * Returns the first child that actually renders to the DOM, skipping comments
890
+ * and no-output control nodes like @key/@const.
891
+ */
892
+ function firstRenderingChild(children) {
893
+ for (const child of children) {
894
+ if (child.type === "comment") continue;
895
+ if (isControlNode(child) && NON_RENDERING_OPERATIONS.has(child.operation)) continue;
896
+ return child;
450
897
  }
451
898
  }
452
899
  function declareFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, lastChild, declare) {
@@ -469,6 +916,7 @@ function declareFragmentVars(fragment, node, path, status, b, parentName, anchor
469
916
  case "special":
470
917
  declareSpecialFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, lastChild, declare);
471
918
  break;
919
+ case "comment": break;
472
920
  default: throw new Error(`Invalid node type: ${node.type}`);
473
921
  }
474
922
  }
@@ -481,6 +929,7 @@ function declareControlFragmentVars(fragment, node, path, status, b, parentName,
481
929
  case "@switch group":
482
930
  case "@for group":
483
931
  case "@await group":
932
+ case "@try group":
484
933
  case "@replace group":
485
934
  case "@html group":
486
935
  declareParentAndAnchorFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, node.operation.substring(1).replace(" group", ""), declare);
@@ -529,10 +978,17 @@ function declareElementFragmentVars(fragment, node, path, status, b, parentName,
529
978
  if (topLevel) fragment.endVarName = node.varName;
530
979
  const varPath = getFragmentVarPath(fragment, status, node.varName, elementPath, varPaths);
531
980
  if (status.options.useCreateElement === true) b.append(`let ${node.varName};`);
532
- else if (node.tagName === "@element") b.append(`let ${node.varName} = ${varPath} as ${elementTypeName(node)};`);
533
981
  else {
534
- b.append(`const ${node.varName} = ${varPath} as ${elementTypeName(node)};`);
535
- printDebug(node.varName, status, b);
982
+ if (node.tagName === "@element") b.append(`let ${node.varName} = ${varPath} as ${elementTypeName(node)};`);
983
+ else {
984
+ b.append(`const ${node.varName} = ${varPath} as ${elementTypeName(node)};`);
985
+ node.varName;
986
+ }
987
+ const refAttribute = node.attributes.find((a) => a.name === "&ref");
988
+ if (refAttribute) {
989
+ let { value, fullyReactive } = refAttribute;
990
+ if (value != null && fullyReactive) b.append(`${value} = ${node.varName};`);
991
+ }
536
992
  }
537
993
  }
538
994
  }
@@ -576,7 +1032,7 @@ function declareTextFragmentVars(fragment, node, path, status, b, varPaths, last
576
1032
  if (status.options.useCreateElement === true) b.append(`let ${node.varName};`);
577
1033
  else {
578
1034
  b.append(`const ${node.varName} = ${varPath};`);
579
- printDebug(node.varName, status, b);
1035
+ node.varName;
580
1036
  }
581
1037
  }
582
1038
  } else {
@@ -593,8 +1049,9 @@ function declareSpecialFragmentVars(fragment, node, path, status, b, parentName,
593
1049
  switch (node.tagName) {
594
1050
  case "slot":
595
1051
  case "fill":
1052
+ case "filldef":
596
1053
  for (let [i, child] of node.children.entries()) {
597
- if (node.tagName === "slot" && isSpecialNode(child) && child.tagName === "fill") continue;
1054
+ if (node.tagName === "slot" && isSpecialNode(child) && (child.tagName === "fill" || child.tagName === "filldef")) continue;
598
1055
  declareFragmentVars(fragment, child, path, status, b, parentName, anchorName, varPaths, i === node.children.length - 1, declare);
599
1056
  }
600
1057
  break;
@@ -608,7 +1065,8 @@ function declareSpecialFragmentVars(fragment, node, path, status, b, parentName,
608
1065
  }
609
1066
  }
610
1067
  function declareParentAndAnchorFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, name, declare) {
611
- if (!path.parent) node.parentName = `t_fragment_${fragment.number}`;
1068
+ const topLevel = !path.parent;
1069
+ if (topLevel) node.parentName = `t_fragment_${fragment.number}`;
612
1070
  else {
613
1071
  const parentPath = path;
614
1072
  const oldChildren = parentPath.children;
@@ -621,7 +1079,7 @@ function declareParentAndAnchorFragmentVars(fragment, node, path, status, b, par
621
1079
  if (status.options.useCreateElement === true) b.append(`let ${node.parentName} as HTMLElement;`);
622
1080
  else {
623
1081
  b.append(`const ${node.parentName} = ${parentVarPath} as HTMLElement;`);
624
- printDebug(node.parentName, status, b);
1082
+ node.parentName;
625
1083
  }
626
1084
  varPaths.set(parentVarPath, node.parentName);
627
1085
  }
@@ -634,12 +1092,13 @@ function declareParentAndAnchorFragmentVars(fragment, node, path, status, b, par
634
1092
  };
635
1093
  path.children.push(anchorPath);
636
1094
  node.varName = nextVarName(`${name}_anchor`, status);
1095
+ if (topLevel) fragment.endVarName = node.varName;
637
1096
  const anchorVarPath = getFragmentVarPath(fragment, status, node.varName, anchorPath, varPaths);
638
1097
  if (status.options.useCreateElement === true) b.append(`let ${node.varName};`);
639
1098
  else {
640
1099
  status.imports.add("t_anchor");
641
1100
  b.append(`let ${node.varName} = t_anchor(${anchorVarPath}) as HTMLElement;`);
642
- printDebug(node.varName, status, b);
1101
+ node.varName;
643
1102
  }
644
1103
  } else {
645
1104
  status.imports.add("t_cmt");
@@ -652,7 +1111,8 @@ function getFragmentVarPath(fragment, status, name, path, varPaths) {
652
1111
  let node = path;
653
1112
  while (node.parent) node = node.parent;
654
1113
  let varName = `t_fragment_${fragment.number}`;
655
- let varPath = getFragmentVarPathPart(node, varName, status, true);
1114
+ const rootFn = fragment.singleRootElement ? "t_root_el" : "t_root";
1115
+ let varPath = getFragmentVarPathPart(node, varName, status, true, rootFn);
656
1116
  for (let [existingPath, existingName] of varPaths) if (varPath.includes(existingPath)) varPath = varPath.replace(existingPath, existingName);
657
1117
  if (name !== "?") {
658
1118
  varPaths.set(varPath, name);
@@ -677,10 +1137,10 @@ function getFragmentVarPath(fragment, status, name, path, varPaths) {
677
1137
  varPath = varPath.replaceAll("t_next_text", "t_next");
678
1138
  return varPath;
679
1139
  }
680
- function getFragmentVarPathPart(path, varPath, status, root = false) {
1140
+ function getFragmentVarPathPart(path, varPath, status, root = false, rootFn = "t_root") {
681
1141
  if (root) {
682
- status.imports.add("t_root");
683
- varPath = `t_root(${varPath})`;
1142
+ status.imports.add(rootFn);
1143
+ varPath = `${rootFn}(${varPath})`;
684
1144
  } else {
685
1145
  status.imports.add("t_child");
686
1146
  varPath = `t_child(${varPath})`;
@@ -695,37 +1155,72 @@ function getFragmentVarPathPart(path, varPath, status, root = false) {
695
1155
  }
696
1156
  return varPath;
697
1157
  }
698
- function printDebug(varName, status, b) {}
699
-
1158
+ //#endregion
1159
+ //#region src/compile/utils/forVarsReadIn.ts
1160
+ /**
1161
+ * Returns a bitmask of the for-loop variables whose substituted paths appear
1162
+ * in `expr`. Bit N is set when the Nth for-var (by position in `forVarNames`)
1163
+ * is read.
1164
+ *
1165
+ * After `replaceForVarNames` has run, every reference to a for-var in the
1166
+ * body has been rewritten to its data-bag path (e.g. `row` →
1167
+ * `t_item_42.data.row`, or `t_item_42.data` for single-var loops). This
1168
+ * helper scans the POST-substitution `expr` for those paths and returns a
1169
+ * bitmask — used to annotate each effect with `forVarMask` so the keyed-list
1170
+ * reconciler's no-proxy force-rerun path (`rerunRegionEffects`) can skip
1171
+ * effects that don't depend on the changed for-vars via a single bitwise AND.
1172
+ *
1173
+ * Conservative by design: a false positive (claiming a dependency that
1174
+ * doesn't really exist) just causes an unnecessary re-run, while a false
1175
+ * negative would drop a needed update. String literals that happen to
1176
+ * contain the substituted path are extremely unlikely and harmless.
1177
+ */
1178
+ function forVarsReadIn(expr, forVarNames) {
1179
+ let mask = 0;
1180
+ for (let i = 0; i < forVarNames.length; i++) {
1181
+ const subPath = forVarNames[i][1];
1182
+ if (subPath && expr.includes(subPath)) mask |= 1 << i;
1183
+ }
1184
+ return mask;
1185
+ }
700
1186
  //#endregion
701
1187
  //#region src/compile/build/client/replaceForVarNames.ts
702
1188
  /**
703
1189
  * Variables used within a for loop need to be retrieved from the loop data.
704
1190
  */
705
1191
  function replaceForVarNames(value, status) {
706
- for (let varName of status.forVarNames) value = value.replaceAll(new RegExp(`(^|\\s|\\(|\\[|\\{|!)${varName[0]}($|\\s|\\.|,|\\(|\\)|\\[|\\]|\\}|;)`, "g"), `$1${varName[1]}$2`);
707
- return value;
1192
+ if (status.forVarNames.length === 0) return value;
1193
+ let result = "";
1194
+ let index = 0;
1195
+ for (let [start, end] of codeRanges(value)) {
1196
+ result += value.substring(index, start);
1197
+ result += replaceVarNames(value.substring(start, end), status);
1198
+ index = end;
1199
+ }
1200
+ result += value.substring(index);
1201
+ return result;
1202
+ }
1203
+ function replaceVarNames(code, status) {
1204
+ for (let varName of status.forVarNames) code = code.replaceAll(new RegExp(`(^|\\s|\\(|\\[|\\{|!|\\?|\\.\\.\\.)${varName[0]}($|\\s|\\.|,|\\(|\\)|\\[|\\]|\\}|;|\\?)`, "g"), `$1${varName[1]}$2`);
1205
+ return code;
708
1206
  }
709
-
710
1207
  //#endregion
711
1208
  //#region src/compile/build/client/buildRun.ts
712
1209
  function buildRun(functionName, functionBody, status, b) {
713
1210
  functionBody = replaceForVarNames(functionBody, status);
714
1211
  addPushDevBoundary("run", functionName, status, b);
1212
+ let forVarMask = status.forVarNames.length > 0 ? forVarsReadIn(functionBody, status.forVarNames) : void 0;
1213
+ let trailing = "";
1214
+ if (status.options.dev === true) {
1215
+ trailing = `, "${functionName}"`;
1216
+ if (forVarMask !== void 0) trailing += `, { forVarMask: ${forVarMask} }`;
1217
+ } else if (forVarMask !== void 0) trailing = `, undefined, { forVarMask: ${forVarMask} }`;
715
1218
  status.imports.add("$run");
716
1219
  b.append("$run(() => {");
717
1220
  b.append(functionBody);
718
- b.append(`}${status.options.dev === true ? `, "${functionName}"` : ""});`);
1221
+ b.append(`}${trailing});`);
719
1222
  addPopDevBoundary(status, b);
720
1223
  }
721
-
722
- //#endregion
723
- //#region src/compile/utils/endOfString.ts
724
- function endOfString(char, content, start) {
725
- for (let j = start + 1; j < content.length; j++) if (content[j] === char && content[j - 1] !== "\\") return j;
726
- return content.length;
727
- }
728
-
729
1224
  //#endregion
730
1225
  //#region src/compile/build/client/getAttributeOffsets.ts
731
1226
  function getAttributeOffsets(value, span) {
@@ -738,7 +1233,6 @@ function getAttributeOffsets(value, span) {
738
1233
  let level = 0;
739
1234
  for (let i = 0; i < value.length; i++) {
740
1235
  const char = value[i];
741
- const nextChar = value[i + 1];
742
1236
  if (char === "{") {
743
1237
  level++;
744
1238
  if (level === 1) {
@@ -761,25 +1255,10 @@ function getAttributeOffsets(value, span) {
761
1255
  }
762
1256
  } else if (char === "`" && level === 0) textContent += "\\";
763
1257
  else if (level > 0) {
764
- if (char === "/" && nextChar === "/") {
765
- let start = i;
766
- i = value.indexOf("\n", i);
767
- textContent += value.substring(start, i + 1);
768
- continue;
769
- } else if (char === "/" && nextChar === "*") {
770
- let start = i;
771
- i = value.indexOf("*/", i) + 1;
772
- textContent += value.substring(start, i + 1);
773
- continue;
774
- } else if (char === "\"" || char === "'") {
775
- let start = i;
776
- i = endOfString(char, value, i);
777
- textContent += value.substring(start, i + 1);
778
- continue;
779
- } else if (char === "`") {
780
- let start = i;
781
- i = endOfTemplateString(value, i);
782
- textContent += value.substring(start, i + 1);
1258
+ const skipped = skipStringOrComment(value, i);
1259
+ if (skipped !== -1) {
1260
+ textContent += value.substring(i, skipped);
1261
+ i = skipped - 1;
783
1262
  continue;
784
1263
  }
785
1264
  }
@@ -793,7 +1272,6 @@ function getAttributeOffsets(value, span) {
793
1272
  lengths
794
1273
  };
795
1274
  }
796
-
797
1275
  //#endregion
798
1276
  //#region src/compile/build/client/buildComponentNode.ts
799
1277
  function buildComponentNode(node, status, b, root = false) {
@@ -810,51 +1288,31 @@ function buildComponentNode(node, status, b, root = false) {
810
1288
  name = name.substring(1);
811
1289
  props.push({
812
1290
  name,
813
- preText: "",
814
1291
  value,
815
- postText: "",
816
- spans: [span],
817
- offsets: [],
818
- lengths: []
1292
+ spans: [span]
819
1293
  });
820
1294
  runs.push(`${propsName}["${name}"] = ${value};`);
821
- bindingRuns.push(`${value} = ${propsName}["${name}"];`);
1295
+ bindingRuns.push(bindingTarget(value, `${propsName}["${name}"]`, name, status.imports));
822
1296
  } else if (name === "class") {
823
- status.imports.add("t_class");
824
- const params = [value];
825
- if (node.scopeStyles) params.push(`"torp-${status.styleHash}"`);
826
- value = params.join(", ");
1297
+ if (node.scopeStyles) value = `[${value}, "torp-${status.styleHash}"]`;
827
1298
  props.push({
828
1299
  name,
829
- preText: "t_class(",
830
1300
  value,
831
- postText: ")",
832
- spans: [span],
833
- offsets: [],
834
- lengths: []
1301
+ spans: [span]
835
1302
  });
836
- runs.push(`${propsName}["${name}"] = t_class(${value});`);
1303
+ runs.push(`${propsName}["${name}"] = ${value};`);
837
1304
  } else if (name === "style") {
838
- status.imports.add("t_style");
839
1305
  props.push({
840
1306
  name,
841
- preText: "t_style(",
842
1307
  value,
843
- postText: ")",
844
- spans: [span],
845
- offsets: [],
846
- lengths: []
1308
+ spans: [span]
847
1309
  });
848
- runs.push(`${propsName}["${name}"] = t_style(${value});`);
1310
+ runs.push(`${propsName}["${name}"] = ${value};`);
849
1311
  } else {
850
1312
  props.push({
851
1313
  name,
852
- preText: "",
853
1314
  value,
854
- postText: "",
855
- spans: [span],
856
- offsets: [],
857
- lengths: []
1315
+ spans: [span]
858
1316
  });
859
1317
  runs.push(`${propsName}["${name}"] = ${value};`);
860
1318
  }
@@ -862,38 +1320,28 @@ function buildComponentNode(node, status, b, root = false) {
862
1320
  const { newValue, spans, offsets, lengths } = getAttributeOffsets(value, span);
863
1321
  value = newValue;
864
1322
  if (name === "class") {
865
- status.imports.add("t_class");
866
- const params = [value];
867
- if (node.scopeStyles) params.push(`"torp-${status.styleHash}"`);
868
- value = params.join(", ");
1323
+ if (node.scopeStyles) value = `[${value}, "torp-${status.styleHash}"]`;
869
1324
  props.push({
870
1325
  name,
871
- preText: "t_class(",
872
1326
  value,
873
- postText: ")",
874
1327
  spans,
875
1328
  offsets,
876
1329
  lengths
877
1330
  });
878
- runs.push(`${propsName}["${name}"] = t_class(${value});`);
1331
+ runs.push(`${propsName}["${name}"] = ${value};`);
879
1332
  } else if (name === "style") {
880
- status.imports.add("t_style");
881
1333
  props.push({
882
1334
  name,
883
- preText: "t_style(",
884
1335
  value,
885
- postText: ")",
886
1336
  spans,
887
1337
  offsets,
888
1338
  lengths
889
1339
  });
890
- runs.push(`${propsName}["${name}"] = t_style(${value});`);
1340
+ runs.push(`${propsName}["${name}"] = ${value};`);
891
1341
  } else {
892
1342
  props.push({
893
1343
  name,
894
- preText: "",
895
1344
  value,
896
- postText: "",
897
1345
  spans,
898
1346
  offsets,
899
1347
  lengths
@@ -904,27 +1352,22 @@ function buildComponentNode(node, status, b, root = false) {
904
1352
  if (name === "class" && node.scopeStyles) value = `"${trimQuotes(value)} torp-${status.styleHash}"`;
905
1353
  props.push({
906
1354
  name,
907
- preText: "",
908
1355
  value,
909
- postText: "",
910
- spans: [span],
911
- offsets: [],
912
- lengths: []
1356
+ postText: " as const",
1357
+ spans: [span]
913
1358
  });
914
1359
  } else props.push({
915
1360
  name,
916
- preText: "",
917
1361
  value: "true",
918
- postText: "",
919
- spans: [span],
920
- offsets: [],
921
- lengths: []
1362
+ spans: [span]
922
1363
  });
923
- b.append(`const ${propsName} = $watch({`);
1364
+ b.append(`let ${propsName} = $watch({`);
924
1365
  for (let p of props) {
925
1366
  let value = replaceForVarNames(p.value, status);
926
- if (p.spans.length === 1) addMappedText(`${p.name}: ` + p.preText, value, p.postText + ",", p.spans[0], status, b);
927
- else addMappedTextWithOffsets(`${p.name}: ` + p.preText, value, p.postText + ",", p.spans, p.offsets, p.lengths, status, b);
1367
+ p.preText ??= "";
1368
+ p.postText ??= "";
1369
+ if (p.spans.length === 1) addMappedText(p.name ? `${p.name}: ` + p.preText : p.preText, value, p.postText + ",", p.spans[0], status, b);
1370
+ else addMappedTextWithOffsets(p.name ? `${p.name}: ` + p.preText : p.preText, value, p.postText + ",", p.spans, p.offsets, p.lengths, status, b);
928
1371
  }
929
1372
  b.append("});");
930
1373
  if (runs.length) buildRun("setProps", replaceForVarNames(runs.join("\n"), status), status, b);
@@ -946,14 +1389,14 @@ function buildComponentNode(node, status, b, root = false) {
946
1389
  const slotsName = componentHasSlots ? nextVarName("slots", status) : "undefined";
947
1390
  if (componentHasSlots) {
948
1391
  b.append(`const ${slotsName}: Record<string, SlotRender> = {};`);
949
- for (let slot of node.children) if (isSpecialNode(slot) && slot.tagName === "fill") {
1392
+ for (let slot of node.children) if (isSpecialNode(slot) && (slot.tagName === "fill" || slot.tagName === "filldef")) {
950
1393
  const nameAttribute = slot.attributes.find((a) => a.name === "name");
951
1394
  const slotName = nameAttribute?.value ? trimQuotes(nameAttribute.value) : "_";
952
1395
  const slotParams = [
953
1396
  "$sparent: ParentNode",
954
1397
  "$sanchor: Node | null",
955
- slot.hasSlotProps ? "$sprops: Record<PropertyKey, any>" : "// @ts-ignore\n$sprops?: Record<PropertyKey, any>",
956
- "// @ts-ignore\n$context?: Record<PropertyKey, any>"
1398
+ slot.hasSlotProps ? "$slot: Record<PropertyKey, any>" : "_$slot?: Record<PropertyKey, any>",
1399
+ "// @ts-ignore\n// eslint-disable-next-line no-unused-vars\n$context?: Record<PropertyKey, any>"
957
1400
  ];
958
1401
  b.append(`${slot.hasSlotProps ? "// @ts-ignore\n" : ""}${slotsName}["${slotName}"] = (\n${slotParams.join(",\n")}\n) => {`);
959
1402
  buildFragment(slot, status, b, "$sparent", "$sanchor");
@@ -974,10 +1417,175 @@ function buildComponentNode(node, status, b, root = false) {
974
1417
  let selfAttribute = node.attributes.find((a) => a.name === "self");
975
1418
  if (selfAttribute && selfAttribute.value && selfAttribute.fullyReactive) componentName = selfAttribute.value;
976
1419
  }
977
- addMappedText("", `${componentName}(${renderParams})`, ";", node.span, status, b);
978
- b.append("");
1420
+ addMappedText("", `${componentName}(${renderParams})`, ";", node.span, status, b);
1421
+ b.append("");
1422
+ }
1423
+ //#endregion
1424
+ //#region src/compile/build/client/buildAwaitNode.ts
1425
+ function buildAwaitNode(node, status, b) {
1426
+ const anchorName = node.varName ?? "null";
1427
+ const parentName = node.parentName || anchorName + ".parentNode";
1428
+ const regionName = nextVarName("await_region", status);
1429
+ const branches = node.children.filter((n) => isControlNode(n));
1430
+ const awaitBranch = branches.find((n) => n.operation === "@await");
1431
+ const withBranch = branches.find((n) => n.operation === "@with");
1432
+ status.imports.add("t_region");
1433
+ status.imports.add("t_run_await");
1434
+ b.append("");
1435
+ b.append("/* @await */");
1436
+ addPushDevBoundary("control", "@await", status, b);
1437
+ b.append(`
1438
+ const ${regionName} = t_region(${status.options.dev === true ? `"await"` : ""});
1439
+ t_run_await(${regionName}, ${anchorName}, (${status.inHead ? "" : "t_before"}) => {`);
1440
+ if (awaitBranch !== void 0 && awaitBranch.children.length > 0) {
1441
+ buildFragment(awaitBranch, status, b, parentName, "t_before");
1442
+ status.fragmentStack.push({
1443
+ fragment: awaitBranch.fragment,
1444
+ path: ""
1445
+ });
1446
+ for (let child of awaitBranch.children) buildNode(child, status, b, parentName, "t_before");
1447
+ status.fragmentStack.pop();
1448
+ buildAddFragment(awaitBranch, status, b, parentName, "t_before");
1449
+ }
1450
+ if (withBranch !== void 0) {
1451
+ b.append(`}, (${status.inHead ? "" : "t_before"}) => {`);
1452
+ if (withBranch.children.length > 0) {
1453
+ buildFragment(withBranch, status, b, parentName, "t_before");
1454
+ status.fragmentStack.push({
1455
+ fragment: withBranch.fragment,
1456
+ path: ""
1457
+ });
1458
+ for (let child of withBranch.children) buildNode(child, status, b, parentName, "t_before");
1459
+ status.fragmentStack.pop();
1460
+ buildAddFragment(withBranch, status, b, parentName, "t_before");
1461
+ }
1462
+ b.append(`}${status.options.dev === true ? `, "runAwait"` : ""});`);
1463
+ } else b.append(`}, null${status.options.dev === true ? `, "runAwait"` : ""});`);
1464
+ addPopDevBoundary(status, b);
1465
+ b.append("");
1466
+ }
1467
+ //#endregion
1468
+ //#region src/compile/utils/hasNestedControl.ts
1469
+ /**
1470
+ * Returns true if any descendant of a `@for` body is a control node that
1471
+ * creates its own region and effects (`@if`, `@for`, `@await`, `@switch`,
1472
+ * `@replace`, `@html`).
1473
+ *
1474
+ * Operations that produce no DOM and no region (`@key`, `@const`, `@console`,
1475
+ * `@debugger`, `@function`) are fine — same category as `@key`. They are
1476
+ * descended through, so a real control statement nested under a `@key` (a
1477
+ * common shape — `@for` bodies almost always lead with `@key = ...`) is still
1478
+ * detected.
1479
+ *
1480
+ * Extracted from `isForBodyNoProxySafe` so the no-proxy specialization, the
1481
+ * leaf-row region-chain specialization (`isForBodyLeafSafe`), and any future
1482
+ * pass that needs the same structural predicate can share one source of
1483
+ * truth.
1484
+ */
1485
+ function hasNestedControl(children) {
1486
+ for (const child of children) {
1487
+ if (isControlNode(child)) {
1488
+ if (!NON_RENDERING_OPERATIONS.has(child.operation)) return true;
1489
+ if (hasNestedControl(child.children)) return true;
1490
+ continue;
1491
+ }
1492
+ if (isElementLike$1(child)) {
1493
+ if (hasNestedControl(child.children)) return true;
1494
+ }
1495
+ }
1496
+ return false;
1497
+ }
1498
+ function isElementLike$1(node) {
1499
+ return node.type === "element" || node.type === "component" || node.type === "special";
1500
+ }
1501
+ //#endregion
1502
+ //#region src/compile/utils/isForBodyLeafSafe.ts
1503
+ /**
1504
+ * Returns true if the body of a `@for` loop contains no nested control
1505
+ * statements (`@if`, `@for`, `@await`, `@switch`, `@replace`, `@html`).
1506
+ *
1507
+ * When true, every effect created by the row template lives directly on the
1508
+ * item's own region — never on a descendant region chained off it. The
1509
+ * compiler can then emit a "leaf-row" `createListItem` that skips the
1510
+ * per-item `pushRegion(item)` / `popRegion(oldRegion)` calls: `runListItems`
1511
+ * has already pushed the item onto the active region (via
1512
+ * `pushRegion(item, true)`) before calling `create()`, and a leaf body never
1513
+ * creates descendant regions, so `context.activeRegion` stays put at the item
1514
+ * for the whole callback. Saves two function calls per row plus the
1515
+ * `devContext.onRegionPushed` / `onRegionPopped` invocations, which on the
1516
+ * 10k-row js-framework-bench `runlots` op is 20000 fewer calls per pass.
1517
+ *
1518
+ * Note: every no-proxy-safe body is also leaf-safe (`isForBodyNoProxySafe`
1519
+ * includes `hasNestedControl`), but the converse is not true — a body that
1520
+ * writes to a for-var (disqualifying it from no-proxy) can still be leaf-safe
1521
+ * and earn this specialization.
1522
+ */
1523
+ function isForBodyLeafSafe(forBodyChildren) {
1524
+ return !hasNestedControl(forBodyChildren);
1525
+ }
1526
+ //#endregion
1527
+ //#region src/compile/utils/isForBodyNoProxySafe.ts
1528
+ /**
1529
+ * Returns true if the body of a `@for` loop never writes to any of its loop
1530
+ * variables (`forVars`) — neither directly (`row = ...`, `row.x = ...`,
1531
+ * `row[k] = ...`) nor via compound assignment (`+=`, `++`, etc.) — AND the
1532
+ * body contains no nested control statements (`@if`, `@for`, `@await`, …).
1533
+ *
1534
+ * When both hold, the compiler can emit a "no-proxy" `@for` specialization
1535
+ * that skips the per-item shallow `$watch` Proxy around the loop variable
1536
+ * bag: reads of `data.<forVar>` are plain property accesses (no proxyGet
1537
+ * trap), and the compiler-emitted `updateListItem` callback re-runs item
1538
+ * effects manually (via `t_rerun_region_effects`) when a forVar's reference
1539
+ * actually changes.
1540
+ *
1541
+ * The "no nested controls" constraint keeps the manual effect re-run sound:
1542
+ * without it, a nested `@if`'s effects would live on a descendant region
1543
+ * chained off the original item, but after keyed reconciliation that chain
1544
+ * is orphaned (the live item is a different object), so the re-run walk
1545
+ * couldn't find them. With no nested controls every row effect is on the
1546
+ * item itself, which is exactly what `t_rerun_region_effects` walks.
1547
+ *
1548
+ * Detection is intentionally conservative: a regex scan over every
1549
+ * expression-like string in the body (text interpolation, attribute values,
1550
+ * event handlers, control statements). Anything ambiguous falls back to the
1551
+ * proxy path. The for-statement itself (`for (let row of ...)`) is NOT
1552
+ * scanned — that's the loop header, not the body — so `for (...; i++)` style
1553
+ * loops with `i++` in the header are still eligible.
1554
+ */
1555
+ function isForBodyNoProxySafe(forBodyChildren, forVars) {
1556
+ if (forVars.length === 0) return false;
1557
+ if (hasNestedControl(forBodyChildren)) return false;
1558
+ const collected = [];
1559
+ for (const child of forBodyChildren) collectExpressionStrings(child, collected);
1560
+ for (const varName of forVars) {
1561
+ const escaped = varName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1562
+ const writePattern = new RegExp(`\\[\\s*${escaped}\\s*\\]\\s*=(?![=>])|\\{\\s*${escaped}\\s*(:[^}]+)?\\}\\s*=(?![=>])|\\b${escaped}(?:\\.[a-zA-Z_$][\\w$]*|\\[[^\\]]+\\])?\\s*(?:=(?![=>])|[+\\-*/%&|^]=|\\+\\+|--)|(?:\\+\\+|--)\\s*${escaped}\\b`);
1563
+ for (const text of collected) if (writePattern.test(text)) return false;
1564
+ }
1565
+ return true;
1566
+ }
1567
+ function isElementLike(node) {
1568
+ return node.type === "element" || node.type === "component" || node.type === "special";
1569
+ }
1570
+ function collectExpressionStrings(node, out) {
1571
+ if (isTextNode(node)) {
1572
+ out.push(node.content);
1573
+ return;
1574
+ }
1575
+ if (isElementLike(node)) {
1576
+ for (const attr of node.attributes) {
1577
+ if (attr.value !== void 0) if (attr.name.startsWith("&")) out.push(`${attr.value} =`);
1578
+ else out.push(attr.value);
1579
+ if (attr.fullyReactive && attr.name !== attr.value) out.push(attr.name);
1580
+ }
1581
+ for (const child of node.children) collectExpressionStrings(child, out);
1582
+ return;
1583
+ }
1584
+ if (isControlNode(node)) {
1585
+ out.push(node.statement);
1586
+ for (const child of node.children) collectExpressionStrings(child, out);
1587
+ }
979
1588
  }
980
-
981
1589
  //#endregion
982
1590
  //#region src/compile/utils/trimMatched.ts
983
1591
  /**
@@ -996,102 +1604,6 @@ function trimMatched(text, startChar, endChar) {
996
1604
  }
997
1605
  return text.substring(start, end);
998
1606
  }
999
-
1000
- //#endregion
1001
- //#region src/compile/build/client/buildAwaitNode.ts
1002
- function buildAwaitNode(node, status, b) {
1003
- const anchorName = node.varName ?? "null";
1004
- const parentName = node.parentName;
1005
- const regionName = nextVarName("await_region", status);
1006
- const indexName = nextVarName("await_index", status);
1007
- const tokenName = nextVarName("await_token", status);
1008
- const branches = node.children.filter((n) => isControlNode(n));
1009
- let awaitBranch = branches.find((n) => n.operation === "@await");
1010
- if (!awaitBranch) {}
1011
- let thenBranch = branches.find((n) => n.operation === "@then");
1012
- if (!thenBranch) thenBranch = {
1013
- type: "control",
1014
- operation: "@then",
1015
- statement: "then",
1016
- children: [],
1017
- span: {
1018
- start: 0,
1019
- end: 0
1020
- }
1021
- };
1022
- let catchBranch = branches.find((n) => n.operation === "@catch");
1023
- if (!catchBranch) catchBranch = {
1024
- type: "control",
1025
- operation: "@catch",
1026
- statement: "catch",
1027
- children: [],
1028
- span: {
1029
- start: 0,
1030
- end: 0
1031
- }
1032
- };
1033
- const awaiterName = trimMatched(awaitBranch.statement.substring(5).trim(), "(", ")");
1034
- const thenVar = trimMatched(thenBranch.statement.substring(4).trim(), "(", ")");
1035
- const catchVar = trimMatched(catchBranch.statement.substring(5).trim(), "(", ")");
1036
- status.imports.add("t_region");
1037
- status.imports.add("t_run_control");
1038
- status.imports.add("t_run_branch");
1039
- status.imports.add("t_push_region");
1040
- status.imports.add("t_pop_region");
1041
- b.append("");
1042
- b.append("/* @await */");
1043
- addPushDevBoundary("control", `@${branches[0].statement}`, status, b);
1044
- b.append(`
1045
- const ${regionName} = t_region(${status.options.dev === true ? `"await"` : ""});
1046
- let ${tokenName} = 0;
1047
- let ${indexName} = -1;
1048
- t_run_control(${regionName}, ${anchorName}, (t_before) => {`);
1049
- let index = 0;
1050
- buildAwaitBranch(awaitBranch, status, b, parentName, regionName, indexName, index++);
1051
- b.append(`
1052
- ${tokenName}++;
1053
- ((t_token) => {`);
1054
- awaitBranch.span.start += 7;
1055
- addMappedText("", replaceForVarNames(awaiterName, status), "", awaitBranch.span, status, b);
1056
- thenBranch.span.start -= 1;
1057
- addMappedText(".then((", replaceForVarNames(thenVar, status), ") => {", thenBranch.span, status, b);
1058
- b.append(`if (t_token === ${tokenName}) {`);
1059
- buildAwaitBranch(thenBranch, status, b, parentName, regionName, indexName, index++);
1060
- b.append(`}
1061
- })`);
1062
- catchBranch.span.start -= 1;
1063
- addMappedText(".catch((", replaceForVarNames(catchVar, status), ") => {", catchBranch.span, status, b);
1064
- b.append(`if (t_token === ${tokenName}) {`);
1065
- buildAwaitBranch(catchBranch, status, b, parentName, regionName, indexName, index++);
1066
- b.append(`}
1067
- });
1068
- })(${tokenName});
1069
- }${status.options.dev === true ? `, "runAwait"` : ""});`);
1070
- addPopDevBoundary(status, b);
1071
- b.append("");
1072
- }
1073
- function buildAwaitBranch(node, status, b, parentName, regionName, indexName, index) {
1074
- b.append(`if (!t_run_branch(${regionName}, ${indexName}, ${index})) return;`);
1075
- if (node.children.length > 0) {
1076
- b.append(`
1077
- const t_new_region = t_region(${status.options.dev === true ? `"await_branch"` : ""});
1078
- const t_old_control_region = t_push_region(${regionName});
1079
- const t_old_region = t_push_region(t_new_region, true);
1080
- `);
1081
- buildFragment(node, status, b, parentName, "t_before");
1082
- status.fragmentStack.push({
1083
- fragment: node.fragment,
1084
- path: ""
1085
- });
1086
- for (let child of node.children) buildNode(child, status, b, parentName, "t_before");
1087
- status.fragmentStack.pop();
1088
- buildAddFragment(node, status, b, parentName, "t_before");
1089
- b.append("t_pop_region(t_old_region);");
1090
- b.append("t_pop_region(t_old_control_region);");
1091
- }
1092
- b.append(`${indexName} = ${index};`);
1093
- }
1094
-
1095
1607
  //#endregion
1096
1608
  //#region src/compile/build/client/buildForNode.ts
1097
1609
  const forLoopRegex = /for\s*\((.+?);.*?;.*?\)/;
@@ -1116,9 +1628,6 @@ function buildForNode(node, status, b) {
1116
1628
  }
1117
1629
  const regionName = nextVarName("for_region", status);
1118
1630
  const listItemsName = nextVarName("new_items", status);
1119
- const previousItemName = nextVarName("previous_item", status);
1120
- const nextItemName = nextVarName("next_item", status);
1121
- const newItemName = nextVarName("new_item", status);
1122
1631
  const itemName = nextVarName("item", status);
1123
1632
  const beforeName = nextVarName("before", status);
1124
1633
  let keyStatement = "";
@@ -1130,10 +1639,13 @@ function buildForNode(node, status, b) {
1130
1639
  keyStatement = matches[2];
1131
1640
  }
1132
1641
  }
1642
+ const noWatch = isForBodyNoProxySafe(node.children, forVarNames);
1643
+ if (noWatch) status.imports.add("t_rerun_region_effects");
1644
+ const singleVar = noWatch && forVarNames.length === 1;
1645
+ const leafRow = isForBodyLeafSafe(node.children);
1133
1646
  status.imports.add("t_region");
1134
1647
  status.imports.add("t_run_list");
1135
- status.imports.add("t_list_item");
1136
- status.imports.add("ListItem");
1648
+ status.imports.add("ListItemSpec");
1137
1649
  b.append("");
1138
1650
  b.append("/* @for */");
1139
1651
  addPushDevBoundary("control", `@${node.statement}`, status, b);
@@ -1144,41 +1656,51 @@ function buildForNode(node, status, b) {
1144
1656
  ${parentName},
1145
1657
  ${anchorName},
1146
1658
  ${status.options.dev === true ? "function createNewItems() {" : "() => {"}
1147
- let ${listItemsName}: ListItem[] = [];
1148
- let ${previousItemName} = ${regionName};
1149
- let ${nextItemName} = ${regionName}.nextRegion;`);
1659
+ let ${listItemsName}: ListItemSpec[] = [];`);
1150
1660
  addMappedText("", `${replaceForVarNames(node.statement, status)}`, " {", node.span, status, b);
1151
- b.append(`let ${newItemName} = t_list_item(`);
1152
- b.append(`{ ${forVarNames.join(", ")} },`);
1153
- if (key !== void 0) addMappedText("", `${keyStatement || "undefined"}`, ",", key.span, status, b);
1154
- if (status.options.dev === true) b.append(`"for item"`);
1155
- b.append(");");
1156
- b.append(`
1157
- ${newItemName}.previousRegion = ${previousItemName};
1158
- ${previousItemName}.nextRegion = ${newItemName};
1159
- ${previousItemName} = ${newItemName};
1160
- ${listItemsName}.push(${newItemName});
1161
- }
1162
- ${regionName}.nextRegion = ${nextItemName};
1163
- return ${listItemsName};
1164
- },
1165
- ${status.options.dev === true ? `function createListItem(${itemName}, ${beforeName}) {` : `(${itemName}, ${beforeName}) => {`}`);
1661
+ if (singleVar) b.append(`${listItemsName}.push({ data: ${forVarNames[0]}, key: `);
1662
+ else b.append(`${listItemsName}.push({ data: { ${forVarNames.join(", ")} }, key: `);
1663
+ if (key !== void 0) addMappedText("", `${keyStatement || "undefined"}`, " });", key.span, status, b);
1664
+ else b.append(`undefined });`);
1665
+ b.append(`}`);
1666
+ b.append(`return ${listItemsName};`);
1667
+ b.append(`},
1668
+ ${status.options.dev === true ? `function createListItem(${itemName}, ${beforeName}) {` : `(${itemName}, ${beforeName}) => {`}`);
1166
1669
  let oldForVarNames = status.forVarNames;
1167
- status.forVarNames = [...status.forVarNames, ...forVarNames.map((v) => [v, `${itemName}.data.${v}`])];
1168
- buildForItem(node, status, b, parentName, beforeName, itemName);
1670
+ const forVarBitBase = oldForVarNames.length;
1671
+ status.forVarNames = [...status.forVarNames, ...singleVar ? forVarNames.map((v) => [v, `${itemName}.data`]) : forVarNames.map((v) => [v, `${itemName}.data.${v}`])];
1672
+ buildForItem(node, status, b, parentName, beforeName, itemName, leafRow);
1169
1673
  status.forVarNames = oldForVarNames;
1170
1674
  b.append(`},
1171
- ${status.options.dev === true ? "function updateListItem(t_old_item, t_new_item) {" : "(t_old_item, t_new_item) => {"}`);
1172
- for (let varName of forVarNames) b.append(`t_old_item.data.${varName} = t_new_item.data.${varName};`);
1173
- b.append(`}
1174
- );`);
1675
+ ${status.options.dev === true ? "function updateListItem(t_old_item, t_new_item) {" : "(t_old_item, t_new_item) => {"}`);
1676
+ if (noWatch) {
1677
+ b.append(`let t_changed_mask = 0;`);
1678
+ if (singleVar) {
1679
+ const bit = 1 << forVarBitBase;
1680
+ b.append(`if (t_old_item.data !== t_new_item.data) {
1681
+ t_old_item.data = t_new_item.data;
1682
+ t_changed_mask = ${bit};
1683
+ }`);
1684
+ } else for (let i = 0; i < forVarNames.length; i++) {
1685
+ const varName = forVarNames[i];
1686
+ const bit = 1 << forVarBitBase + i;
1687
+ b.append(`if (t_old_item.data.${varName} !== t_new_item.data.${varName}) {
1688
+ t_old_item.data.${varName} = t_new_item.data.${varName};
1689
+ t_changed_mask |= ${bit};
1690
+ }`);
1691
+ }
1692
+ b.append(`if (t_changed_mask) t_rerun_region_effects(t_old_item, t_changed_mask);`);
1693
+ } else for (let varName of forVarNames) b.append(`t_old_item.data.${varName} = t_new_item.data.${varName};`);
1694
+ b.append(`}${noWatch ? ",\ntrue" : ""}\n);`);
1175
1695
  addPopDevBoundary(status, b);
1176
1696
  b.append("");
1177
1697
  }
1178
- function buildForItem(node, status, b, parentName, beforeName, itemName) {
1698
+ function buildForItem(node, status, b, parentName, beforeName, itemName, leafRow) {
1179
1699
  const oldRegionName = nextVarName("old_region", status);
1180
- status.imports.add("t_push_region");
1181
- b.append(`let ${oldRegionName} = t_push_region(${itemName});`);
1700
+ if (!leafRow) {
1701
+ status.imports.add("t_push_region");
1702
+ b.append(`let ${oldRegionName} = t_push_region(${itemName});`);
1703
+ }
1182
1704
  buildFragment(node, status, b, parentName, beforeName);
1183
1705
  status.fragmentStack.push({
1184
1706
  fragment: node.fragment,
@@ -1190,39 +1712,37 @@ function buildForItem(node, status, b, parentName, beforeName, itemName) {
1190
1712
  }
1191
1713
  status.fragmentStack.pop();
1192
1714
  buildAddFragment(node, status, b, parentName, beforeName);
1193
- status.imports.add("t_pop_region");
1194
- b.append(`t_pop_region(${oldRegionName});`);
1715
+ if (!leafRow) {
1716
+ status.imports.add("t_pop_region");
1717
+ b.append(`t_pop_region(${oldRegionName});`);
1718
+ }
1195
1719
  }
1196
-
1197
1720
  //#endregion
1198
1721
  //#region src/compile/build/client/buildHtmlNode.ts
1199
1722
  function buildHtmlNode(node, status, b) {
1200
1723
  const htmlAnchorName = node.varName;
1201
1724
  const htmlParentName = node.parentName || htmlAnchorName + ".parentNode";
1202
- const htmlRegionName = nextVarName("html_region", status);
1203
1725
  node = node.children[0];
1204
1726
  status.imports.add("t_region");
1205
1727
  status.imports.add("t_run_control");
1206
- status.imports.add("t_run_branch");
1207
- status.imports.add("t_push_region");
1208
- status.imports.add("t_pop_region");
1209
- b.append("");
1210
- b.append(`
1211
- /* @html */
1212
- const ${htmlRegionName} = t_region(${status.options.dev === true ? `"html"` : ""});
1213
- t_run_control(${htmlRegionName}, ${htmlAnchorName}, (t_before) => {`);
1214
- buildHtmlBranch(node, status, b, htmlParentName, htmlRegionName);
1215
- b.append(`}${status.options.dev === true ? ", \"html\"" : ""});`);
1728
+ const firstNodeVar = nextVarName("html_first", status);
1729
+ const lastNodeVar = nextVarName("html_last", status);
1216
1730
  b.append("");
1217
- }
1218
- function buildHtmlBranch(node, status, b, parentName, regionName) {
1219
- status.imports.add("t_run_branch");
1731
+ b.append(`/* @html */`);
1732
+ b.append(`let ${firstNodeVar}: ChildNode | null = null;`);
1733
+ b.append(`let ${lastNodeVar}: ChildNode | null = null;`);
1734
+ b.append(`t_run_control(t_region(), ${htmlAnchorName}, (t_before) => {`);
1220
1735
  b.append(`${replaceForVarNames(node.statement, status)};`);
1221
- b.append(`if (!t_run_branch(${regionName}, 0, -1)) return;`);
1222
- b.append(`
1223
- const t_new_region = t_region(${status.options.dev === true ? `"html_branch"` : ""});
1224
- const t_old_region = t_push_region(t_new_region, true);
1225
- `);
1736
+ b.append(`if (${firstNodeVar} !== null && ${lastNodeVar} !== null) {`);
1737
+ b.append(`let t_node: ChildNode | null = ${lastNodeVar};`);
1738
+ b.append(`while (t_node !== null && t_node !== ${firstNodeVar}) {`);
1739
+ b.append(`const t_prev: ChildNode | null = t_node.previousSibling;`);
1740
+ b.append(`t_node.remove();`);
1741
+ b.append(`t_node = t_prev;`);
1742
+ b.append(`}`);
1743
+ b.append(`if (${firstNodeVar}) ${firstNodeVar}.remove();`);
1744
+ b.append(`${firstNodeVar} = ${lastNodeVar} = null;`);
1745
+ b.append(`}`);
1226
1746
  const templateName = nextVarName("template", status);
1227
1747
  const fragmentName = `t_fragment_${node.fragment.number}`;
1228
1748
  b.append(`let ${templateName} = document.createElement("template");`);
@@ -1230,10 +1750,29 @@ function buildHtmlBranch(node, status, b, parentName, regionName) {
1230
1750
  node.span.end -= 2;
1231
1751
  addMappedText("", `${templateName}.innerHTML = ${replaceForVarNames(node.statement, status)};`, "", node.span, status, b);
1232
1752
  b.append(`let ${fragmentName} = ${templateName}.content.cloneNode(true) as DocumentFragment;`);
1233
- buildAddFragment(node, status, b, parentName, "t_before");
1234
- b.append("t_pop_region(t_old_region);");
1753
+ b.append(`${firstNodeVar} = ${fragmentName}.firstChild;`);
1754
+ b.append(`${lastNodeVar} = ${fragmentName}.lastChild;`);
1755
+ buildAddFragment(node, status, b, htmlParentName, "t_before");
1756
+ status.imports.add("t_first_inside");
1757
+ b.append(`if (${firstNodeVar} !== null && ${firstNodeVar}.parentNode !== ${htmlParentName}) {`);
1758
+ b.append(`${lastNodeVar} = ${htmlAnchorName}.previousSibling as ChildNode | null;`);
1759
+ b.append(`if (${lastNodeVar} !== null) {`);
1760
+ b.append(`const t_stashed_first = t_first_inside(${htmlAnchorName} as ChildNode);`);
1761
+ b.append(`if (t_stashed_first !== undefined && t_stashed_first !== null) {`);
1762
+ b.append(`${firstNodeVar} = t_stashed_first;`);
1763
+ b.append(`} else {`);
1764
+ b.append(`${firstNodeVar} = ${lastNodeVar};`);
1765
+ b.append(`let t_scan: ChildNode | null = ${lastNodeVar};`);
1766
+ b.append(`while (t_scan !== null && t_scan.previousSibling !== null && t_scan.previousSibling !== ${htmlAnchorName} && (t_scan.previousSibling.nodeType !== 3 || (t_scan.previousSibling.textContent ?? "").trim() !== "")) {`);
1767
+ b.append(`t_scan = t_scan.previousSibling;`);
1768
+ b.append(`}`);
1769
+ b.append(`${firstNodeVar} = t_scan;`);
1770
+ b.append(`}`);
1771
+ b.append(`}`);
1772
+ b.append(`}`);
1773
+ b.append(`}${status.options.dev === true ? ", \"html\"" : ""});`);
1774
+ b.append("");
1235
1775
  }
1236
-
1237
1776
  //#endregion
1238
1777
  //#region src/compile/build/client/buildIfNode.ts
1239
1778
  function buildIfNode(node, status, b) {
@@ -1291,7 +1830,6 @@ function buildIfBranch(node, status, b, parentName, regionName, indexName, index
1291
1830
  b.append(`${indexName} = ${index};`);
1292
1831
  b.append("}");
1293
1832
  }
1294
-
1295
1833
  //#endregion
1296
1834
  //#region src/compile/build/client/buildReplaceNode.ts
1297
1835
  function buildReplaceNode(node, status, b) {
@@ -1331,7 +1869,6 @@ function buildReplaceBranch(node, status, b, parentName, regionName) {
1331
1869
  buildAddFragment(node, status, b, parentName, "t_before");
1332
1870
  b.append("t_pop_region(t_old_region);");
1333
1871
  }
1334
-
1335
1872
  //#endregion
1336
1873
  //#region src/compile/build/client/buildScriptNode.ts
1337
1874
  function buildScriptNode(node, status, b) {
@@ -1342,7 +1879,6 @@ function maybeAppend$1(text, end) {
1342
1879
  if (!text.endsWith(end)) text += end;
1343
1880
  return text;
1344
1881
  }
1345
-
1346
1882
  //#endregion
1347
1883
  //#region src/compile/build/client/buildSwitchNode.ts
1348
1884
  function buildSwitchNode(node, status, b) {
@@ -1354,7 +1890,7 @@ function buildSwitchNode(node, status, b) {
1354
1890
  if (branches.findIndex((n) => n.operation === "@default") === -1) branches.push({
1355
1891
  type: "control",
1356
1892
  operation: "@default",
1357
- statement: "default",
1893
+ statement: "default:",
1358
1894
  children: [],
1359
1895
  span: {
1360
1896
  start: 0,
@@ -1405,7 +1941,45 @@ function buildSwitchBranch(node, status, b, parentName, regionName, indexName, i
1405
1941
  break;
1406
1942
  }`);
1407
1943
  }
1408
-
1944
+ //#endregion
1945
+ //#region src/compile/build/client/buildTryNode.ts
1946
+ function buildTryNode(node, status, b) {
1947
+ const anchorName = node.varName ?? "null";
1948
+ const parentName = node.parentName || anchorName + ".parentNode";
1949
+ const regionName = nextVarName("try_region", status);
1950
+ const branches = node.children.filter((n) => isControlNode(n));
1951
+ const tryBranch = branches.find((n) => n.operation === "@try");
1952
+ const catchBranch = branches.find((n) => n.operation === "@catch");
1953
+ status.imports.add("t_region");
1954
+ status.imports.add("t_run_try");
1955
+ b.append("");
1956
+ b.append("/* @try */");
1957
+ addPushDevBoundary("control", "@try", status, b);
1958
+ b.append(`
1959
+ const ${regionName} = t_region(${status.options.dev === true ? `"try"` : ""});
1960
+ t_run_try(${regionName}, ${anchorName}, (${status.inHead ? "" : "t_before"}) => {`);
1961
+ buildTryBranchBody(tryBranch, status, b, parentName);
1962
+ const catchVar = catchBranch ? trimMatched(catchBranch.statement.substring(5).trim(), "(", ")") || "err" : null;
1963
+ if (catchBranch && catchVar) {
1964
+ addMappedText("},", `(${status.inHead ? "" : "t_before"}, ${replaceForVarNames(catchVar, status)}) => {`, "", catchBranch.span, status, b);
1965
+ buildTryBranchBody(catchBranch, status, b, parentName);
1966
+ b.append(`}${status.options.dev === true ? `, "runTry"` : ""});`);
1967
+ } else b.append(`}, null${status.options.dev === true ? `, "runTry"` : ""});`);
1968
+ addPopDevBoundary(status, b);
1969
+ b.append("");
1970
+ }
1971
+ function buildTryBranchBody(node, status, b, parentName) {
1972
+ if (node.children.length > 0) {
1973
+ buildFragment(node, status, b, parentName, "t_before");
1974
+ status.fragmentStack.push({
1975
+ fragment: node.fragment,
1976
+ path: ""
1977
+ });
1978
+ for (let child of node.children) buildNode(child, status, b, parentName, "t_before");
1979
+ status.fragmentStack.pop();
1980
+ buildAddFragment(node, status, b, parentName, "t_before");
1981
+ }
1982
+ }
1409
1983
  //#endregion
1410
1984
  //#region src/compile/build/client/buildControlNode.ts
1411
1985
  function buildControlNode(node, status, b) {
@@ -1430,8 +2004,12 @@ function buildControlNode(node, status, b) {
1430
2004
  buildAwaitNode(node, status, b);
1431
2005
  break;
1432
2006
  case "@await":
1433
- case "@then":
2007
+ case "@with":
2008
+ case "@try":
1434
2009
  case "@catch": break;
2010
+ case "@try group":
2011
+ buildTryNode(node, status, b);
2012
+ break;
1435
2013
  case "@replace group":
1436
2014
  buildReplaceNode(node, status, b);
1437
2015
  break;
@@ -1454,7 +2032,6 @@ function buildControlNode(node, status, b) {
1454
2032
  default: throw new Error(`Invalid operation: ${node.operation}`);
1455
2033
  }
1456
2034
  }
1457
-
1458
2035
  //#endregion
1459
2036
  //#region src/compile/utils/trimEnd.ts
1460
2037
  /**
@@ -1470,30 +2047,29 @@ function trimEnd(text, char) {
1470
2047
  while (text[end - 1] === char) end -= 1;
1471
2048
  return text.substring(0, end);
1472
2049
  }
1473
-
1474
2050
  //#endregion
1475
2051
  //#region src/compile/build/client/buildMount.ts
1476
2052
  function buildMount(_functionName, functionBody, status, b) {
1477
2053
  functionBody = replaceForVarNames(functionBody, status);
1478
- status.imports.add("$mount");
1479
- b.append("// @ts-ignore\n$mount(() => {");
2054
+ status.imports.add("$onmount");
2055
+ b.append("// @ts-ignore\n$onmount(() => {");
1480
2056
  b.append(functionBody);
1481
2057
  b.append("});");
1482
2058
  }
1483
-
1484
2059
  //#endregion
1485
2060
  //#region src/compile/build/client/stashRun.ts
1486
2061
  function stashRun(fragment, functionStart, value, functionEnd, span, status) {
1487
2062
  value = replaceForVarNames(value, status);
1488
2063
  let functionBody = functionStart + value + functionEnd;
2064
+ let forVarMask = status.forVarNames.length > 0 ? forVarsReadIn(value, status.forVarNames) : void 0;
1489
2065
  fragment.effects.push({
1490
2066
  functionBody,
1491
2067
  spans: [span],
1492
2068
  offsets: [functionStart.length],
1493
- lengths: [value.length]
2069
+ lengths: [value.length],
2070
+ forVarMask
1494
2071
  });
1495
2072
  }
1496
-
1497
2073
  //#endregion
1498
2074
  //#region src/compile/build/client/stashRunWithOffsets.ts
1499
2075
  function stashRunWithOffsets(fragment, functionStart, value, functionEnd, spans, offsets, lengths, status) {
@@ -1506,17 +2082,18 @@ function stashRunWithOffsets(fragment, functionStart, value, functionEnd, spans,
1506
2082
  lengths[i] += changedLength;
1507
2083
  for (let j = i + 1; j < offsets.length; j++) offsets[j] += changedLength;
1508
2084
  }
2085
+ let forVarMask = status.forVarNames.length > 0 ? forVarsReadIn(functionBody, status.forVarNames) : void 0;
1509
2086
  fragment.effects.push({
1510
2087
  functionBody,
1511
2088
  spans,
1512
2089
  offsets,
1513
- lengths
2090
+ lengths,
2091
+ forVarMask
1514
2092
  });
1515
2093
  }
1516
-
1517
2094
  //#endregion
1518
2095
  //#region src/compile/build/client/buildElementNode.ts
1519
- function buildElementNode(node, status, b, parentName, root = false) {
2096
+ function buildElementNode(node, status, b, parentName) {
1520
2097
  if (status.inHead) {
1521
2098
  if (node.tagName === "title") buildTitleNode(node, status, b);
1522
2099
  else buildHeadNode(node, status, b);
@@ -1527,8 +2104,10 @@ function buildElementNode(node, status, b, parentName, root = false) {
1527
2104
  if (svgElement) status.ns = true;
1528
2105
  const varName = node.varName;
1529
2106
  if (varName) {
1530
- if (node.tagName === "@element") buildDynamicElementNode(node, status, b);
1531
- if (root) {}
2107
+ if (node.tagName === "@element") {
2108
+ buildDynamicElementNode(node, status, b);
2109
+ return;
2110
+ }
1532
2111
  buildElementAttributes$1(node, varName, status, b);
1533
2112
  }
1534
2113
  const oldPreserveWhitespace = status.preserveWhitespace;
@@ -1539,23 +2118,18 @@ function buildElementNode(node, status, b, parentName, root = false) {
1539
2118
  }
1540
2119
  function buildDynamicElementNode(node, status, b) {
1541
2120
  let selfAttribute = node.attributes.find((a) => a.name === "self");
1542
- if (selfAttribute && selfAttribute.value && selfAttribute.fullyReactive) {
1543
- status.imports.add("$run");
2121
+ if (selfAttribute && selfAttribute.value) {
1544
2122
  status.imports.add("t_dynamic");
1545
2123
  let selfValue = selfAttribute.value;
1546
- b.append("$run(() => {");
1547
- b.append(`${node.varName} = t_dynamic(${node.varName}, ${selfValue});`);
1548
- let parentName = node.varName;
1549
- buildFragment(node, status, b, parentName, "null");
1550
- status.fragmentStack.push({
1551
- fragment: node.fragment,
1552
- path: ""
1553
- });
1554
- for (let child of node.children) buildNode(child, status, b, parentName, "null");
1555
- status.fragmentStack.pop();
1556
- buildAddFragment(node, status, b, parentName, "null");
1557
- b.append(`}${status.options.dev === true ? `, "setDynamic"` : ""});`);
2124
+ if (selfAttribute.fullyReactive) {
2125
+ status.imports.add("$run");
2126
+ b.append("$run(() => {");
2127
+ b.append(`${node.varName} = t_dynamic(${node.varName}, ${selfValue});`);
2128
+ b.append(`}${status.options.dev === true ? `, "setDynamic"` : ""});`);
2129
+ } else b.append(`${node.varName} = t_dynamic(${node.varName}, ${selfValue});`);
1558
2130
  }
2131
+ buildElementAttributes$1(node, node.varName, status, b);
2132
+ for (let child of node.children) buildNode(child, status, b, node.varName, "null");
1559
2133
  }
1560
2134
  function buildTitleNode(node, status, b) {
1561
2135
  let content = node.children[0].content || "";
@@ -1598,11 +2172,6 @@ function buildHeadNode(node, status, b) {
1598
2172
  function buildElementAttributes$1(node, varName, status, b) {
1599
2173
  let fragment = status.fragmentStack.at(-1)?.fragment;
1600
2174
  if (!fragment) throw new Error("No fragment on stack");
1601
- const refAttribute = node.attributes.find((a) => a.name === "&ref");
1602
- if (refAttribute) {
1603
- let { value, fullyReactive } = refAttribute;
1604
- if (value != null && fullyReactive) b.append(`${value} = ${varName};`);
1605
- }
1606
2175
  for (let { name, value, reactive, fullyReactive, span } of node.attributes) if (name === "self" && node.tagName === "@element") {} else if (name === "&ref") {} else if (value != null && fullyReactive) if (name === "&group") buildBindGroupAttribute(node, varName, value, status, b);
1607
2176
  else if (name === "&value" || name === "&checked") buildBindAttribute(node, varName, name, value, status, b);
1608
2177
  else if (name === "onmount") buildMount("elMount", `return (${trimEnd(value.trim(), ";")})(${varName});`, status, b);
@@ -1615,9 +2184,8 @@ function buildElementAttributes$1(node, varName, status, b) {
1615
2184
  const propName = status.ns ? "className.baseVal" : "className";
1616
2185
  stashRun(fragment, `${status.ns ? "// @ts-ignore\n" : ""}${varName}.${propName} = t_class(`, params.join(", "), ");", span, status);
1617
2186
  } else if (name === "style") {
1618
- status.imports.add("t_attribute");
1619
2187
  status.imports.add("t_style");
1620
- stashRun(fragment, `t_attribute(${varName}, "style", t_style(`, value, ") || null);", span, status);
2188
+ stashRun(fragment, `${varName}.style.cssText += t_style(`, value, ");", span, status);
1621
2189
  } else if (name.includes("-")) {
1622
2190
  status.imports.add("t_attribute");
1623
2191
  stashRun(fragment, `t_attribute(${varName}, "${name}", `, value, ");", span, status);
@@ -1636,7 +2204,7 @@ function buildElementAttributes$1(node, varName, status, b) {
1636
2204
  stashRunWithOffsets(fragment, `${status.ns ? "// @ts-ignore\n" : ""}${varName}.${propName} = t_class(`, params.join(", "), ");", spans, offsets, lengths, status);
1637
2205
  } else if (name === "style") {
1638
2206
  status.imports.add("t_style");
1639
- stashRunWithOffsets(fragment, `${varName}.setAttribute("style", t_style(`, value, "));", spans, offsets, lengths, status);
2207
+ stashRunWithOffsets(fragment, `${varName}.style.cssText += t_style(`, value, ");", spans, offsets, lengths, status);
1640
2208
  } else if (name.includes("-")) {
1641
2209
  status.imports.add("t_attribute");
1642
2210
  stashRunWithOffsets(fragment, `t_attribute(${varName}, "${name}", `, value, ");", spans, offsets, lengths, status);
@@ -1679,9 +2247,18 @@ function buildBindAttribute(node, varName, name, value, status, b) {
1679
2247
  inputValue = "e.target.value";
1680
2248
  break;
1681
2249
  }
1682
- } else if (node.tagName === "select") eventName = "change";
2250
+ } else if (node.tagName === "select") {
2251
+ eventName = "change";
2252
+ if (node.attributes.find((a) => a.name === "multiple")) {
2253
+ buildRun("setBinding", `Array.from(${varName}.options).forEach((opt) => opt.selected = Array.isArray(${value}) && ${value}.includes(opt.value));`, status, b);
2254
+ status.imports.add("t_event");
2255
+ b.append(`t_event(${varName}, "change", (e) => ${value} = Array.from(e.target.selectedOptions).map((opt) => opt.value));`);
2256
+ return;
2257
+ }
2258
+ }
1683
2259
  let set = `${value} || ${defaultValue}`;
1684
- buildRun("setBinding", `${`${varName}.${name.substring(1)} = ${set}`};`, status, b);
2260
+ const propName = name.substring(1);
2261
+ buildRun("setBinding", `${propName === "value" ? `${varName}.${propName} = String(${set})` : `${varName}.${propName} = ${set}`};`, status, b);
1685
2262
  status.imports.add("t_event");
1686
2263
  b.append(`t_event(${varName}, "${eventName}", (e) => ${value} = ${inputValue});`);
1687
2264
  }
@@ -1717,7 +2294,6 @@ function buildTransitionAttribute(node, varName, name, value, status, b) {
1717
2294
  b.append(`t_animate(${varName}, null, ${entryVarName});`);
1718
2295
  }
1719
2296
  }
1720
-
1721
2297
  //#endregion
1722
2298
  //#region src/compile/build/client/buildRootNode.ts
1723
2299
  function buildRootNode(node, status, b, parentName, anchorName) {
@@ -1730,13 +2306,12 @@ function buildRootNode(node, status, b, parentName, anchorName) {
1730
2306
  status.fragmentStack.pop();
1731
2307
  buildAddFragment(node, status, b, parentName, anchorName);
1732
2308
  }
1733
-
1734
2309
  //#endregion
1735
2310
  //#region src/compile/build/client/buildSlotNode.ts
1736
2311
  function buildSlotNode(node, status, b) {
1737
2312
  let slotName = node.attributes.find((a) => a.name === "name")?.value;
1738
2313
  slotName = slotName ? trimQuotes(slotName) : "_";
1739
- const propsName = nextVarName("sprops", status);
2314
+ const propsName = nextVarName("slot_props", status);
1740
2315
  const slotAttributes = node.attributes.filter((a) => a.name !== "name");
1741
2316
  const slotHasProps = slotAttributes.length;
1742
2317
  if (slotHasProps) {
@@ -1773,7 +2348,7 @@ function buildSlotNode(node, status, b) {
1773
2348
  "$context"
1774
2349
  ];
1775
2350
  b.append(`$slots["${slotName}"](${params.join(", ")})`);
1776
- const fill = node.children.find((c) => isSpecialNode(c) && c.tagName === "fill");
2351
+ const fill = node.children.find((c) => isSpecialNode(c) && (c.tagName === "fill" || c.tagName === "filldef"));
1777
2352
  if (fill && isSpecialNode(fill) && fill.children.length) {
1778
2353
  b.append(`} else {`);
1779
2354
  buildFragment(fill, status, b, slotParentName, slotAnchorName);
@@ -1787,7 +2362,6 @@ function buildSlotNode(node, status, b) {
1787
2362
  }
1788
2363
  b.append(`}`);
1789
2364
  }
1790
-
1791
2365
  //#endregion
1792
2366
  //#region src/compile/build/client/buildSpecialNode.ts
1793
2367
  function buildSpecialNode(node, status, b, parentName, root = false) {
@@ -1795,9 +2369,10 @@ function buildSpecialNode(node, status, b, parentName, root = false) {
1795
2369
  case "slot":
1796
2370
  buildSlotNode(node, status, b);
1797
2371
  break;
1798
- case "fill": break;
2372
+ case "fill":
2373
+ case "filldef": break;
1799
2374
  case "@element":
1800
- buildElementNode(node, status, b, parentName, root);
2375
+ buildElementNode(node, status, b, parentName);
1801
2376
  break;
1802
2377
  case "@component":
1803
2378
  buildComponentNode(node, status, b, root);
@@ -1806,7 +2381,6 @@ function buildSpecialNode(node, status, b, parentName, root = false) {
1806
2381
  }
1807
2382
  return "";
1808
2383
  }
1809
-
1810
2384
  //#endregion
1811
2385
  //#region src/compile/build/client/buildTextNode.ts
1812
2386
  function buildTextNode(node, status) {
@@ -1823,7 +2397,6 @@ function buildTextNode(node, status) {
1823
2397
  let maxLevel = 0;
1824
2398
  for (let i = 0; i < content.length; i++) {
1825
2399
  const char = content[i];
1826
- const nextChar = content[i + 1];
1827
2400
  if (char === "{") {
1828
2401
  level++;
1829
2402
  maxLevel = Math.max(level, maxLevel);
@@ -1842,25 +2415,10 @@ function buildTextNode(node, status) {
1842
2415
  }
1843
2416
  } else if (char === "`" && level === 0) textContent += "\\";
1844
2417
  else if (level > 0) {
1845
- if (char === "/" && nextChar === "/") {
1846
- let start = i;
1847
- i = content.indexOf("\n", i);
1848
- textContent += content.substring(start, i + 1);
1849
- continue;
1850
- } else if (char === "/" && nextChar === "*") {
1851
- let start = i;
1852
- i = content.indexOf("*/", i) + 1;
1853
- textContent += content.substring(start, i + 1);
1854
- continue;
1855
- } else if (char === "\"" || char === "'") {
1856
- let start = i;
1857
- i = endOfString(char, content, i);
1858
- textContent += content.substring(start, i + 1);
1859
- continue;
1860
- } else if (char === "`") {
1861
- let start = i;
1862
- i = endOfTemplateString(content, i);
1863
- textContent += content.substring(start, i + 1);
2418
+ const skipped = skipStringOrComment(content, i);
2419
+ if (skipped !== -1) {
2420
+ textContent += content.substring(i, skipped);
2421
+ i = skipped - 1;
1864
2422
  continue;
1865
2423
  }
1866
2424
  }
@@ -1880,7 +2438,6 @@ function buildTextNode(node, status) {
1880
2438
  stashRunWithOffsets(fragment, `${node.varName}.textContent = `, textContent, ";", node.spans, offsets, lengths, status);
1881
2439
  }
1882
2440
  }
1883
-
1884
2441
  //#endregion
1885
2442
  //#region src/compile/build/client/buildNode.ts
1886
2443
  function buildNode(node, status, b, parentName, anchorName, root = false) {
@@ -1899,7 +2456,7 @@ function buildNode(node, status, b, parentName, anchorName, root = false) {
1899
2456
  buildComponentNode(node, status, b, root);
1900
2457
  break;
1901
2458
  case "element":
1902
- buildElementNode(node, status, b, parentName, root);
2459
+ buildElementNode(node, status, b, parentName);
1903
2460
  break;
1904
2461
  case "text":
1905
2462
  buildTextNode(node, status);
@@ -1907,28 +2464,40 @@ function buildNode(node, status, b, parentName, anchorName, root = false) {
1907
2464
  case "special":
1908
2465
  buildSpecialNode(node, status, b, parentName, root);
1909
2466
  break;
2467
+ case "comment": break;
1910
2468
  default: throw new Error(`Invalid node type: ${node.type}`);
1911
2469
  }
1912
2470
  }
1913
-
1914
2471
  //#endregion
1915
2472
  //#region src/compile/build/client/buildCode.ts
1916
2473
  const importsMap$1 = {
1917
2474
  $watch: "import { $watch } from \"${folder}\";",
2475
+ $bind: "import { $bind } from \"${folder}\";",
1918
2476
  $cache: "import { $cache } from \"${folder}\";",
2477
+ $async: "import { $async } from \"${folder}\";",
2478
+ $pending: "import { $pending } from \"${folder}\";",
2479
+ $refresh: "import { $refresh } from \"${folder}\";",
1919
2480
  $run: "import { $run } from \"${folder}\";",
1920
- $mount: "import { $mount } from \"${folder}\";",
2481
+ $onmount: "import { $onmount } from \"${folder}\";",
2482
+ $stream: "import { $stream } from \"${folder}\";",
2483
+ fromElement: "import { fromElement } from \"${folder}\";",
2484
+ fromServer: "import { fromServer } from \"${folder}\";",
2485
+ fromWebSocket: "import { fromWebSocket } from \"${folder}\";",
1921
2486
  $unwrap: "import { $unwrap } from \"${folder}\";",
1922
2487
  $peek: "import { $peek } from \"${folder}\";",
1923
2488
  $batch: "import { $batch } from \"${folder}\";",
1924
2489
  t_region: "import { t_region } from \"${folder}\";",
1925
2490
  t_push_region: "import { t_push_region } from \"${folder}\";",
1926
2491
  t_pop_region: "import { t_pop_region } from \"${folder}\";",
2492
+ t_rerun_region_effects: "import { t_rerun_region_effects } from \"${folder}\";",
1927
2493
  t_run_control: "import { t_run_control } from \"${folder}\";",
1928
2494
  t_run_branch: "import { t_run_branch } from \"${folder}\";",
1929
2495
  t_list_item: "import { t_list_item } from \"${folder}\";",
1930
2496
  t_run_list: "import { t_run_list } from \"${folder}\";",
2497
+ t_run_await: "import { t_run_await } from \"${folder}\";",
2498
+ t_run_try: "import { t_run_try } from \"${folder}\";",
1931
2499
  t_add_fragment: "import { t_add_fragment } from \"${folder}\";",
2500
+ t_add_element: "import { t_add_element } from \"${folder}\";",
1932
2501
  t_apply_props: "import { t_apply_props } from \"${folder}\";",
1933
2502
  t_class: "import { t_class } from \"${folder}\";",
1934
2503
  t_style: "import { t_style } from \"${folder}\";",
@@ -1936,19 +2505,25 @@ const importsMap$1 = {
1936
2505
  t_dynamic: "import { t_dynamic } from \"${folder}\";",
1937
2506
  t_fmt: "import { t_fmt } from \"${folder}\";",
1938
2507
  t_fragment: "import { t_fragment } from \"${folder}\";",
2508
+ t_fragment_el: "import { t_fragment_el } from \"${folder}\";",
1939
2509
  t_event: "import { t_event } from \"${folder}\";",
1940
2510
  t_animate: "import { t_animate } from \"${folder}\";",
1941
2511
  t_root: "import { t_root } from \"${folder}\";",
2512
+ t_root_el: "import { t_root_el } from \"${folder}\";",
1942
2513
  t_anchor: "import { t_anchor } from \"${folder}\";",
2514
+ t_first_inside: "import { t_first_inside } from \"${folder}\";",
1943
2515
  t_child: "import { t_child } from \"${folder}\";",
1944
2516
  t_next: "import { t_next } from \"${folder}\";",
1945
2517
  t_skip: "import { t_skip } from \"${folder}\";",
1946
2518
  t_frg: "import { t_frg } from \"${folder}\";",
2519
+ t_save_hydration: "import { t_save_hydration } from \"${folder}\";",
2520
+ t_restore_hydration: "import { t_restore_hydration } from \"${folder}\";",
1947
2521
  t_elm: "import { t_elm } from \"${folder}\";",
1948
2522
  t_txt: "import { t_txt } from \"${folder}\";",
1949
2523
  t_cmt: "import { t_cmt } from \"${folder}\";",
1950
2524
  t_print: "import { t_print } from \"${folder}\";",
1951
2525
  ListItem: "import { type ListItem } from \"${folder}\";",
2526
+ ListItemSpec: "import { type ListItemSpec } from \"${folder}\";",
1952
2527
  SlotRender: "import { type SlotRender } from \"${folder}\";",
1953
2528
  devContext: "import { devContext } from \"${folder}/dev\";",
1954
2529
  t_push_dev_bound: "import { t_push_dev_bound } from \"${folder}/dev\";",
@@ -1961,13 +2536,10 @@ function buildCode(template, map, options) {
1961
2536
  buildTemplate(template, map, imports, b, options);
1962
2537
  let startSize = b.toString().length;
1963
2538
  if (imports.size) {
2539
+ const folder = options?.renderFolder ?? "@torpor/view";
2540
+ const sortedImports = Array.from(imports).map((imp) => (importsMap$1[imp] ?? imp).replace("${folder}", folder)).sort().reverse();
1964
2541
  b.prepend("");
1965
- for (let imp of Array.from(imports).sort().reverse()) {
1966
- let importStatement = importsMap$1[imp] ?? imp;
1967
- let folder = options?.renderFolder ?? "@torpor/view";
1968
- importStatement = importStatement.replace("${folder}", folder);
1969
- b.prepend(importStatement);
1970
- }
2542
+ for (let imp of sortedImports) b.prepend(imp);
1971
2543
  }
1972
2544
  if (options?.mapped) {
1973
2545
  let diff = b.toString().length - startSize + 1;
@@ -1980,13 +2552,27 @@ function buildCode(template, map, options) {
1980
2552
  }
1981
2553
  function buildTemplate(template, map, imports, b, options) {
1982
2554
  let script = template.script.map((s) => s.script).join("\n");
1983
- if (/\$watch\b/.test(script)) imports.add("$watch");
1984
- if (/\$cache\b/.test(script)) imports.add("$cache");
1985
- if (/\$run\b/.test(script)) imports.add("$run");
1986
- if (/\$mount\b/.test(script)) imports.add("$mount");
1987
- if (/\$unwrap\b/.test(script)) imports.add("$unwrap");
1988
- if (/\$peek\b/.test(script)) imports.add("$peek");
1989
- if (/\$batch\b/.test(script)) imports.add("$batch");
2555
+ for (const component of template.components) {
2556
+ if (component.markup) script += "\n" + collectMarkupExpressions(component.markup);
2557
+ if (component.error) script += "\n" + collectMarkupExpressions(component.error);
2558
+ if (component.head) script += "\n" + collectMarkupExpressions(component.head);
2559
+ }
2560
+ let scriptCode = codeRanges(script).map(([start, end]) => script.substring(start, end)).join("");
2561
+ if (/\$watch\b/.test(scriptCode)) imports.add("$watch");
2562
+ if (/\$bind\b/.test(scriptCode)) imports.add("$bind");
2563
+ if (/\$cache\b/.test(scriptCode)) imports.add("$cache");
2564
+ if (/\$async\b/.test(scriptCode)) imports.add("$async");
2565
+ if (/\$pending\b/.test(scriptCode)) imports.add("$pending");
2566
+ if (/\$refresh\b/.test(scriptCode)) imports.add("$refresh");
2567
+ if (/\$run\b/.test(scriptCode)) imports.add("$run");
2568
+ if (/\$onmount\b/.test(scriptCode)) imports.add("$onmount");
2569
+ if (/\$stream\b/.test(scriptCode)) imports.add("$stream");
2570
+ if (/\bfromElement\b/.test(scriptCode)) imports.add("fromElement");
2571
+ if (/\bfromServer\b/.test(scriptCode)) imports.add("fromServer");
2572
+ if (/\bfromWebSocket\b/.test(scriptCode)) imports.add("fromWebSocket");
2573
+ if (/\$unwrap\b/.test(scriptCode)) imports.add("$unwrap");
2574
+ if (/\$peek\b/.test(scriptCode)) imports.add("$peek");
2575
+ if (/\$batch\b/.test(scriptCode)) imports.add("$batch");
1990
2576
  let currentIndex = 0;
1991
2577
  let current = template.components[0];
1992
2578
  let status = makeStatus(imports, map, current, options);
@@ -1998,10 +2584,10 @@ function buildTemplate(template, map, imports, b, options) {
1998
2584
  ${current.markup ? "$parent" : "_$parent"}: ParentNode,
1999
2585
  ${current.markup ? "$anchor" : "_$anchor"}: Node | null,`);
2000
2586
  if (current.params !== void 0) addMappedText("", current.params, ",", chunk.span, status, b);
2001
- else b.append(`${current.props?.length ? "$props: Record<PropertyKey, any>" : "// @ts-ignore\n$props?: Record<PropertyKey, any>"},`);
2587
+ else b.append(`${current.props?.length ? "$props: Record<PropertyKey, any>" : "_$props?: Record<PropertyKey, any>"},`);
2002
2588
  b.append(`
2003
- ${current.contextProps?.length ? "$context" : "// @ts-ignore\n$context"}?: Record<PropertyKey, any>,
2004
- ${current.slotProps?.length ? "$slots" : "// @ts-ignore\n$slots"}?: Record<string, SlotRender>`);
2589
+ ${current.contextProps?.length || current.markup && markupRendersComponent(current.markup) || current.error && markupRendersComponent(current.error) ? "$context" : "_$context"}?: Record<PropertyKey, any>,
2590
+ ${current.slotProps?.length ? "$slots" : "_$slots"}?: Record<string, SlotRender>,`);
2005
2591
  } else if (chunk.script === ") /* @return_type */ {") b.append("): void {");
2006
2592
  else if (chunk.script === "/* @start */") {
2007
2593
  addPushDevBoundary("component", current.name || "Anon Component", status, b);
@@ -2010,10 +2596,26 @@ function buildTemplate(template, map, imports, b, options) {
2010
2596
  if (current.markup) {
2011
2597
  b.append("");
2012
2598
  b.append("/* User interface */");
2599
+ if (current.error) {
2600
+ status.imports.add("t_region");
2601
+ status.imports.add("t_run_try");
2602
+ b.append(`const t_error_region = t_region();`);
2603
+ b.append(`t_run_try(t_error_region, $anchor, () => {`);
2604
+ }
2013
2605
  buildFragmentText(current.markup, status, b);
2014
2606
  b.append("");
2015
2607
  buildNode(current.markup, status, b, "$parent", "$anchor", true);
2016
2608
  }
2609
+ } else if (chunk.script === "/* @error */") {
2610
+ if (current.markup && current.error) {
2611
+ b.append(`}, (_, ${current.errorVar ?? "err"}) => {`);
2612
+ b.append("");
2613
+ b.append("/* User interface error */");
2614
+ buildFragmentText(current.error, status, b);
2615
+ b.append("");
2616
+ buildNode(current.error, status, b, "$parent", "$anchor", true);
2617
+ b.append(`}${status.options.dev === true ? ", \"runError\"" : ""});`);
2618
+ }
2017
2619
  } else if (chunk.script === "/* @head */") {
2018
2620
  if (current.head) {
2019
2621
  b.append("");
@@ -2045,15 +2647,26 @@ function makeStatus(imports, map, current, options) {
2045
2647
  options: options ?? {}
2046
2648
  };
2047
2649
  }
2048
-
2049
2650
  //#endregion
2050
2651
  //#region src/compile/build/client/buildStyles.ts
2051
2652
  function buildStyles(style, styleHash) {
2052
2653
  const b = new Builder();
2053
- for (let block of style.blocks) buildStyleBlock(block, b, styleHash);
2654
+ for (let block of style.children) buildStyleNode(block, b, styleHash);
2054
2655
  return b.toString();
2055
2656
  }
2056
- function buildStyleBlock(block, b, styleHash) {
2657
+ function buildStyleNode(node, b, styleHash) {
2658
+ switch (node.type) {
2659
+ case "block":
2660
+ buildBlockNode(node, b, styleHash);
2661
+ break;
2662
+ case "attribute":
2663
+ buildAttributeNode(node, b);
2664
+ break;
2665
+ case "comment": break;
2666
+ default: throw new Error(`Invalid style node type: ${node.type}`);
2667
+ }
2668
+ }
2669
+ function buildBlockNode(block, b, styleHash) {
2057
2670
  if (block.selector.startsWith("@")) b.append(`${block.selector} {`);
2058
2671
  else {
2059
2672
  block.selector = block.selector.replaceAll(/\s+/g, " ");
@@ -2079,8 +2692,7 @@ function buildStyleBlock(block, b, styleHash) {
2079
2692
  }
2080
2693
  b.append(`${selectors.join(" ").replaceAll(" ,", ",")} {`);
2081
2694
  }
2082
- for (let attribute of block.attributes) buildStyleAttribute(attribute, b);
2083
- for (let child of block.children) buildStyleBlock(child, b, styleHash);
2695
+ for (let child of block.children) buildStyleNode(child, b, styleHash);
2084
2696
  b.append(`}`);
2085
2697
  }
2086
2698
  function addSelector(selectors, selector, styleHash, start, end, global) {
@@ -2099,10 +2711,9 @@ function addSelector(selectors, selector, styleHash, start, end, global) {
2099
2711
  selectors.push(s);
2100
2712
  }
2101
2713
  }
2102
- function buildStyleAttribute(attribute, b) {
2714
+ function buildAttributeNode(attribute, b) {
2103
2715
  b.append(`${attribute.name}: ${attribute.value};`);
2104
2716
  }
2105
-
2106
2717
  //#endregion
2107
2718
  //#region src/compile/build/server/buildServerComponentNode.ts
2108
2719
  function buildServerComponentNode(node, status, b) {
@@ -2118,20 +2729,16 @@ function buildServerComponentNode(node, status, b) {
2118
2729
  for (let { name, value } of node.attributes) {
2119
2730
  if (name.startsWith("&")) name = name.substring(1);
2120
2731
  if (name === "class" && value != null) {
2121
- status.imports.add("t_class");
2122
- const params = [value];
2123
- if (node.scopeStyles) params.push(`"torp-${status.styleHash}"`);
2124
- props.push({
2125
- name,
2126
- value: `t_class(${params.join(", ")})`
2127
- });
2128
- } else if (name === "style" && value != null) {
2129
- status.imports.add("t_style");
2732
+ if (node.scopeStyles) value = `[${value}, "torp-${status.styleHash}"]`;
2130
2733
  props.push({
2131
2734
  name,
2132
- value: `t_style(${value})`
2735
+ value
2133
2736
  });
2134
- } else if (value != null) props.push({
2737
+ } else if (name === "style" && value != null) props.push({
2738
+ name,
2739
+ value
2740
+ });
2741
+ else if (value != null) props.push({
2135
2742
  name,
2136
2743
  value
2137
2744
  });
@@ -2141,17 +2748,17 @@ function buildServerComponentNode(node, status, b) {
2141
2748
  });
2142
2749
  }
2143
2750
  b.append(`const ${propsName} = {`);
2144
- for (let p of props) b.append(`${p.name}: ${p.value},`);
2751
+ for (let p of props) b.append(p.name ? `${p.name}: ${p.value},` : `${p.value},`);
2145
2752
  b.append("};");
2146
2753
  }
2147
2754
  const componentHasSlots = node.children.length;
2148
2755
  const slotsName = componentHasSlots ? nextVarName("slots", status) : "undefined";
2149
2756
  if (componentHasSlots) {
2150
2757
  b.append(`const ${slotsName}: Record<string, ServerSlotRender> = {};`);
2151
- for (let slot of node.children) if (isSpecialNode(slot) && slot.tagName === "fill") {
2758
+ for (let slot of node.children) if (isSpecialNode(slot) && (slot.tagName === "fill" || slot.tagName === "filldef")) {
2152
2759
  const nameAttribute = slot.attributes.find((a) => a.name === "name");
2153
2760
  const slotName = nameAttribute?.value ? trimQuotes(nameAttribute.value) : "_";
2154
- const slotParams = [slot.hasSlotProps ? "$sprops: Record<PropertyKey, any>" : "// @ts-ignore\n$sprops?: Record<PropertyKey, any>", "// @ts-ignore\n$context?: Record<PropertyKey, any>"];
2761
+ const slotParams = [slot.hasSlotProps ? "$slot: Record<PropertyKey, any>" : "_$slot?: Record<PropertyKey, any>", "// @ts-ignore\n// eslint-disable-next-line no-unused-vars\n$context?: Record<PropertyKey, any>"];
2155
2762
  b.append(`${slot.hasSlotProps ? "// @ts-ignore\n" : ""}${slotsName}["${slotName}"] = (\n${slotParams.join(",\n")}\n) => {`);
2156
2763
  b.append(`let t_body = "";`);
2157
2764
  for (let child of slot.children) buildServerNode(child, status, b);
@@ -2175,9 +2782,8 @@ function buildServerComponentNode(node, status, b) {
2175
2782
  b.append(`t_body += ${componentResult}.body;`);
2176
2783
  b.append(`t_head += ${componentResult}.head;`);
2177
2784
  status.output += HYDRATION_END_COMMENT;
2178
- status.output += ANCHOR_COMMENT;
2785
+ status.output += "<!>";
2179
2786
  }
2180
-
2181
2787
  //#endregion
2182
2788
  //#region src/compile/build/server/buildServerAwaitNode.ts
2183
2789
  function buildServerAwaitNode(node, status, b) {
@@ -2186,9 +2792,9 @@ function buildServerAwaitNode(node, status, b) {
2186
2792
  b.append(`t_body += \`${status.output}\`;`);
2187
2793
  status.output = "";
2188
2794
  }
2189
- for (let branch of node.children) if (isControlNode(branch) && branch.operation === "@await") buildServerAwaitBranch(branch, status, b);
2795
+ for (let branch of node.children) if (isControlNode(branch) && branch.operation === "@with") buildServerAwaitBranch(branch, status, b);
2190
2796
  status.output += HYDRATION_END_COMMENT;
2191
- status.output += ANCHOR_COMMENT;
2797
+ status.output += "<!>";
2192
2798
  }
2193
2799
  function buildServerAwaitBranch(node, status, b) {
2194
2800
  for (let child of node.children) buildServerNode(child, status, b);
@@ -2197,7 +2803,6 @@ function buildServerAwaitBranch(node, status, b) {
2197
2803
  status.output = "";
2198
2804
  }
2199
2805
  }
2200
-
2201
2806
  //#endregion
2202
2807
  //#region src/compile/build/server/buildServerForNode.ts
2203
2808
  function buildServerForNode(node, status, b) {
@@ -2208,7 +2813,7 @@ function buildServerForNode(node, status, b) {
2208
2813
  }
2209
2814
  for (let branch of node.children) if (isControlNode(branch)) buildServerForBranch(branch, status, b);
2210
2815
  status.output += HYDRATION_END_COMMENT;
2211
- status.output += ANCHOR_COMMENT;
2816
+ status.output += "<!>";
2212
2817
  }
2213
2818
  function buildServerForBranch(node, status, b) {
2214
2819
  b.append(`${node.statement} {`);
@@ -2220,16 +2825,14 @@ function buildServerForBranch(node, status, b) {
2220
2825
  }
2221
2826
  b.append("}");
2222
2827
  }
2223
-
2224
2828
  //#endregion
2225
2829
  //#region src/compile/build/server/buildServerHtmlNode.ts
2226
2830
  function buildServerHtmlNode(node, status) {
2227
2831
  status.output += HYDRATION_START_COMMENT;
2228
2832
  for (let branch of node.children) if (isControlNode(branch)) status.output += `\${${branch.statement}}`;
2229
2833
  status.output += HYDRATION_END_COMMENT;
2230
- status.output += ANCHOR_COMMENT;
2834
+ status.output += "<!>";
2231
2835
  }
2232
-
2233
2836
  //#endregion
2234
2837
  //#region src/compile/build/server/buildServerIfNode.ts
2235
2838
  function buildServerIfNode(node, status, b) {
@@ -2240,7 +2843,7 @@ function buildServerIfNode(node, status, b) {
2240
2843
  }
2241
2844
  for (let branch of node.children) if (isControlNode(branch)) buildServerIfBranch(branch, status, b);
2242
2845
  status.output += HYDRATION_END_COMMENT;
2243
- status.output += ANCHOR_COMMENT;
2846
+ status.output += "<!>";
2244
2847
  }
2245
2848
  function buildServerIfBranch(node, status, b) {
2246
2849
  b.append(`${node.statement} {`);
@@ -2252,7 +2855,6 @@ function buildServerIfBranch(node, status, b) {
2252
2855
  }
2253
2856
  b.append("}");
2254
2857
  }
2255
-
2256
2858
  //#endregion
2257
2859
  //#region src/compile/build/server/buildServerReplaceNode.ts
2258
2860
  function buildServerReplaceNode(node, status, b) {
@@ -2263,7 +2865,7 @@ function buildServerReplaceNode(node, status, b) {
2263
2865
  }
2264
2866
  for (let branch of node.children) if (isControlNode(branch)) buildServerReplaceBranch(branch, status, b);
2265
2867
  status.output += HYDRATION_END_COMMENT;
2266
- status.output += ANCHOR_COMMENT;
2868
+ status.output += "<!>";
2267
2869
  }
2268
2870
  function buildServerReplaceBranch(node, status, b) {
2269
2871
  b.append(`${node.statement};`);
@@ -2273,7 +2875,6 @@ function buildServerReplaceBranch(node, status, b) {
2273
2875
  status.output = "";
2274
2876
  }
2275
2877
  }
2276
-
2277
2878
  //#endregion
2278
2879
  //#region src/compile/build/server/buildServerScriptNode.ts
2279
2880
  function buildServerScriptNode(node, b) {
@@ -2283,7 +2884,6 @@ function maybeAppend(text, end) {
2283
2884
  if (!text.endsWith(end)) text += end;
2284
2885
  return text;
2285
2886
  }
2286
-
2287
2887
  //#endregion
2288
2888
  //#region src/compile/build/server/buildServerSwitchNode.ts
2289
2889
  function buildServerSwitchNode(node, status, b) {
@@ -2296,7 +2896,7 @@ function buildServerSwitchNode(node, status, b) {
2296
2896
  for (let branch of node.children.filter((c) => isControlNode(c))) if (isControlNode(branch)) buildServerSwitchBranch(branch, status, b);
2297
2897
  b.append("}");
2298
2898
  status.output += HYDRATION_END_COMMENT;
2299
- status.output += ANCHOR_COMMENT;
2899
+ status.output += "<!>";
2300
2900
  }
2301
2901
  function buildServerSwitchBranch(node, status, b) {
2302
2902
  b.append(`${node.statement} {`);
@@ -2309,7 +2909,39 @@ function buildServerSwitchBranch(node, status, b) {
2309
2909
  b.append("break;");
2310
2910
  b.append("}");
2311
2911
  }
2312
-
2912
+ //#endregion
2913
+ //#region src/compile/build/server/buildServerTryNode.ts
2914
+ function buildServerTryNode(node, status, b) {
2915
+ status.output += HYDRATION_START_COMMENT;
2916
+ if (status.output) {
2917
+ b.append(`t_body += \`${status.output}\`;`);
2918
+ status.output = "";
2919
+ }
2920
+ const branches = node.children.filter((n) => isControlNode(n));
2921
+ let tryBranch = branches.find((n) => n.operation === "@try");
2922
+ let catchBranch = branches.find((n) => n.operation === "@catch");
2923
+ if (catchBranch) {
2924
+ b.append(`const t_try_body = t_body;`);
2925
+ b.append(`const t_try_head = t_head;`);
2926
+ b.append("try {");
2927
+ if (tryBranch) buildServerTryBranch(tryBranch, status, b);
2928
+ const catchVar = trimMatched(catchBranch.statement.substring(5).trim(), "(", ")");
2929
+ b.append(`} catch (${catchVar || "err"}) {`);
2930
+ b.append("t_body = t_try_body;");
2931
+ b.append("t_head = t_try_head;");
2932
+ buildServerTryBranch(catchBranch, status, b);
2933
+ b.append("}");
2934
+ } else if (tryBranch) buildServerTryBranch(tryBranch, status, b);
2935
+ status.output += HYDRATION_END_COMMENT;
2936
+ status.output += "<!>";
2937
+ }
2938
+ function buildServerTryBranch(node, status, b) {
2939
+ for (let child of node.children) buildServerNode(child, status, b);
2940
+ if (status.output) {
2941
+ b.append(`t_body += \`${status.output}\`;`);
2942
+ status.output = "";
2943
+ }
2944
+ }
2313
2945
  //#endregion
2314
2946
  //#region src/compile/build/server/buildServerControlNode.ts
2315
2947
  function buildServerControlNode(node, status, b) {
@@ -2334,8 +2966,12 @@ function buildServerControlNode(node, status, b) {
2334
2966
  buildServerAwaitNode(node, status, b);
2335
2967
  break;
2336
2968
  case "@await":
2337
- case "@then":
2969
+ case "@with":
2970
+ case "@try":
2338
2971
  case "@catch": break;
2972
+ case "@try group":
2973
+ buildServerTryNode(node, status, b);
2974
+ break;
2339
2975
  case "@replace group":
2340
2976
  buildServerReplaceNode(node, status, b);
2341
2977
  break;
@@ -2366,27 +3002,6 @@ function buildServerControlNode(node, status, b) {
2366
3002
  default: throw new Error(`Invalid operation: ${node.operation}`);
2367
3003
  }
2368
3004
  }
2369
-
2370
- //#endregion
2371
- //#region src/compile/utils/voidTags.ts
2372
- const voidTags = [
2373
- "area",
2374
- "base",
2375
- "br",
2376
- "col",
2377
- "embed",
2378
- "hr",
2379
- "img",
2380
- "input",
2381
- "link",
2382
- "meta",
2383
- "param",
2384
- "source",
2385
- "track",
2386
- "wbr"
2387
- ];
2388
- var voidTags_default = voidTags;
2389
-
2390
3005
  //#endregion
2391
3006
  //#region src/compile/build/server/buildServerElementNode.ts
2392
3007
  function buildServerElementNode(node, status, b) {
@@ -2398,7 +3013,7 @@ function buildServerElementNode(node, status, b) {
2398
3013
  if (selfAttribute) tagName = `$\{${selfAttribute.value}}`;
2399
3014
  }
2400
3015
  status.output += `<${tagName}${attributes}>`;
2401
- if (!voidTags_default.includes(tagName)) {
3016
+ if (!voidTags.includes(tagName)) {
2402
3017
  const oldPreserveWhitespace = status.preserveWhitespace;
2403
3018
  status.preserveWhitespace = ["pre", "code"].includes(tagName);
2404
3019
  for (let child of node.children) buildServerNode(child, status, b);
@@ -2410,6 +3025,7 @@ function buildElementAttributes(node, status) {
2410
3025
  let needsClass = node.scopeStyles;
2411
3026
  let attributes = [];
2412
3027
  for (let { name, value, reactive, fullyReactive } of node.attributes) if (name === "self" && node.tagName === "@element") {} else if (name === "&ref") {} else if (name.startsWith("on") || name.startsWith(":on")) {} else if (name.startsWith("transition") && value) {} else if (value != null && fullyReactive) if (name === "&value" || name === "&checked" || name === "&group") {
3028
+ if (name === "&value" && node.tagName === "select" && node.attributes.some((a) => a.name === "multiple")) continue;
2413
3029
  let defaultValue = "";
2414
3030
  let typeAttribute = node.attributes.find((a) => a.name === "type");
2415
3031
  if (typeAttribute && typeAttribute.value) switch (trimQuotes(typeAttribute.value)) {
@@ -2456,13 +3072,11 @@ function buildElementAttributes(node, status) {
2456
3072
  if (needsClass) attributes.push(`class="torp-${status.styleHash}"`);
2457
3073
  return attributes.join(" ");
2458
3074
  }
2459
-
2460
3075
  //#endregion
2461
3076
  //#region src/compile/build/server/buildServerRootNode.ts
2462
3077
  function buildServerRootNode(node, status, b) {
2463
3078
  for (let child of node.children) buildServerNode(child, status, b);
2464
3079
  }
2465
-
2466
3080
  //#endregion
2467
3081
  //#region src/compile/build/server/buildServerSlotNode.ts
2468
3082
  function buildServerSlotNode(node, status, b) {
@@ -2473,7 +3087,7 @@ function buildServerSlotNode(node, status, b) {
2473
3087
  }
2474
3088
  let slotName = node.attributes.find((a) => a.name === "name")?.value;
2475
3089
  slotName = slotName ? trimQuotes(slotName) : "_";
2476
- const propsName = nextVarName("sprops", status);
3090
+ const propsName = nextVarName("slot_props", status);
2477
3091
  const slotAttributes = node.attributes.filter((a) => a.name !== "name");
2478
3092
  const slotHasProps = slotAttributes.length;
2479
3093
  if (slotHasProps) {
@@ -2483,7 +3097,7 @@ function buildServerSlotNode(node, status, b) {
2483
3097
  }
2484
3098
  b.append(`if ($slots && $slots["${slotName}"]) {`);
2485
3099
  b.append(`t_body += $slots["${slotName}"](${slotHasProps ? propsName : "undefined"}, $context);`);
2486
- const fill = node.children.find((c) => isSpecialNode(c) && c.tagName === "fill");
3100
+ const fill = node.children.find((c) => isSpecialNode(c) && (c.tagName === "fill" || c.tagName === "filldef"));
2487
3101
  if (fill && isSpecialNode(fill) && fill.children.length) {
2488
3102
  b.append(`} else {`);
2489
3103
  for (let child of fill.children) buildServerNode(child, status, b);
@@ -2494,9 +3108,8 @@ function buildServerSlotNode(node, status, b) {
2494
3108
  }
2495
3109
  b.append(`}`);
2496
3110
  status.output += HYDRATION_END_COMMENT;
2497
- status.output += ANCHOR_COMMENT;
3111
+ status.output += "<!>";
2498
3112
  }
2499
-
2500
3113
  //#endregion
2501
3114
  //#region src/compile/build/server/buildServerSpecialNode.ts
2502
3115
  function buildServerSpecialNode(node, status, b) {
@@ -2504,7 +3117,8 @@ function buildServerSpecialNode(node, status, b) {
2504
3117
  case "slot":
2505
3118
  buildServerSlotNode(node, status, b);
2506
3119
  break;
2507
- case "fill": break;
3120
+ case "fill":
3121
+ case "filldef": break;
2508
3122
  case "@element":
2509
3123
  buildServerElementNode(node, status, b);
2510
3124
  break;
@@ -2514,7 +3128,6 @@ function buildServerSpecialNode(node, status, b) {
2514
3128
  default: throw new Error(`Invalid special node: ${node.tagName}`);
2515
3129
  }
2516
3130
  }
2517
-
2518
3131
  //#endregion
2519
3132
  //#region src/compile/build/server/buildServerTextNode.ts
2520
3133
  function buildServerTextNode(node, status) {
@@ -2523,7 +3136,6 @@ function buildServerTextNode(node, status) {
2523
3136
  let level = 0;
2524
3137
  for (let i = 0; i < content.length; i++) {
2525
3138
  const char = content[i];
2526
- const nextChar = content[i + 1];
2527
3139
  if (char === "{") {
2528
3140
  level++;
2529
3141
  if (level === 1) {
@@ -2536,35 +3148,16 @@ function buildServerTextNode(node, status) {
2536
3148
  if (level === 0) status.output += ")";
2537
3149
  } else if (char === "`" && level === 0) status.output += "\\";
2538
3150
  else if (level > 0) {
2539
- if (char === "/" && nextChar === "/") {
2540
- let start = i;
2541
- i = content.indexOf("\n", i);
2542
- status.output += escapeHtml(content.substring(start, i + 1));
2543
- continue;
2544
- } else if (char === "/" && nextChar === "*") {
2545
- let start = i;
2546
- i = content.indexOf("*/", i) + 1;
2547
- status.output += escapeHtml(content.substring(start, i + 1));
2548
- continue;
2549
- } else if (char === "\"" || char === "'") {
2550
- let start = i;
2551
- i = endOfString(char, content, i);
2552
- status.output += escapeHtml(content.substring(start, i + 1));
2553
- continue;
2554
- } else if (char === "`") {
2555
- let start = i;
2556
- i = endOfTemplateString(content, i);
2557
- status.output += escapeHtml(content.substring(start, i + 1));
3151
+ const skipped = skipStringOrComment(content, i);
3152
+ if (skipped !== -1) {
3153
+ status.output += content.substring(i, skipped);
3154
+ i = skipped - 1;
2558
3155
  continue;
2559
3156
  }
2560
3157
  }
2561
3158
  status.output += char;
2562
3159
  }
2563
3160
  }
2564
- function escapeHtml(text) {
2565
- return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
2566
- }
2567
-
2568
3161
  //#endregion
2569
3162
  //#region src/compile/build/server/buildServerNode.ts
2570
3163
  function buildServerNode(node, status, b) {
@@ -2587,17 +3180,25 @@ function buildServerNode(node, status, b) {
2587
3180
  case "special":
2588
3181
  buildServerSpecialNode(node, status, b);
2589
3182
  break;
3183
+ case "comment": break;
2590
3184
  default: throw new Error(`Invalid node type: ${node.type}`);
2591
3185
  }
2592
3186
  }
2593
-
2594
3187
  //#endregion
2595
3188
  //#region src/compile/build/server/buildServerCode.ts
2596
3189
  const importsMap = {
2597
3190
  $watch: "import { $watch } from \"${folder}\";",
3191
+ $bind: "import { $bind } from \"${folder}\";",
2598
3192
  $cache: "import { $cache } from \"${folder}\";",
3193
+ $async: "import { $async } from \"${folder}\";",
3194
+ $pending: "import { $pending } from \"${folder}\";",
3195
+ $refresh: "import { $refresh } from \"${folder}\";",
2599
3196
  $run: "import { $run } from \"${folder}\";",
2600
- $mount: "import { $mount } from \"${folder}\";",
3197
+ $onmount: "import { $onmount } from \"${folder}\";",
3198
+ $stream: "import { $stream } from \"${folder}\";",
3199
+ fromElement: "import { fromElement } from \"${folder}\";",
3200
+ fromServer: "import { fromServer } from \"${folder}\";",
3201
+ fromWebSocket: "import { fromWebSocket } from \"${folder}\";",
2601
3202
  $unwrap: "import { $unwrap } from \"${folder}\";",
2602
3203
  $peek: "import { $peek } from \"${folder}\";",
2603
3204
  $batch: "import { $batch } from \"${folder}\";",
@@ -2613,33 +3214,46 @@ function buildServerCode(template, options) {
2613
3214
  imports.add("ServerSlotRender");
2614
3215
  buildServerTemplate(template, imports, b, options);
2615
3216
  if (imports.size) {
3217
+ const folder = options?.renderFolder ?? "@torpor/view/ssr";
3218
+ const sortedImports = Array.from(imports).map((imp) => (importsMap[imp] ?? imp).replace("${folder}", folder)).sort().reverse();
2616
3219
  b.prepend("");
2617
- for (let imp of Array.from(imports).sort().reverse()) {
2618
- imp = importsMap[imp] || imp;
2619
- b.prepend(imp.replace("${folder}", options?.renderFolder || "@torpor/view/ssr"));
2620
- }
3220
+ for (let imp of sortedImports) b.prepend(imp);
2621
3221
  }
2622
3222
  return b.toString();
2623
3223
  }
2624
3224
  function buildServerTemplate(template, imports, b, options) {
2625
3225
  let script = template.script.map((s) => s.script).join("\n");
2626
- if (/\$watch\b/.test(script)) imports.add("$watch");
2627
- if (/\$cache\b/.test(script)) imports.add("$cache");
2628
- if (/\$run\b/.test(script)) imports.add("$run");
2629
- if (/\$mount\b/.test(script)) imports.add("$mount");
2630
- if (/\$unwrap\b/.test(script)) imports.add("$unwrap");
2631
- if (/\$peek\b/.test(script)) imports.add("$peek");
2632
- if (/\$batch\b/.test(script)) imports.add("$batch");
3226
+ for (const component of template.components) {
3227
+ if (component.markup) script += "\n" + collectMarkupExpressions(component.markup);
3228
+ if (component.error) script += "\n" + collectMarkupExpressions(component.error);
3229
+ if (component.head) script += "\n" + collectMarkupExpressions(component.head);
3230
+ }
3231
+ let scriptCode = codeRanges(script).map(([start, end]) => script.substring(start, end)).join("");
3232
+ if (/\$watch\b/.test(scriptCode)) imports.add("$watch");
3233
+ if (/\$bind\b/.test(scriptCode)) imports.add("$bind");
3234
+ if (/\$cache\b/.test(scriptCode)) imports.add("$cache");
3235
+ if (/\$async\b/.test(scriptCode)) imports.add("$async");
3236
+ if (/\$pending\b/.test(scriptCode)) imports.add("$pending");
3237
+ if (/\$refresh\b/.test(scriptCode)) imports.add("$refresh");
3238
+ if (/\$run\b/.test(scriptCode)) imports.add("$run");
3239
+ if (/\$onmount\b/.test(scriptCode)) imports.add("$onmount");
3240
+ if (/\$stream\b/.test(scriptCode)) imports.add("$stream");
3241
+ if (/\bfromElement\b/.test(scriptCode)) imports.add("fromElement");
3242
+ if (/\bfromServer\b/.test(scriptCode)) imports.add("fromServer");
3243
+ if (/\bfromWebSocket\b/.test(scriptCode)) imports.add("fromWebSocket");
3244
+ if (/\$unwrap\b/.test(scriptCode)) imports.add("$unwrap");
3245
+ if (/\$peek\b/.test(scriptCode)) imports.add("$peek");
3246
+ if (/\$batch\b/.test(scriptCode)) imports.add("$batch");
2633
3247
  let currentIndex = 0;
2634
3248
  let current = template.components[0];
2635
3249
  for (let chunk of template.script) if (chunk.script === "/* @params */") {
2636
3250
  let params = [
2637
- current.params ?? `${current.props?.length ? "$props: Record<PropertyKey, any>" : "// @ts-ignore\n$props?: Record<PropertyKey, any>"}`,
2638
- `${current.contextProps?.length ? "$context" : "// @ts-ignore\n$context"}?: Record<PropertyKey, any>`,
2639
- `${current.slotProps?.length ? "$slots" : "// @ts-ignore\n$slots"}?: Record<string, ServerSlotRender>`
3251
+ current.params ?? `${current.props?.length ? "$props: Record<PropertyKey, any>" : "_$props?: Record<PropertyKey, any>"}`,
3252
+ `${current.contextProps?.length || current.markup && markupRendersComponent(current.markup) || current.error && markupRendersComponent(current.error) ? "$context" : "_$context"}?: Record<PropertyKey, any>`,
3253
+ `${current.slotProps?.length ? "$slots" : "_$slots"}?: Record<string, ServerSlotRender>`
2640
3254
  ];
2641
- b.append(params.join(",\n"));
2642
- } else if (chunk.script === ") /* @return_type */ {") b.append("): { body: string, head: string } {");
3255
+ b.append(params.join(",\n") + ",");
3256
+ } else if (chunk.script === ") /* @return_type */ {") b.append("): { body: string; head: string } {");
2643
3257
  else if (chunk.script === "/* @start */") {
2644
3258
  if (current.contextProps?.length) b.append(`$context = Object.assign({}, $context);`);
2645
3259
  b.append(`let t_body = "";`);
@@ -2656,12 +3270,38 @@ function buildServerTemplate(template, imports, b, options) {
2656
3270
  };
2657
3271
  b.append("");
2658
3272
  b.append("/* User interface */");
3273
+ if (current.error) {
3274
+ b.append(`const t_try_body = t_body;`);
3275
+ b.append(`const t_try_head = t_head;`);
3276
+ b.append("try {");
3277
+ }
2659
3278
  buildServerNode(current.markup, status, b);
2660
3279
  if (status.output) {
2661
3280
  b.append(`t_body += \`${status.output}\`;`);
2662
3281
  status.output = "";
2663
3282
  }
2664
3283
  }
3284
+ } else if (chunk.script === "/* @error */") {
3285
+ if (current.markup && current.error) {
3286
+ const status = {
3287
+ imports,
3288
+ output: "",
3289
+ styleHash: current.style?.hash || "",
3290
+ varNames: {},
3291
+ preserveWhitespace: false,
3292
+ options
3293
+ };
3294
+ b.append(`} catch (${current.errorVar}) {`);
3295
+ b.append("t_body = t_try_body;");
3296
+ b.append("t_head = t_try_head;");
3297
+ b.append("/* User interface error */");
3298
+ buildServerNode(current.error, status, b);
3299
+ if (status.output) {
3300
+ b.append(`t_body += \`${status.output}\`;`);
3301
+ status.output = "";
3302
+ }
3303
+ b.append("}");
3304
+ }
2665
3305
  } else if (chunk.script === "/* @head */") {} else if (chunk.script === "/* @style */") {
2666
3306
  if (current.style) {
2667
3307
  b.append("");
@@ -2669,13 +3309,115 @@ function buildServerTemplate(template, imports, b, options) {
2669
3309
  const styles = buildStyles(current.style, current.style.hash).replaceAll("\"", "\\\"").replaceAll(/\s+/g, " ");
2670
3310
  b.append(`t_head += "<style id='${current.style.hash}'>${styles}</style>";\n`);
2671
3311
  }
2672
- } else if (chunk.script === "/* @end */") {
2673
- b.append(`return { body: t_body, head: t_head };`);
2674
- currentIndex += 1;
2675
- current = template.components[currentIndex];
2676
- } else b.append(chunk.script);
3312
+ } else if (chunk.script === "/* @end */") {
3313
+ b.append(`return { body: t_body, head: t_head };`);
3314
+ currentIndex += 1;
3315
+ current = template.components[currentIndex];
3316
+ } else b.append(chunk.script);
3317
+ }
3318
+ //#endregion
3319
+ //#region src/compile/utils/trimWhitespace.ts
3320
+ /**
3321
+ * Tag names whose contents should keep all whitespace untouched, matching
3322
+ * native HTML rendering of `pre`/`textarea` and the existing build-time
3323
+ * `preserveWhitespace` behavior for `code`.
3324
+ */
3325
+ const PRESERVE_TAGS = /* @__PURE__ */ new Set([
3326
+ "pre",
3327
+ "textarea",
3328
+ "code"
3329
+ ]);
3330
+ /**
3331
+ * Container tags whose child layout ignores inter-child whitespace entirely
3332
+ * (table layout collapses it). Inside these, whitespace-only text nodes
3333
+ * between children are removed rather than collapsed to a single space.
3334
+ */
3335
+ const WHITESPACE_INSIGNIFICANT_TAGS = /* @__PURE__ */ new Set([
3336
+ "tr",
3337
+ "tbody",
3338
+ "thead",
3339
+ "tfoot",
3340
+ "colgroup",
3341
+ "table",
3342
+ "ul",
3343
+ "ol",
3344
+ "select"
3345
+ ]);
3346
+ /**
3347
+ * Trims and collapses whitespace text nodes in a parsed markup tree,
3348
+ * Svelte 5-style:
3349
+ *
3350
+ * - pure-whitespace text nodes at the start/end of a container are removed
3351
+ * - pure-whitespace text nodes between siblings are collapsed to a single space
3352
+ * (or removed entirely inside table/list containers where whitespace is
3353
+ * insignificant — see WHITESPACE_INSIGNIFICANT_TAGS)
3354
+ * - whitespace inside `pre`, `textarea`, and `code` is preserved
3355
+ *
3356
+ * Whitespace adjacent to non-rendering control nodes (e.g. `@key`, `@const`)
3357
+ * is treated as leading/trailing whitespace of the container and removed,
3358
+ * because those nodes produce no DOM output — without this rule, every row
3359
+ * of `@for { @key = ...; <tr>...</tr> }` would emit a phantom leading
3360
+ * whitespace text node.
3361
+ *
3362
+ * Mixed text nodes (containing non-whitespace content) are left untouched;
3363
+ * their internal whitespace continues to be collapsed at build time. The
3364
+ * walk mutates the tree in place and is idempotent.
3365
+ *
3366
+ * @param node The root node of the markup to trim (a component's markup or head)
3367
+ */
3368
+ function trimWhitespace(node) {
3369
+ trimChildren(node.children, false, void 0);
3370
+ }
3371
+ function trimChildren(children, preserveAll, parentTag) {
3372
+ for (const child of children) {
3373
+ const grandChildren = getChildren(child);
3374
+ if (grandChildren) {
3375
+ const childTag = getTagName(child);
3376
+ trimChildren(grandChildren, preserveAll || PRESERVE_TAGS.has(childTag ?? ""), childTag);
3377
+ }
3378
+ }
3379
+ if (preserveAll) return;
3380
+ if (parentTag !== void 0 && WHITESPACE_INSIGNIFICANT_TAGS.has(parentTag)) {
3381
+ for (let i = children.length - 1; i >= 0; i--) if (isWhitespaceText(children[i])) children.splice(i, 1);
3382
+ return;
3383
+ }
3384
+ const length = children.length;
3385
+ const hasRenderingBefore = Array.from({ length });
3386
+ const hasRenderingAfter = Array.from({ length });
3387
+ let seenBefore = false;
3388
+ let seenAfter = false;
3389
+ for (let i = 0; i < length; i++) {
3390
+ hasRenderingBefore[i] = seenBefore;
3391
+ if (isRenderingSibling(children[i])) seenBefore = true;
3392
+ const j = length - 1 - i;
3393
+ hasRenderingAfter[j] = seenAfter;
3394
+ if (isRenderingSibling(children[j])) seenAfter = true;
3395
+ }
3396
+ for (let i = length - 1; i >= 0; i--) if (isWhitespaceText(children[i]) && (!hasRenderingBefore[i] || !hasRenderingAfter[i])) children.splice(i, 1);
3397
+ for (const child of children) if (isTextNode(child) && child.content !== "" && isSpace(child.content)) child.content = " ";
3398
+ }
3399
+ /**
3400
+ * Returns true if `node` is a "rendering sibling" — i.e. a node that produces
3401
+ * visible DOM output and therefore acts as a boundary between leading,
3402
+ * inter-sibling, and trailing whitespace. Whitespace text nodes themselves
3403
+ * (the subject of the trim) and non-rendering control nodes (e.g. @key,
3404
+ * @const) are not rendering siblings.
3405
+ */
3406
+ function isRenderingSibling(node) {
3407
+ if (isWhitespaceText(node)) return false;
3408
+ if (isControlNode(node) && NON_RENDERING_OPERATIONS.has(node.operation)) return false;
3409
+ return true;
3410
+ }
3411
+ function getChildren(node) {
3412
+ if (node.type === "text" || node.type === "comment") return void 0;
3413
+ return node.children;
3414
+ }
3415
+ function getTagName(node) {
3416
+ if (node.type === "element" || node.type === "component" || node.type === "special") return node.tagName;
3417
+ }
3418
+ function isWhitespaceText(node) {
3419
+ return isTextNode(node) && node.content !== "" && isSpace(node.content);
2677
3420
  }
2678
-
2679
3421
  //#endregion
2680
3422
  //#region src/compile/build.ts
2681
3423
  /**
@@ -2686,6 +3428,11 @@ function buildServerTemplate(template, imports, b, options) {
2686
3428
  * @returns The component's code and styles
2687
3429
  */
2688
3430
  function build(template, options) {
3431
+ if (!options?.preserveWhitespace) for (const component of template.components) {
3432
+ if (component.markup) trimWhitespace(component.markup);
3433
+ if (component.error) trimWhitespace(component.error);
3434
+ if (component.head) trimWhitespace(component.head);
3435
+ }
2689
3436
  let map = [];
2690
3437
  return {
2691
3438
  code: options?.server ? buildServerCode(template, options) : buildCode(template, map, options),
@@ -2696,7 +3443,6 @@ function build(template, options) {
2696
3443
  map
2697
3444
  };
2698
3445
  }
2699
-
2700
3446
  //#endregion
2701
3447
  //#region src/compile/buildType.ts
2702
3448
  /**
@@ -2718,7 +3464,7 @@ function buildType(template, options) {
2718
3464
  }
2719
3465
  b.append(`
2720
3466
  ${component.documentation ?? ""}
2721
- ${component.default ? "declare" : "export declare"} function ${component.name}(
3467
+ ${component.default ? "declare" : "export declare"} function ${component.name}${component.typeParams ?? ""}(
2722
3468
  $parent: ParentNode,
2723
3469
  $anchor: Node | null,
2724
3470
  ${component.params || "$props?: Record<PropertyKey, any>"},
@@ -2730,13 +3476,29 @@ function buildType(template, options) {
2730
3476
  }
2731
3477
  return b.toString();
2732
3478
  }
2733
-
3479
+ //#endregion
3480
+ //#region src/compile/utils/endOfTemplateString.ts
3481
+ /**
3482
+ * Returns the index of the closing backtick of the template string that
3483
+ * starts at `start`.
3484
+ *
3485
+ * NOTE: For scanning JavaScript expression text, use `codeScanner.ts` instead
3486
+ * -- this variant doesn't handle escaped characters or nested templates in
3487
+ * interpolations, and is kept only for non-JS contexts (CSS in `@style`
3488
+ * blocks) where a simpler scan is wanted.
3489
+ */
3490
+ function endOfTemplateString(content, start) {
3491
+ let level = 0;
3492
+ for (let j = start + 1; j < content.length; j++) if (content[j] === "`" && content[j - 1] !== "\\" && level === 0) return j;
3493
+ else if (content[j] === "{" && (level > 0 || content[j - 1] === "$")) level += 1;
3494
+ else if (content[j] === "}" && level > 0) level -= 1;
3495
+ return content.length;
3496
+ }
2734
3497
  //#endregion
2735
3498
  //#region src/compile/utils/isSpaceNode.ts
2736
3499
  function isSpaceNode(node) {
2737
3500
  return isTextNode(node) && isSpace(node.content);
2738
3501
  }
2739
-
2740
3502
  //#endregion
2741
3503
  //#region src/compile/parse/utils/accept.ts
2742
3504
  /**
@@ -2757,25 +3519,24 @@ function accept(value, status, advance = true) {
2757
3519
  }
2758
3520
  return false;
2759
3521
  }
2760
-
2761
3522
  //#endregion
2762
3523
  //#region src/compile/parse/parseInlineScript.ts
2763
3524
  function parseInlineScript(status) {
2764
3525
  const start = status.i;
2765
3526
  let braceCount = 0;
2766
- while (status.i < status.source.length) if (accept("{", status)) braceCount += 1;
2767
- else if (accept("}", status)) if (braceCount > 0) braceCount -= 1;
2768
- else return status.source.substring(start, status.i - 1);
2769
- else if (accept("//", status)) status.i = status.source.indexOf("\n", status.i) + 1;
2770
- else if (accept("/*", status)) status.i = status.source.indexOf("*/", status.i) + 2;
2771
- else if (accept("\"", status) || accept("'", status)) {
2772
- const char = status.source[status.i - 1];
2773
- status.i = endOfString(char, status.source, status.i - 1) + 1;
2774
- } else if (accept("`", status)) status.i = endOfTemplateString(status.source, status.i - 1) + 1;
2775
- else status.i += 1;
3527
+ while (status.i < status.source.length) {
3528
+ const skipped = skipStringOrComment(status.source, status.i);
3529
+ if (skipped !== -1) {
3530
+ status.i = skipped;
3531
+ continue;
3532
+ }
3533
+ if (accept("{", status)) braceCount += 1;
3534
+ else if (accept("}", status)) if (braceCount > 0) braceCount -= 1;
3535
+ else return status.source.substring(start, status.i - 1);
3536
+ else status.i += 1;
3537
+ }
2776
3538
  return "";
2777
3539
  }
2778
-
2779
3540
  //#endregion
2780
3541
  //#region src/compile/utils/trimStart.ts
2781
3542
  /**
@@ -2791,7 +3552,6 @@ function trimStart(text, char) {
2791
3552
  while (text[start] === char) start += 1;
2792
3553
  return text.substring(start);
2793
3554
  }
2794
-
2795
3555
  //#endregion
2796
3556
  //#region src/compile/parse/utils/consumeSpace.ts
2797
3557
  /**
@@ -2808,7 +3568,6 @@ function consumeSpace(status) {
2808
3568
  for (status.i; status.i < status.source.length; status.i++) if (!isSpaceChar(status.source, status.i)) return status.source.substring(start, status.i);
2809
3569
  return "";
2810
3570
  }
2811
-
2812
3571
  //#endregion
2813
3572
  //#region src/compile/parse/addSpaceElement.ts
2814
3573
  function addSpaceElement(parent, status) {
@@ -2826,7 +3585,6 @@ function addSpaceElement(parent, status) {
2826
3585
  }
2827
3586
  }
2828
3587
  }
2829
-
2830
3588
  //#endregion
2831
3589
  //#region src/compile/utils/isFullyReactive.ts
2832
3590
  /**
@@ -2839,7 +3597,41 @@ function addSpaceElement(parent, status) {
2839
3597
  function isFullyReactive(text) {
2840
3598
  return text.trim().startsWith("{") && text.trim().endsWith("}");
2841
3599
  }
2842
-
3600
+ //#endregion
3601
+ //#region src/compile/parse/utils/addError.ts
3602
+ /**
3603
+ * Adds an error to the parse status
3604
+ *
3605
+ * @param status The parse status
3606
+ * @param message The error message to add
3607
+ * @param start (Optional) The start position of the error (defaults to the
3608
+ * current parse position)
3609
+ */
3610
+ function addError(status, message, start = status.i, end = status.i) {
3611
+ let startLine = 0;
3612
+ let lastLineStart = 0;
3613
+ let i = 0;
3614
+ for (; i < start; i++) if (status.source[i] === "\n") {
3615
+ startLine += 1;
3616
+ lastLineStart = i + 1;
3617
+ }
3618
+ let startChar = start - lastLineStart;
3619
+ let endLine = startLine;
3620
+ for (; i < end; i++) if (status.source[i] === "\n") {
3621
+ endLine += 1;
3622
+ lastLineStart = i + 1;
3623
+ }
3624
+ let endChar = end - lastLineStart;
3625
+ status.errors.push({
3626
+ message,
3627
+ startIndex: start,
3628
+ startLine,
3629
+ startChar,
3630
+ endIndex: end,
3631
+ endLine,
3632
+ endChar
3633
+ });
3634
+ }
2843
3635
  //#endregion
2844
3636
  //#region src/compile/parse/utils/isAlphaNumericChar.ts
2845
3637
  /**
@@ -2854,7 +3646,6 @@ function isAlphaNumericChar(text, index) {
2854
3646
  let code = text.charCodeAt(index);
2855
3647
  return code > 47 && code < 58 || code > 64 && code < 91 || code > 96 && code < 123;
2856
3648
  }
2857
-
2858
3649
  //#endregion
2859
3650
  //#region src/compile/parse/utils/consumeAlphaNumeric.ts
2860
3651
  /**
@@ -2871,7 +3662,6 @@ function consumeAlphaNumeric(status) {
2871
3662
  for (status.i; status.i < status.source.length; status.i++) if (!isAlphaNumericChar(status.source, status.i)) return status.source.substring(start, status.i);
2872
3663
  return "";
2873
3664
  }
2874
-
2875
3665
  //#endregion
2876
3666
  //#region src/compile/parse/utils/consumeUntil.ts
2877
3667
  /**
@@ -2889,7 +3679,6 @@ function consumeUntil(chars, status) {
2889
3679
  for (status.i; status.i < status.source.length; status.i++) if (chars.includes(status.source[status.i])) return status.source.substring(start, status.i);
2890
3680
  return "";
2891
3681
  }
2892
-
2893
3682
  //#endregion
2894
3683
  //#region src/compile/parse/parseTag.ts
2895
3684
  function parseTag(status) {
@@ -2912,6 +3701,7 @@ function parseTag(status) {
2912
3701
  type: [
2913
3702
  "slot",
2914
3703
  "fill",
3704
+ "filldef",
2915
3705
  "@component",
2916
3706
  "@element"
2917
3707
  ].includes(tagName) ? "special" : "element",
@@ -2965,10 +3755,12 @@ function parseAttribute(status) {
2965
3755
  value = name.substring(1, name.length - 1);
2966
3756
  valueStart++;
2967
3757
  valueEnd = valueStart + name.length;
2968
- name = value.split(".").at(-1);
3758
+ if (value.startsWith("...")) name = value;
3759
+ else name = value.split(".").at(-1);
2969
3760
  if (name.endsWith("!")) name = name.substring(0, name.length - 1);
2970
3761
  reactive = fullyReactive = true;
2971
3762
  }
3763
+ if (value != null && reactive && value.startsWith("...")) addError(status, `Spread attributes are not supported`, valueStart, valueEnd);
2972
3764
  return {
2973
3765
  name,
2974
3766
  value,
@@ -2999,7 +3791,6 @@ function parseAttributeValue(status) {
2999
3791
  return value;
3000
3792
  }
3001
3793
  }
3002
-
3003
3794
  //#endregion
3004
3795
  //#region src/compile/parse/parseText.ts
3005
3796
  function parseText(status, element) {
@@ -3013,18 +3804,16 @@ function parseText(status, element) {
3013
3804
  let level = 0;
3014
3805
  for (let k = j; k < status.source.length; k++) {
3015
3806
  const char = status.source[k];
3016
- const nextChar = status.source[k + 1];
3017
- if (char === "/" && nextChar === "/") k = status.source.indexOf("\n", k);
3018
- else if (char === "/" && nextChar === "*") k = status.source.indexOf("*/", k) + 1;
3019
- else if (char === "\"" || char === "'") k = endOfString(char, status.source, k);
3020
- else if (char === "`") k = endOfTemplateString(status.source, k);
3021
- else if (char === "{") level += 1;
3807
+ if (char === "{") level += 1;
3022
3808
  else if (char === "}") {
3023
3809
  level -= 1;
3024
3810
  if (level === 0) {
3025
3811
  j = k;
3026
3812
  break;
3027
3813
  }
3814
+ } else {
3815
+ const skipped = skipStringOrComment(status.source, k);
3816
+ if (skipped !== -1) k = skipped - 1;
3028
3817
  }
3029
3818
  }
3030
3819
  spans.push({
@@ -3060,7 +3849,38 @@ function parseText(status, element) {
3060
3849
  status.i = end;
3061
3850
  } else status.i += 1;
3062
3851
  }
3063
-
3852
+ //#endregion
3853
+ //#region src/compile/parse/utils/usesSlot.ts
3854
+ const slotRegex = /\$slot\b/;
3855
+ /**
3856
+ * Checks whether the $slot variable is read in the nodes' own scope
3857
+ *
3858
+ * Note that the children of a component node are excluded, because they are
3859
+ * passed to the component as fills, which get their own $slot parameter.
3860
+ * The component's attributes do belong to this scope.
3861
+ *
3862
+ * @param nodes The nodes to check
3863
+ *
3864
+ * @returns True if a $slot variable is read in the nodes' own scope
3865
+ */
3866
+ function usesSlot(nodes) {
3867
+ for (let node of nodes) if (node.type === "text") {
3868
+ const text = node;
3869
+ if (slotRegex.test(text.content)) return true;
3870
+ } else if (node.type === "control") {
3871
+ const control = node;
3872
+ if (slotRegex.test(control.statement)) return true;
3873
+ if (usesSlot(control.children)) return true;
3874
+ } else if (node.type === "element" || node.type === "special" || node.type === "component") {
3875
+ const element = node;
3876
+ for (let attribute of element.attributes) {
3877
+ if (slotRegex.test(attribute.name)) return true;
3878
+ if (attribute.value && slotRegex.test(attribute.value)) return true;
3879
+ }
3880
+ if (element.type !== "component" && usesSlot(element.children)) return true;
3881
+ }
3882
+ return false;
3883
+ }
3064
3884
  //#endregion
3065
3885
  //#region src/compile/parse/slottifyChildNodes.ts
3066
3886
  /**
@@ -3068,65 +3888,33 @@ function parseText(status, element) {
3068
3888
  * <fill> node
3069
3889
  */
3070
3890
  function slottifyChildNodes(node) {
3071
- const nonFillNodes = node.children.filter((c) => !isFillNode(c));
3891
+ let nonFillNodes = [];
3892
+ for (let child of node.children) if (isFillNode(child)) if (nonFillNodes.find((n) => !isSpaceNode(n)) === void 0) nonFillNodes = [];
3893
+ else break;
3894
+ else nonFillNodes.push(child);
3895
+ if (nonFillNodes.find((n) => !isSpaceNode(n)) === void 0) nonFillNodes = [];
3072
3896
  if (nonFillNodes.length) {
3073
3897
  let defaultFillNode = node.children.find((n) => isFillNode(n) && !n.attributes.find((a) => a.name === "name"));
3074
3898
  if (!defaultFillNode) {
3075
3899
  defaultFillNode = {
3076
3900
  type: "special",
3077
- tagName: "fill",
3901
+ tagName: "filldef",
3078
3902
  attributes: [],
3079
3903
  children: nonFillNodes,
3080
3904
  span: {
3081
3905
  start: 0,
3082
3906
  end: 0
3083
- }
3907
+ },
3908
+ hasSlotProps: usesSlot(nonFillNodes)
3084
3909
  };
3085
3910
  node.children.unshift(defaultFillNode);
3086
3911
  }
3912
+ node.children = node.children.filter((c) => isFillNode(c) || !nonFillNodes.includes(c));
3087
3913
  }
3088
- node.children = node.children.filter((c) => isFillNode(c));
3089
3914
  }
3090
3915
  function isFillNode(n) {
3091
- return isSpecialNode(n) && n.tagName === "fill";
3092
- }
3093
-
3094
- //#endregion
3095
- //#region src/compile/parse/utils/addError.ts
3096
- /**
3097
- * Adds an error to the parse status
3098
- *
3099
- * @param status The parse status
3100
- * @param message The error message to add
3101
- * @param start (Optional) The start position of the error (defaults to the
3102
- * current parse position)
3103
- */
3104
- function addError(status, message, start = status.i, end = status.i) {
3105
- let startLine = 0;
3106
- let lastLineStart = 0;
3107
- let i = 0;
3108
- for (; i < start; i++) if (status.source[i] === "\n") {
3109
- startLine += 1;
3110
- lastLineStart = i + 1;
3111
- }
3112
- let startChar = start - lastLineStart;
3113
- let endLine = startLine;
3114
- for (; i < end; i++) if (status.source[i] === "\n") {
3115
- endLine += 1;
3116
- lastLineStart = i + 1;
3117
- }
3118
- let endChar = end - lastLineStart;
3119
- status.errors.push({
3120
- message,
3121
- startIndex: start,
3122
- startLine,
3123
- startChar,
3124
- endIndex: end,
3125
- endLine,
3126
- endChar
3127
- });
3916
+ return isSpecialNode(n) && (n.tagName === "fill" || n.tagName === "filldef");
3128
3917
  }
3129
-
3130
3918
  //#endregion
3131
3919
  //#region src/compile/parse/parseElement.ts
3132
3920
  function parseElement(status) {
@@ -3138,16 +3926,40 @@ function parseElement(status) {
3138
3926
  addError(status, `Non-matching close tag: ${element.tagName.substring(1)}`, start, start + element.tagName.length);
3139
3927
  return element;
3140
3928
  }
3141
- if (voidTags_default.includes(element.tagName)) checkVoidElement(status, element);
3142
- if (!element.selfClosed && !voidTags_default.includes(element.tagName)) {
3929
+ if (voidTags.includes(element.tagName)) checkVoidElement(status, element);
3930
+ if (!element.selfClosed && !voidTags.includes(element.tagName)) {
3143
3931
  status.stack.push(element);
3144
3932
  while (status.i < status.source.length) {
3145
3933
  addSpaceElement(element, status);
3146
3934
  if (accept("}", status, false)) break;
3147
- else if (accept("<!--", status)) status.i = status.source.indexOf("-->", status.i) + 3;
3148
- else if (accept("@//", status)) status.i = status.source.indexOf("\n", status.i) + 1;
3149
- else if (accept("@/*", status)) status.i = status.source.indexOf("*/", status.i) + 2;
3150
- else if (accept("</", status, false)) {
3935
+ else if (accept("<!--", status)) {
3936
+ const start = status.i;
3937
+ status.i = status.source.indexOf("-->", status.i) + 3;
3938
+ const text = {
3939
+ type: "comment",
3940
+ commentType: "html",
3941
+ content: status.source.substring(start, status.i - 3)
3942
+ };
3943
+ element.children.push(text);
3944
+ } else if (accept("@//", status)) {
3945
+ const start = status.i;
3946
+ status.i = status.source.indexOf("\n", status.i) + 1;
3947
+ const text = {
3948
+ type: "comment",
3949
+ commentType: "line",
3950
+ content: status.source.substring(start, status.i - 1)
3951
+ };
3952
+ element.children.push(text);
3953
+ } else if (accept("@/*", status)) {
3954
+ const start = status.i;
3955
+ status.i = status.source.indexOf("*/", status.i) + 2;
3956
+ const text = {
3957
+ type: "comment",
3958
+ commentType: "block",
3959
+ content: status.source.substring(start, status.i - 2)
3960
+ };
3961
+ element.children.push(text);
3962
+ } else if (accept("</", status, false)) {
3151
3963
  if (checkCloseTag(status, element)) break;
3152
3964
  } else if (accept("<", status, false)) {
3153
3965
  const child = parseElement(status);
@@ -3166,10 +3978,8 @@ function parseElement(status) {
3166
3978
  slottifyChildNodes(element);
3167
3979
  current.slotProps ??= [];
3168
3980
  current.slotProps.push(element.attributes.find((a) => a.name === "name")?.name ?? "default");
3169
- } else if (element.tagName === "fill") {
3170
- const fillSource = status.source.substring(start, status.i);
3171
- element.hasSlotProps = /\$sprops\b/.test(fillSource);
3172
- } else if (element.tagName === "@component") {
3981
+ } else if (element.tagName === "fill" || element.tagName === "filldef") element.hasSlotProps = usesSlot(element.children);
3982
+ else if (element.tagName === "@component") {
3173
3983
  const selfAttribute = element.attributes.find((a) => a.name === "self");
3174
3984
  if (selfAttribute && selfAttribute.value && selfAttribute.fullyReactive) {
3175
3985
  element.type = "component";
@@ -3197,7 +4007,7 @@ function parseElement(status) {
3197
4007
  } else addError(status, "@component element must have a self attribute", start, start + element.tagName.length + 1);
3198
4008
  }
3199
4009
  }
3200
- if (!element.closed && !voidTags_default.includes(element.tagName)) addError(status, `Unclosed non-void element: ${element.tagName}`, start, start + element.tagName.length + 1);
4010
+ if (!element.closed && !voidTags.includes(element.tagName)) addError(status, `Unclosed non-void element: ${element.tagName}`, start, start + element.tagName.length + 1);
3201
4011
  return element;
3202
4012
  }
3203
4013
  function checkVoidElement(status, element) {
@@ -3232,7 +4042,6 @@ function checkCloseTag(status, element) {
3232
4042
  if (!found) addError(status, `Non-matching close tag: ${closingTagName} (expected ${element.tagName})`, start, start + element.tagName.length + 1);
3233
4043
  return found;
3234
4044
  }
3235
-
3236
4045
  //#endregion
3237
4046
  //#region src/compile/parse/parseControl.ts
3238
4047
  const controlOperations = [
@@ -3244,7 +4053,8 @@ const controlOperations = [
3244
4053
  "@case",
3245
4054
  "@default",
3246
4055
  "@await",
3247
- "@then",
4056
+ "@with",
4057
+ "@try",
3248
4058
  "@catch",
3249
4059
  "@replace",
3250
4060
  "@html",
@@ -3267,7 +4077,7 @@ function parseControl(status, parentNode) {
3267
4077
  const node = parseControlOpen(status);
3268
4078
  if (!node) return;
3269
4079
  if (standaloneOperations.includes(node.operation)) {
3270
- wrangleControlNode(node, parentNode);
4080
+ wrangleControlNode(node, parentNode, status);
3271
4081
  return;
3272
4082
  }
3273
4083
  while (status.i < status.source.length) {
@@ -3286,7 +4096,7 @@ function parseControl(status, parentNode) {
3286
4096
  break;
3287
4097
  }
3288
4098
  }
3289
- wrangleControlNode(node, parentNode);
4099
+ wrangleControlNode(node, parentNode, status);
3290
4100
  parseControlBranches(status, parentNode);
3291
4101
  }
3292
4102
  function parseControlOpen(status) {
@@ -3308,6 +4118,7 @@ function parseControlOpen(status) {
3308
4118
  accept("}", status);
3309
4119
  }
3310
4120
  if (operation === "@replace" || operation === "@html") statement = trimMatched(statement.substring(statement.indexOf("(")).trim(), "(", ")");
4121
+ if (operation === "@await" && /^await\s*\(/.test(statement)) addError(status, "old `@await (expr) { … } then (…) { … }` syntax is removed — use `@await { … } with { … }` with `$async` getters", start, status.i);
3311
4122
  } else {
3312
4123
  addError(status, `Unknown operation: ${operation}`, status.i, status.i + operation.length);
3313
4124
  return null;
@@ -3325,25 +4136,27 @@ function parseControlOpen(status) {
3325
4136
  }
3326
4137
  function parseControlStatement(start, operation, status) {
3327
4138
  let parenCount = 0;
3328
- while (status.i < status.source.length) if (accept("(", status)) parenCount += 1;
3329
- else if (accept(")", status)) parenCount -= 1;
3330
- else if (accept("{", status) || accept("\n", status) && standaloneOperations.includes(operation)) {
3331
- if (parenCount === 0) return trimStart(status.source.substring(start, status.i - 1).trim(), "@");
3332
- } else if (accept("//", status)) status.i = status.source.indexOf("\n", status.i) + 1;
3333
- else if (accept("/*", status)) status.i = status.source.indexOf("*/", status.i) + 2;
3334
- else if (accept("\"", status) || accept("'", status)) {
3335
- let char = status.source[status.i - 1];
3336
- status.i = endOfString(char, status.source, status.i - 1) + 1;
3337
- } else if (accept("`", status)) status.i = endOfTemplateString(status.source, status.i - 1) + 1;
3338
- else status.i += 1;
4139
+ while (status.i < status.source.length) {
4140
+ const skipped = skipStringOrComment(status.source, status.i);
4141
+ if (skipped !== -1) {
4142
+ status.i = skipped;
4143
+ continue;
4144
+ }
4145
+ if (accept("(", status)) parenCount += 1;
4146
+ else if (accept(")", status)) parenCount -= 1;
4147
+ else if (accept("{", status) || accept("\n", status) && standaloneOperations.includes(operation)) {
4148
+ if (parenCount === 0) return trimStart(status.source.substring(start, status.i - 1).trim(), "@");
4149
+ } else status.i += 1;
4150
+ }
3339
4151
  return "";
3340
4152
  }
3341
4153
  /**
3342
4154
  * Groups the control node into the correct parent e.g. if/else into an if group
3343
4155
  * @param node The control node
3344
4156
  * @param parentNode The parent node
4157
+ * @param status The parse status, for error reporting
3345
4158
  */
3346
- function wrangleControlNode(node, parentNode) {
4159
+ function wrangleControlNode(node, parentNode, status) {
3347
4160
  if (node.operation === "@if") {
3348
4161
  const ifGroup = {
3349
4162
  type: "control",
@@ -3392,14 +4205,41 @@ function wrangleControlNode(node, parentNode) {
3392
4205
  }
3393
4206
  };
3394
4207
  parentNode.children.push(awaitGroup);
3395
- } else if (node.operation === "@then" || node.operation === "@catch") for (let i = parentNode.children.length - 1; i >= 0; i--) {
4208
+ } else if (node.operation === "@with") for (let i = parentNode.children.length - 1; i >= 0; i--) {
3396
4209
  const lastChild = parentNode.children[i];
3397
4210
  if (isControlNode(lastChild) && lastChild.operation === "@await group") {
3398
4211
  lastChild.children.push(node);
3399
4212
  break;
3400
4213
  }
3401
4214
  }
3402
- else if (node.operation === "@replace") {
4215
+ else if (node.operation === "@catch") {
4216
+ let attached = false;
4217
+ let duplicate = false;
4218
+ for (let i = parentNode.children.length - 1; i >= 0; i--) {
4219
+ const lastChild = parentNode.children[i];
4220
+ if (isControlNode(lastChild) && lastChild.operation === "@try group") {
4221
+ if (lastChild.children.some((child) => isControlNode(child) && child.operation === "@catch")) duplicate = true;
4222
+ else {
4223
+ lastChild.children.push(node);
4224
+ attached = true;
4225
+ }
4226
+ break;
4227
+ }
4228
+ }
4229
+ if (!attached) addError(status, duplicate ? "`@try` cannot have more than one `@catch` block" : "`@catch` must follow a `@try` block", node.span.start, node.span.end);
4230
+ } else if (node.operation === "@try") {
4231
+ const tryGroup = {
4232
+ type: "control",
4233
+ operation: "@try group",
4234
+ statement: "",
4235
+ children: [node],
4236
+ span: {
4237
+ start: 0,
4238
+ end: 0
4239
+ }
4240
+ };
4241
+ parentNode.children.push(tryGroup);
4242
+ } else if (node.operation === "@replace") {
3403
4243
  const replaceGroup = {
3404
4244
  type: "control",
3405
4245
  operation: "@replace group",
@@ -3436,61 +4276,93 @@ function parseControlBranches(status, parentNode) {
3436
4276
  let start = status.i;
3437
4277
  status.i += 1;
3438
4278
  consumeSpace(status);
3439
- if (accept("else", status, false) || accept("case", status, false) || accept("default", status, false) || accept("then", status, false) || accept("catch", status, false)) parseControl(status, parentNode);
4279
+ if (accept("else", status, false) || accept("case", status, false) || accept("default", status, false) || accept("catch", status, false) || accept("with", status, false)) parseControl(status, parentNode);
3440
4280
  else {
3441
4281
  gotAnotherBranch = false;
3442
4282
  status.i = start;
3443
4283
  }
3444
4284
  }
3445
4285
  }
3446
-
3447
4286
  //#endregion
3448
4287
  //#region src/compile/parse/parseMarkup.ts
3449
4288
  function parseMarkup(status, source) {
3450
4289
  const current = status.components.at(-1);
3451
4290
  if (!current) return;
4291
+ parseMarkupInto(status, source, (root) => current.markup ??= root);
4292
+ }
4293
+ /**
4294
+ * Parses markup into a root node, letting callers choose where the resulting
4295
+ * RootNode is stored (e.g. a component's `markup` or its `error` block).
4296
+ *
4297
+ * @param status The parse status
4298
+ * @param source The full source code
4299
+ * @param setRoot Called with a fresh root node the first time content is
4300
+ * parsed; subsequent content is appended to the same node.
4301
+ */
4302
+ function parseMarkupInto(status, source, setRoot) {
4303
+ let root = null;
4304
+ const getRoot = () => root ??= {
4305
+ type: "root",
4306
+ children: []
4307
+ };
3452
4308
  while (status.i < source.length) if (accept("}", status, false)) return;
3453
- else if (accept("<!--", status)) status.i = status.source.indexOf("-->", status.i) + 3;
3454
- else if (accept("@//", status)) status.i = status.source.indexOf("\n", status.i) + 1;
3455
- else if (accept("@/*", status)) status.i = status.source.indexOf("*/", status.i) + 2;
3456
- else if (isSpaceChar(status.source, status.i)) {
3457
- current.markup ??= {
3458
- type: "root",
3459
- children: []
4309
+ else if (accept("<!--", status)) {
4310
+ const start = status.i;
4311
+ status.i = status.source.indexOf("-->", status.i) + 3;
4312
+ const text = {
4313
+ type: "comment",
4314
+ commentType: "html",
4315
+ content: status.source.substring(start, status.i - 3)
4316
+ };
4317
+ setRoot(getRoot());
4318
+ getRoot().children.push(text);
4319
+ } else if (accept("@//", status)) {
4320
+ const start = status.i;
4321
+ status.i = status.source.indexOf("\n", status.i) + 1;
4322
+ const text = {
4323
+ type: "comment",
4324
+ commentType: "line",
4325
+ content: status.source.substring(start, status.i - 1)
4326
+ };
4327
+ setRoot(getRoot());
4328
+ getRoot().children.push(text);
4329
+ } else if (accept("@/*", status)) {
4330
+ const start = status.i;
4331
+ status.i = status.source.indexOf("*/", status.i) + 2;
4332
+ const text = {
4333
+ type: "comment",
4334
+ commentType: "block",
4335
+ content: status.source.substring(start, status.i - 2)
3460
4336
  };
4337
+ setRoot(getRoot());
4338
+ getRoot().children.push(text);
4339
+ } else if (isSpaceChar(status.source, status.i)) {
4340
+ setRoot(getRoot());
3461
4341
  const text = {
3462
4342
  type: "text",
3463
4343
  content: consumeSpace(status),
3464
4344
  spans: []
3465
4345
  };
3466
- current.markup.children.push(text);
4346
+ getRoot().children.push(text);
3467
4347
  } else if (accept("<", status, false)) {
3468
- current.markup ??= {
3469
- type: "root",
3470
- children: []
3471
- };
4348
+ setRoot(getRoot());
3472
4349
  const element = parseElement(status);
3473
- current.markup.children.push(element);
4350
+ getRoot().children.push(element);
3474
4351
  } else if (accept("@", status, false)) {
3475
- current.markup ??= {
3476
- type: "root",
3477
- children: []
3478
- };
3479
- parseControl(status, current.markup);
4352
+ setRoot(getRoot());
4353
+ parseControl(status, getRoot());
3480
4354
  } else status.i += 1;
3481
4355
  }
3482
-
3483
4356
  //#endregion
3484
4357
  //#region src/compile/utils/hash.ts
3485
4358
  const returnRegex = /\r/g;
3486
4359
  function hash(input) {
3487
4360
  input = input.replace(returnRegex, "");
3488
- let hash$1 = 5381;
4361
+ let hash = 5381;
3489
4362
  let i = input.length;
3490
- while (i--) hash$1 = (hash$1 << 5) - hash$1 ^ input.charCodeAt(i);
3491
- return (hash$1 >>> 0).toString(36);
4363
+ while (i--) hash = (hash << 5) - hash ^ input.charCodeAt(i);
4364
+ return (hash >>> 0).toString(36);
3492
4365
  }
3493
-
3494
4366
  //#endregion
3495
4367
  //#region src/compile/parse/utils/expect.ts
3496
4368
  /**
@@ -3512,7 +4384,6 @@ function expect(value, status, advance = true) {
3512
4384
  else addError(status, "Expected token");
3513
4385
  return false;
3514
4386
  }
3515
-
3516
4387
  //#endregion
3517
4388
  //#region src/compile/parse/parseStyles.ts
3518
4389
  function parseStyles(source, status) {
@@ -3520,7 +4391,7 @@ function parseStyles(source, status) {
3520
4391
  if (!current) return;
3521
4392
  current.style = {
3522
4393
  global: false,
3523
- blocks: [],
4394
+ children: [],
3524
4395
  hash: hash(source)
3525
4396
  };
3526
4397
  const styleStatus = {
@@ -3528,6 +4399,7 @@ function parseStyles(source, status) {
3528
4399
  i: 0,
3529
4400
  marker: 0,
3530
4401
  level: 0,
4402
+ braces: [],
3531
4403
  imports: [],
3532
4404
  script: [],
3533
4405
  components: [],
@@ -3536,69 +4408,86 @@ function parseStyles(source, status) {
3536
4408
  };
3537
4409
  while (styleStatus.i < styleStatus.source.length) if (isSpaceChar(styleStatus.source, styleStatus.i)) consumeSpace(styleStatus);
3538
4410
  else {
3539
- const block = parseStyleBlock(styleStatus);
3540
- if (block) current.style.blocks.push(block);
4411
+ const node = parseStyleNode(styleStatus);
4412
+ if (node) current.style.children.push(node);
3541
4413
  else break;
3542
4414
  }
3543
4415
  status.errors = status.errors.concat(styleStatus.errors);
3544
4416
  }
3545
- function parseStyleBlock(status) {
4417
+ function parseStyleNode(status) {
4418
+ const gapBefore = /\n\s*\n\s*$/.test(status.source.slice(0, status.i));
4419
+ if (accept("//", status, false) || accept("/*", status, false)) return parseComment(status, gapBefore);
3546
4420
  const selector = consumeUntil("{", status).trim();
3547
4421
  if (expect("{", status)) {
3548
4422
  const block = {
4423
+ type: "block",
3549
4424
  selector,
3550
- attributes: [],
3551
- children: []
4425
+ children: [],
4426
+ gapBefore
3552
4427
  };
3553
4428
  consumeSpace(status);
3554
4429
  while (status.source[status.i] !== "}") {
3555
- let nextColon = status.source.indexOf(":", status.i);
3556
- if (nextColon === -1) nextColon = status.source.length;
3557
- let nextOpenBrace = status.source.indexOf("{", status.i);
3558
- if (nextOpenBrace === -1) nextOpenBrace = status.source.length;
3559
- if (nextColon < nextOpenBrace) {
3560
- const attribute = parseStyleAttribute(status);
3561
- block.attributes.push(attribute);
3562
- } else if (nextOpenBrace < nextColon) {
3563
- const child = parseStyleBlock(status);
3564
- if (child) block.children.push(child);
3565
- else break;
3566
- } else return block;
4430
+ if (accept("//", status, false) || accept("/*", status, false)) block.children.push(parseComment(status, false));
4431
+ else {
4432
+ let nextColon = status.source.indexOf(":", status.i);
4433
+ if (nextColon === -1) nextColon = status.source.length;
4434
+ let nextOpenBrace = status.source.indexOf("{", status.i);
4435
+ if (nextOpenBrace === -1) nextOpenBrace = status.source.length;
4436
+ if (nextColon < nextOpenBrace) {
4437
+ const attribute = parseStyleAttribute(status);
4438
+ block.children.push(attribute);
4439
+ } else if (nextOpenBrace < nextColon) {
4440
+ const child = parseStyleNode(status);
4441
+ if (child) block.children.push(child);
4442
+ else break;
4443
+ } else return block;
4444
+ }
3567
4445
  consumeSpace(status);
3568
4446
  }
3569
4447
  accept("}", status);
3570
4448
  return block;
3571
4449
  }
3572
4450
  }
4451
+ function parseComment(status, gapBefore) {
4452
+ const start = status.i;
4453
+ if (accept("//", status)) {
4454
+ const end = status.source.indexOf("\n", status.i);
4455
+ status.i = end === -1 ? status.source.length : end;
4456
+ } else {
4457
+ accept("/*", status);
4458
+ const end = status.source.indexOf("*/", status.i);
4459
+ status.i = end === -1 ? status.source.length : end + 2;
4460
+ }
4461
+ return {
4462
+ type: "comment",
4463
+ content: status.source.substring(start, status.i),
4464
+ gapBefore
4465
+ };
4466
+ }
3573
4467
  function parseStyleAttribute(status) {
4468
+ const gapBefore = /\n\s*\n\s*$/.test(status.source.slice(0, status.i));
3574
4469
  const name = consumeUntil(":", status).trim();
3575
4470
  accept(":", status);
3576
4471
  const value = consumeUntil(";", status).trim();
3577
4472
  accept(";", status);
3578
4473
  return {
4474
+ type: "attribute",
3579
4475
  name,
3580
- value
4476
+ value,
4477
+ gapBefore
3581
4478
  };
3582
4479
  }
3583
-
3584
4480
  //#endregion
3585
4481
  //#region src/compile/utils/isComponentNode.ts
3586
4482
  function isComponentNode(node) {
3587
4483
  return node.type === "component";
3588
4484
  }
3589
-
3590
- //#endregion
3591
- //#region src/compile/utils/isParentNode.ts
3592
- function isParentNode(node) {
3593
- return "children" in node;
3594
- }
3595
-
3596
4485
  //#endregion
3597
4486
  //#region src/compile/parse/scopeStyles.ts
3598
4487
  function scopeStyles(status) {
3599
4488
  let selectors = [];
3600
4489
  for (let component of status.components) if (component.style && component.markup) {
3601
- for (let block of component.style.blocks) collectStyleSelectors(block, selectors);
4490
+ for (let block of component.style.children) if (block.type === "block") collectStyleSelectors(block, selectors);
3602
4491
  scopeStylesOnNode(component.markup, selectors);
3603
4492
  }
3604
4493
  }
@@ -3616,7 +4505,6 @@ function scopeStylesOnNode(node, selectors) {
3616
4505
  function collectStyleSelectors(block, selectors) {
3617
4506
  for (let s of block.selector.split(/[\s*,>+~[\]]/)) selectors.push(s);
3618
4507
  }
3619
-
3620
4508
  //#endregion
3621
4509
  //#region src/compile/parse/parseCode.ts
3622
4510
  function parseCode(source) {
@@ -3625,16 +4513,24 @@ function parseCode(source) {
3625
4513
  i: 0,
3626
4514
  marker: 0,
3627
4515
  level: 0,
4516
+ braces: [],
3628
4517
  imports: [],
3629
4518
  script: [],
3630
4519
  components: [],
3631
4520
  stack: [],
3632
4521
  errors: []
3633
4522
  };
3634
- for (status.i; status.i < source.length; status.i++) if (consumeScriptComments(status)) {} else if (status.source[status.i] === "{") status.level += 1;
3635
- else if (status.source[status.i] === "}") {
4523
+ for (status.i; status.i < source.length; status.i++) if (consumeScriptComments(status)) {} else if (status.source[status.i] === "{") {
4524
+ status.level += 1;
4525
+ status.braces.push(false);
4526
+ } else if (status.source[status.i] === "}") {
3636
4527
  status.level -= 1;
3637
- if (status.level === 0) parseComponentEnd(status);
4528
+ const fromComponent = status.braces.pop();
4529
+ if (status.level === 0 && fromComponent) parseComponentEnd(status);
4530
+ else if (status.level < 0) {
4531
+ addError(status, `Unexpected closing brace`, status.i, status.i + 1);
4532
+ status.level = 0;
4533
+ }
3638
4534
  } else if (accept("function", status)) {
3639
4535
  if (consumeSpace(status)) if (/[A-Z]/.test(source[status.i])) parseComponentStart(status);
3640
4536
  else status.i--;
@@ -3656,8 +4552,13 @@ function parseCode(source) {
3656
4552
  if (consumeSpace(status) && accept("default", status) && consumeSpace(status) && accept("(", status, false)) parseComponentStart(status);
3657
4553
  else status.i = start;
3658
4554
  } else if (accept("@render", status, false)) parseComponentRender(status);
4555
+ else if (accept("@error", status, false)) parseComponentError(status);
3659
4556
  else if (accept("@head", status, false)) parseComponentHead(status);
3660
4557
  else if (accept("@style", status, false)) parseComponentStyle(status);
4558
+ if (status.level > 0) {
4559
+ const count = status.level;
4560
+ addError(status, `Unbalanced braces: missing ${count} closing brace${count === 1 ? "" : "s"}`, source.length, source.length);
4561
+ }
3661
4562
  status.script.push({
3662
4563
  script: source.substring(status.marker, source.length),
3663
4564
  span: {
@@ -3684,13 +4585,33 @@ function parseComponentStart(status) {
3684
4585
  let def = /export\s+default\s+(function\s+|const\s+)*$/.test(source);
3685
4586
  let isConst = /const\s+$/.test(source);
3686
4587
  let isAnon = /export default\s+$/.test(source);
4588
+ let componentStart = status.i;
4589
+ let name = consumeAlphaNumeric(status);
4590
+ if (def && status.components.some((c) => c.default)) addError(status, `Multiple default exports`, componentStart, componentStart + name.length);
3687
4591
  const current = {
3688
- start: status.i,
3689
- name: consumeAlphaNumeric(status),
4592
+ start: componentStart,
4593
+ name,
3690
4594
  exported,
3691
- default: def
4595
+ default: def,
4596
+ scriptStart: status.script.length
3692
4597
  };
3693
4598
  status.components.push(current);
4599
+ if (status.source[status.i] === "<") {
4600
+ const typeParamsStart = status.i;
4601
+ let level = 0;
4602
+ for (status.i; status.i < status.source.length; status.i++) {
4603
+ const char = status.source[status.i];
4604
+ if (char === "<") level += 1;
4605
+ else if (char === ">") {
4606
+ level -= 1;
4607
+ if (level === 0) {
4608
+ current.typeParams = status.source.substring(typeParamsStart, status.i + 1);
4609
+ status.i += 1;
4610
+ break;
4611
+ }
4612
+ }
4613
+ }
4614
+ }
3694
4615
  let presource = source.substring(0, status.i).trimEnd();
3695
4616
  let lastNewline = presource.lastIndexOf("\n");
3696
4617
  if (lastNewline !== -1) {
@@ -3737,8 +4658,8 @@ function parseComponentStart(status) {
3737
4658
  const paramsMatch = current.params?.match(/\$props:\s*([$A-Za-z0-9]+)/);
3738
4659
  if (paramsMatch) {
3739
4660
  let propsTypeName = paramsMatch[1];
3740
- let propsTypeStart = status.source.search(/* @__PURE__ */ new RegExp(`interface\\s+${propsTypeName}`));
3741
- if (propsTypeStart === -1) propsTypeStart = status.source.search(/* @__PURE__ */ new RegExp(`type\\s+${propsTypeName}`));
4661
+ let propsTypeStart = status.source.search(new RegExp(`interface\\s+${propsTypeName}`));
4662
+ if (propsTypeStart === -1) propsTypeStart = status.source.search(new RegExp(`type\\s+${propsTypeName}`));
3742
4663
  if (propsTypeStart !== -1) {
3743
4664
  let bracesStart = status.source.indexOf("{", propsTypeStart);
3744
4665
  if (bracesStart !== -1) {
@@ -3747,6 +4668,7 @@ function parseComponentStart(status) {
3747
4668
  i: bracesStart + 1,
3748
4669
  marker: 0,
3749
4670
  level: 0,
4671
+ braces: [],
3750
4672
  imports: [],
3751
4673
  script: [],
3752
4674
  components: [],
@@ -3767,6 +4689,7 @@ function parseComponentStart(status) {
3767
4689
  }
3768
4690
  accept("{", status);
3769
4691
  status.level += 1;
4692
+ status.braces.push(true);
3770
4693
  const postFunctionChunk = {
3771
4694
  script: status.source.substring(status.marker, status.i),
3772
4695
  span: {
@@ -3821,6 +4744,42 @@ function parseComponentRender(status) {
3821
4744
  }
3822
4745
  status.marker = status.i;
3823
4746
  }
4747
+ function parseComponentError(status) {
4748
+ const current = status.components.at(-1);
4749
+ if (!current) return;
4750
+ if (current.error) addError(status, `Multiple @error sections`, status.i, status.i + 6);
4751
+ if (!current.markup) addError(status, `@error must come after @render`, status.i, status.i + 6);
4752
+ status.script.push({
4753
+ script: status.source.substring(status.marker, status.i),
4754
+ span: {
4755
+ start: status.marker,
4756
+ end: status.i
4757
+ }
4758
+ });
4759
+ status.script.push({
4760
+ script: "/* @error */",
4761
+ span: {
4762
+ start: 0,
4763
+ end: 0
4764
+ }
4765
+ });
4766
+ accept("@error", status);
4767
+ consumeSpace(status);
4768
+ let errorVar = "";
4769
+ if (accept("(", status)) {
4770
+ let start = status.i;
4771
+ while (status.i < status.source.length && status.source[status.i] !== ")") status.i += 1;
4772
+ errorVar = status.source.substring(start, status.i).trim();
4773
+ accept(")", status);
4774
+ consumeSpace(status);
4775
+ }
4776
+ current.errorVar = errorVar || "err";
4777
+ if (accept("{", status)) {
4778
+ parseMarkupInto(status, status.source, (root) => current.error ??= root);
4779
+ accept("}", status);
4780
+ }
4781
+ status.marker = status.i;
4782
+ }
3824
4783
  function parseComponentHead(status) {
3825
4784
  const current = status.components.at(-1);
3826
4785
  if (!current) return;
@@ -3874,16 +4833,22 @@ function parseComponentStyle(status) {
3874
4833
  for (; status.i < status.source.length; status.i++) {
3875
4834
  const char = status.source[status.i];
3876
4835
  const nextChar = status.source[status.i + 1];
3877
- if (char === "/" && nextChar === "/") status.i = status.source.indexOf("\n", status.i);
3878
- else if (char === "/" && nextChar === "*") status.i = status.source.indexOf("*/", status.i) + 1;
3879
- else if (char === "\"" || char === "'") {
3880
- let start$1 = status.i;
3881
- status.i = endOfString(char, status.source, start$1);
3882
- styleSource += status.source.substring(start$1, status.i + 1);
4836
+ if (char === "/" && nextChar === "/") {
4837
+ let start = status.i;
4838
+ status.i = status.source.indexOf("\n", status.i);
4839
+ styleSource += status.source.substring(start, status.i + 1);
4840
+ } else if (char === "/" && nextChar === "*") {
4841
+ let start = status.i;
4842
+ status.i = status.source.indexOf("*/", status.i) + 1;
4843
+ styleSource += status.source.substring(start, status.i + 1);
4844
+ } else if (char === "\"" || char === "'") {
4845
+ let start = status.i;
4846
+ status.i = endOfString(char, status.source, start);
4847
+ styleSource += status.source.substring(start, status.i + 1);
3883
4848
  } else if (char === "`") {
3884
- let start$1 = status.i;
3885
- status.i = endOfTemplateString(status.source, start$1);
3886
- styleSource += status.source.substring(start$1, status.i + 1);
4849
+ let start = status.i;
4850
+ status.i = endOfTemplateString(status.source, start);
4851
+ styleSource += status.source.substring(start, status.i + 1);
3887
4852
  } else if (char === "{") {
3888
4853
  level += 1;
3889
4854
  if (start === -1) start = status.i + 1;
@@ -3918,49 +4883,80 @@ function parseComponentEnd(status) {
3918
4883
  end: 0
3919
4884
  }
3920
4885
  });
3921
- const componentSource = status.source.substring(current.start || 0, status.i);
3922
- current.props = getPropsUsage(componentSource);
3923
- current.contextProps = getContextUsage(componentSource);
4886
+ const scriptText = status.script.slice(current.scriptStart ?? status.script.length).map((chunk) => chunk.script).join("\n");
4887
+ const markupParts = [];
4888
+ if (current.markup) markupParts.push(collectMarkupExpressions(current.markup));
4889
+ if (current.error) markupParts.push(collectMarkupExpressions(current.error));
4890
+ if (current.head) markupParts.push(collectMarkupExpressions(current.head));
4891
+ const usageSource = scriptText + "\n" + markupParts.join("\n");
4892
+ const usageRanges = codeRanges(usageSource);
4893
+ current.props = getPropsUsage(usageSource, usageRanges);
4894
+ current.contextProps = getContextUsage(usageSource, usageRanges);
3924
4895
  if (current.markup && isElementNode(current.markup.children[0]) && current.markup.children[0].tagName === "html" && isSpaceNode(current.markup.children[0].children[0])) current.markup.children.splice(1, 1);
3925
4896
  status.marker = status.i;
3926
4897
  }
3927
- function getPropsUsage(source) {
3928
- const propsMatches = source.matchAll(/\$props\s*(?:\.([\d\w]+)|\[([^\]]+)\])/g);
4898
+ /**
4899
+ * Gets the `$props` names that the compiled output references. The input is
4900
+ * the component's code-only text (script plus markup expressions), with
4901
+ * `ranges` marking the positions that are code — matches that start inside a
4902
+ * string, comment or regex literal are ignored, so mentions inside
4903
+ * sample-code strings or prose don't count.
4904
+ */
4905
+ function getPropsUsage(source, ranges) {
3929
4906
  const props = [];
3930
- for (let match of propsMatches) {
4907
+ for (let match of source.matchAll(/\$props\??\s*(?:\.([\d\w]+)|\[([^\]]+)\])/g)) {
4908
+ if (!isInCode(ranges, match.index)) continue;
3931
4909
  const name = trimQuotes(match[1] || match[2]);
3932
4910
  if (!props.includes(name)) props.push(name);
3933
4911
  }
4912
+ if (!props.length) for (let match of source.matchAll(/(?<![\w$])\$props(?![\w])(?!\s*:)(?!<)/g)) {
4913
+ if (!isInCode(ranges, match.index)) continue;
4914
+ props.push("$props");
4915
+ break;
4916
+ }
3934
4917
  return props.length ? props : void 0;
3935
4918
  }
3936
- function getContextUsage(source) {
3937
- const contextsMatches = source.matchAll(/\$context\s*(?:\.([\d\w]+)|\[([^\]]+)\])/g);
4919
+ /**
4920
+ * Gets the `$context` names that the compiled output references. The input is
4921
+ * the component's code-only text (script plus markup expressions), with
4922
+ * `ranges` marking the positions that are code — matches that start inside a
4923
+ * string, comment or regex literal are ignored, so mentions inside
4924
+ * sample-code strings or prose don't count.
4925
+ */
4926
+ function getContextUsage(source, ranges) {
4927
+ const contextsMatches = source.matchAll(/\$context\??\s*(?:\.([\d\w]+)|\[([^\]]+)\])/g);
3938
4928
  const contexts = [];
3939
4929
  for (let match of contextsMatches) {
4930
+ if (!isInCode(ranges, match.index)) continue;
3940
4931
  const name = trimQuotes(match[1] || match[2]);
3941
4932
  if (!contexts.includes(name)) contexts.push(name);
3942
4933
  }
4934
+ if (!contexts.length) for (let match of source.matchAll(/(?<![\w$])\$context(?![\w])(?!\s*:)(?!<)/g)) {
4935
+ if (!isInCode(ranges, match.index)) continue;
4936
+ contexts.push("$context");
4937
+ break;
4938
+ }
3943
4939
  return contexts.length ? contexts : void 0;
3944
4940
  }
4941
+ /**
4942
+ * Returns true if `index` falls inside one of the (sorted, non-overlapping)
4943
+ * code ranges — i.e. not inside a string, comment or regex literal.
4944
+ */
4945
+ function isInCode(ranges, index) {
4946
+ for (const [start, end] of ranges) {
4947
+ if (index >= start && index < end) return true;
4948
+ if (index < start) return false;
4949
+ }
4950
+ return false;
4951
+ }
3945
4952
  function consumeScriptComments(status) {
3946
- const char = status.source[status.i];
3947
- const nextChar = status.source[status.i + 1];
3948
- if (char === "/" && nextChar === "/") {
3949
- status.i = status.source.indexOf("\n", status.i);
3950
- return true;
3951
- } else if (char === "/" && nextChar === "*") {
3952
- status.i = status.source.indexOf("*/", status.i) + 1;
3953
- return true;
3954
- } else if (char === "\"" || char === "'") {
3955
- status.i = endOfString(char, status.source, status.i);
3956
- return true;
3957
- } else if (char === "`") {
3958
- status.i = endOfTemplateString(status.source, status.i);
4953
+ const skipped = skipStringOrComment(status.source, status.i);
4954
+ if (skipped !== -1) {
4955
+ status.i = skipped;
3959
4956
  return true;
3960
4957
  }
3961
4958
  return false;
3962
4959
  }
3963
-
3964
4960
  //#endregion
3965
4961
  //#region src/compile/parse.ts
3966
4962
  /**
@@ -3974,7 +4970,7 @@ function consumeScriptComments(status) {
3974
4970
  function parse(source) {
3975
4971
  return parseCode(source);
3976
4972
  }
3977
-
3978
4973
  //#endregion
3979
4974
  export { build, buildType, parse };
4975
+
3980
4976
  //# sourceMappingURL=compile.mjs.map