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