@torpor/view 0.4.18 → 1.0.0

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 +1727 -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 +1730 -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 +2073 -494
  39. package/dist/index.d.cts +600 -112
  40. package/dist/index.d.cts.map +1 -1
  41. package/dist/index.d.mts +600 -112
  42. package/dist/index.d.mts.map +1 -1
  43. package/dist/index.mjs +2054 -491
  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/index.mjs CHANGED
@@ -1,6 +1,5 @@
1
- import { t as devContext_default } from "./devContext-CQ8RhBX-.mjs";
2
- import { a as buildStyles, i as proxyHandledSymbol, n as $unwrap, o as buildClasses, r as proxyDataSymbol, t as formatText } from "./formatText-DTIRmc3X.mjs";
3
-
1
+ import { t as devContext } from "./devContext-ZNsPyOgB.mjs";
2
+ import { a as buildStyles, c as proxyDataSymbol, i as formatText, n as fromServer, o as buildClasses, r as fromElement, s as $unwrap, t as fromWebSocket } from "./fromWebSocket-Dxc_uwm1.mjs";
4
3
  //#region src/render/context.ts
5
4
  const CONTEXT_SYMBOL = Symbol.for("torp.Context");
6
5
  const context = globalThis[CONTEXT_SYMBOL] ??= {
@@ -10,55 +9,41 @@ const context = globalThis[CONTEXT_SYMBOL] ??= {
10
9
  batch: 0,
11
10
  batchOperation: 0,
12
11
  registerComputed: null,
13
- firstEffectToRun: null,
12
+ effectsToRun: [],
14
13
  firstSignalToUpdate: null,
14
+ lastSignalToUpdate: null,
15
15
  activeRegion: null,
16
16
  previousRegion: null,
17
17
  rootRegion: null,
18
+ awaitBoundary: null,
19
+ suspendPeek: false,
20
+ suspendPeekHit: false,
21
+ refreshSignals: null,
22
+ refreshInitialized: null,
18
23
  mountEffects: [],
19
24
  stashedEvents: [],
20
25
  stashedAnimations: [],
21
26
  hydrationNode: null
22
27
  };
23
- var context_default = context;
24
-
25
28
  //#endregion
26
29
  //#region src/render/addAnimation.ts
27
30
  function addAnimation(el, entry, exit) {
28
- context_default.stashedAnimations.push({
29
- region: context_default.activeRegion,
31
+ context.stashedAnimations.push({
32
+ region: context.activeRegion,
30
33
  el,
31
34
  in: entry,
32
35
  out: exit
33
36
  });
34
37
  }
35
-
36
38
  //#endregion
37
39
  //#region src/render/addEvent.ts
38
40
  function addEvent(el, type, listener) {
39
- if (listener !== void 0 && listener !== null) context_default.stashedEvents.push({
40
- region: context_default.activeRegion,
41
+ if (listener !== void 0 && listener !== null) context.stashedEvents.push({
41
42
  el,
42
43
  type,
43
44
  listener
44
45
  });
45
46
  }
46
-
47
- //#endregion
48
- //#region src/types/constants.ts
49
- /**
50
- * The type of a Signal object.
51
- */
52
- const SIGNAL_TYPE = 0;
53
- /**
54
- * The type of a Computed object.
55
- */
56
- const COMPUTED_TYPE = 1;
57
- /**
58
- * The type of an Effect object.
59
- */
60
- const EFFECT_TYPE = 2;
61
-
62
47
  //#endregion
63
48
  //#region src/watch/clearSources.ts
64
49
  /**
@@ -77,7 +62,6 @@ function clearSources(target) {
77
62
  previousSub = clearSub;
78
63
  }
79
64
  }
80
-
81
65
  //#endregion
82
66
  //#region src/watch/deactivateSources.ts
83
67
  /**
@@ -86,7 +70,6 @@ function clearSources(target) {
86
70
  function deactivateSources(target) {
87
71
  for (let sourceSub = target.firstSource; sourceSub !== null; sourceSub = sourceSub.nextSource) sourceSub.active = false;
88
72
  }
89
-
90
73
  //#endregion
91
74
  //#region src/watch/trackSignal.ts
92
75
  /**
@@ -94,60 +77,48 @@ function deactivateSources(target) {
94
77
  * run when the signal changes.
95
78
  */
96
79
  function trackSignal(signal) {
97
- const target = context_default.activeTarget;
80
+ const target = context.activeTarget;
98
81
  if (target !== null) {
99
- let sub = null;
100
- let lastSub = null;
101
- for (let nextSub = signal.firstTarget; nextSub !== null; nextSub = nextSub.nextTarget) {
102
- lastSub = nextSub;
103
- if (nextSub.target === target) {
104
- sub = nextSub;
105
- break;
106
- }
82
+ for (let existingSub = target.firstSource; existingSub !== null; existingSub = existingSub.nextSource) if (existingSub.source === signal) {
83
+ existingSub.active = true;
84
+ return;
107
85
  }
108
- if (sub === null) {
109
- sub = {
110
- source: signal,
111
- target,
112
- previousTarget: lastSub,
113
- nextTarget: null,
114
- nextSource: null,
115
- active: true,
116
- recalc: false
117
- };
118
- if (lastSub === null) signal.firstTarget = sub;
119
- else lastSub.nextTarget = sub;
120
- if (target.firstSource === null) target.firstSource = sub;
121
- else {
122
- let lastEffectSub = target.firstSource;
123
- for (let nextSub = lastEffectSub.nextSource; nextSub !== null; nextSub = nextSub.nextSource) lastEffectSub = nextSub;
124
- lastEffectSub.nextSource = sub;
125
- }
126
- } else sub.active = true;
86
+ const oldFirstTarget = signal.firstTarget;
87
+ const sub = {
88
+ source: signal,
89
+ target,
90
+ previousTarget: null,
91
+ nextTarget: oldFirstTarget,
92
+ nextSource: target.firstSource,
93
+ active: true,
94
+ recalc: false
95
+ };
96
+ if (oldFirstTarget !== null) oldFirstTarget.previousTarget = sub;
97
+ signal.firstTarget = sub;
98
+ target.firstSource = sub;
127
99
  }
128
100
  }
129
-
130
101
  //#endregion
131
102
  //#region src/watch/runComputed.ts
132
103
  function runComputed(computed) {
133
- const oldActiveTarget = context_default.activeTarget;
104
+ const oldActiveTarget = context.activeTarget;
134
105
  computed.running = true;
135
106
  computed.didError = false;
107
+ computed.didSuspend = false;
136
108
  try {
137
- context_default.activeTarget = computed;
109
+ context.activeTarget = computed;
138
110
  computed.value = computed.run();
139
111
  } catch (err) {
140
112
  computed.didError = true;
141
113
  computed.value = err;
142
114
  throw err;
143
115
  } finally {
144
- context_default.activeTarget = oldActiveTarget;
116
+ context.activeTarget = oldActiveTarget;
145
117
  computed.running = false;
146
118
  }
147
119
  trackSignal(computed);
148
120
  return computed.value;
149
121
  }
150
-
151
122
  //#endregion
152
123
  //#region src/watch/checkComputed.ts
153
124
  /**
@@ -171,7 +142,6 @@ function checkComputed(computed) {
171
142
  }
172
143
  return changed;
173
144
  }
174
-
175
145
  //#endregion
176
146
  //#region src/watch/checkSources.ts
177
147
  /**
@@ -180,21 +150,20 @@ function checkComputed(computed) {
180
150
  */
181
151
  function checkSources(target) {
182
152
  let haveComputed = false;
183
- for (let sourceSub = target.firstSource; sourceSub !== null; sourceSub = sourceSub.nextSource) if (sourceSub.recalc) if (sourceSub.source.type === SIGNAL_TYPE) return true;
153
+ for (let sourceSub = target.firstSource; sourceSub !== null; sourceSub = sourceSub.nextSource) if (sourceSub.recalc) if (sourceSub.source.type === 0) return true;
184
154
  else if (sourceSub.source.recalc) haveComputed = true;
185
155
  else return true;
186
156
  if (haveComputed) {
187
- for (let sourceSub = target.firstSource; sourceSub !== null; sourceSub = sourceSub.nextSource) if (sourceSub.recalc && sourceSub.source.type === COMPUTED_TYPE && sourceSub.source.recalc) {
188
- const oldActiveTarget = context_default.activeTarget;
189
- context_default.activeTarget = target;
157
+ for (let sourceSub = target.firstSource; sourceSub !== null; sourceSub = sourceSub.nextSource) if (sourceSub.recalc && sourceSub.source.type === 1 && sourceSub.source.recalc) {
158
+ const oldActiveTarget = context.activeTarget;
159
+ context.activeTarget = target;
190
160
  const recalc = checkComputed(sourceSub.source);
191
- context_default.activeTarget = oldActiveTarget;
161
+ context.activeTarget = oldActiveTarget;
192
162
  if (recalc) return true;
193
163
  }
194
164
  }
195
165
  return false;
196
166
  }
197
-
198
167
  //#endregion
199
168
  //#region src/watch/runCleanups.ts
200
169
  /**
@@ -203,9 +172,10 @@ function checkSources(target) {
203
172
  function runCleanups(effect) {
204
173
  let effectToClean = effect;
205
174
  for (let i = 0; i < effect.extent; i++) {
175
+ if (effectToClean === null) break;
206
176
  if (typeof effectToClean.cleanup === "function") {
207
- const oldActiveTarget = context_default.activeTarget;
208
- context_default.activeTarget = null;
177
+ const oldActiveTarget = context.activeTarget;
178
+ context.activeTarget = null;
209
179
  try {
210
180
  effectToClean.cleanup();
211
181
  } catch (err) {
@@ -214,18 +184,12 @@ function runCleanups(effect) {
214
184
  throw err;
215
185
  } finally {
216
186
  effectToClean.cleanup = void 0;
217
- context_default.activeTarget = oldActiveTarget;
187
+ context.activeTarget = oldActiveTarget;
218
188
  }
219
189
  }
220
- const nextEffect = effectToClean.nextEffect;
221
- if (i !== 0 && effect.nextEffectToRun === effectToClean) {
222
- effect.nextEffectToRun = effectToClean.nextEffectToRun;
223
- effectToClean.nextEffect = effectToClean.nextEffectToRun = null;
224
- }
225
- effectToClean = nextEffect;
190
+ effectToClean = effectToClean.nextEffect;
226
191
  }
227
192
  }
228
-
229
193
  //#endregion
230
194
  //#region src/watch/checkEffect.ts
231
195
  /**
@@ -240,130 +204,245 @@ function checkEffect(effect) {
240
204
  clearSources(effect);
241
205
  }
242
206
  }
243
-
244
207
  //#endregion
245
- //#region src/watch/clearTargets.ts
208
+ //#region src/watch/routeEffectError.ts
246
209
  /**
247
- * Clear unused target subscriptions after all Effects have been run.
210
+ * Routes an error thrown by an effect re-run to the nearest enclosing error
211
+ * boundary (`@try`/`@catch` or top-level `@error`), and forces the boundary's
212
+ * control effect to re-run so it renders its catch branch. Returns true if a
213
+ * boundary handled the error.
214
+ *
215
+ * The boundary is found by walking the flattened region chain from the
216
+ * effect's owning region (`Effect.region`, set by `$run`) through its
217
+ * ancestors (regions with strictly decreasing depth on the `previousRegion`
218
+ * chain — the same shape `clearRegion` walks).
219
+ *
220
+ * For recovery, the boundary holds the failing effect's source signals
221
+ * (captured by `runEffect` before it cleared them, `Effect.errorSources`)
222
+ * together with the boundary effect's own sources: reads wrapped in nested
223
+ * `$run` effects (text/attribute interpolation) are tracked by those
224
+ * effects, not the boundary, so without holding them the catch branch would
225
+ * never re-attempt the try branch. `runTry` re-subscribes the boundary
226
+ * effect to the held signals on every run that shows the catch branch.
227
+ *
228
+ * Called from `triggerEffects`' per-effect catch. If forcing the boundary
229
+ * re-run itself throws (e.g. the catch branch content errors), the error
230
+ * propagates to the caller, which reports it as unhandled — matching the
231
+ * compiled form, where a throwing catch branch escapes the boundary.
248
232
  */
249
- function clearTargets(source) {
250
- let previousSub = null;
251
- for (let clearSub = source.firstTarget; clearSub !== null; clearSub = clearSub.nextTarget) if (!clearSub.active) if (previousSub === null) source.firstTarget = clearSub.nextTarget;
252
- else previousSub.nextTarget = clearSub.nextTarget;
253
- else previousSub = clearSub;
233
+ function routeEffectError(effect, error) {
234
+ let region = effect.region ?? null;
235
+ while (region !== null) {
236
+ const boundary = region.errorBoundary;
237
+ if (boundary !== void 0 && boundary.effect !== null) {
238
+ const boundaryEffect = boundary.effect;
239
+ const held = [];
240
+ for (let sub = boundaryEffect.firstSource; sub !== null; sub = sub.nextSource) held.push(sub.source);
241
+ for (const signal of effect.errorSources ?? []) if (!held.includes(signal)) held.push(signal);
242
+ boundary.error = error;
243
+ boundary.hasError = true;
244
+ boundary.heldSignals = held;
245
+ runCleanups(boundaryEffect);
246
+ deactivateSources(boundaryEffect);
247
+ runEffect(boundaryEffect);
248
+ clearSources(boundaryEffect);
249
+ return true;
250
+ }
251
+ const depth = region.depth;
252
+ let previous = region.previousRegion;
253
+ while (previous !== null && previous.depth >= depth) previous = previous.previousRegion;
254
+ region = previous;
255
+ }
256
+ return false;
254
257
  }
255
-
256
258
  //#endregion
257
259
  //#region src/watch/triggerEffects.ts
258
260
  /**
261
+ * Re-subscribes a failed effect to the suspended computeds its run had read,
262
+ * after `runEffect`'s catch detached them via `clearSources`. Called when no
263
+ * error boundary handled the crash: the error still surfaces (see below),
264
+ * but the promise's resolve re-runs the effect, so a crash caused by a
265
+ * pending read's `undefined` heals instead of leaving a dead effect.
266
+ */
267
+ function resubscribeSuspendSources(effect) {
268
+ const sources = effect.suspendSources;
269
+ if (!sources || sources.size === 0) return;
270
+ const oldActive = context.activeTarget;
271
+ context.activeTarget = effect;
272
+ for (const signal of sources) trackSignal(signal);
273
+ context.activeTarget = oldActive;
274
+ }
275
+ /**
259
276
  * Runs the effects that have been collected during the batch.
260
277
  */
261
278
  function triggerEffects() {
262
- let didError = false;
263
- let error;
264
- let effect = context_default.firstEffectToRun;
265
- while (effect !== null) {
266
- context_default.batchOperation++;
279
+ let unhandledError = null;
280
+ const queue = context.effectsToRun;
281
+ for (let i = 0; i < queue.length; i++) {
282
+ const effect = queue[i];
283
+ context.batchOperation++;
284
+ effect.queued = false;
267
285
  try {
268
286
  checkEffect(effect);
269
287
  } catch (err) {
270
- if (!didError) {
271
- didError = true;
272
- error = err;
288
+ let handled = false;
289
+ try {
290
+ handled = routeEffectError(effect, err);
291
+ } catch (routingError) {
292
+ if (unhandledError === null) unhandledError = { error: routingError };
293
+ }
294
+ if (!handled) {
295
+ resubscribeSuspendSources(effect);
296
+ if (unhandledError === null) unhandledError = { error: err };
273
297
  }
274
298
  }
275
- effect = effect.nextEffectToRun;
276
299
  }
277
- effect = context_default.firstEffectToRun;
278
- context_default.firstEffectToRun = null;
279
- while (effect !== null) {
280
- let nextEffect = effect.nextEffectToRun;
281
- effect.nextEffectToRun = null;
282
- effect = nextEffect;
283
- }
284
- let signal = context_default.firstSignalToUpdate;
285
- context_default.firstSignalToUpdate = null;
300
+ for (let effect of queue) effect.queued = false;
301
+ context.effectsToRun = [];
302
+ let signal = context.firstSignalToUpdate;
303
+ context.firstSignalToUpdate = null;
304
+ context.lastSignalToUpdate = null;
286
305
  while (signal !== null) {
287
- clearTargets(signal);
288
306
  const nextSignal = signal.nextSignalToUpdate;
289
307
  signal.nextSignalToUpdate = null;
290
308
  signal = nextSignal;
291
309
  }
292
- if (didError) throw error;
310
+ if (unhandledError !== null) throw unhandledError.error;
293
311
  }
294
-
295
312
  //#endregion
296
313
  //#region src/watch/batchEnd.ts
297
314
  function batchEnd() {
298
- if (context_default.batch > 1) {
299
- context_default.batch--;
315
+ if (context.batch > 1) {
316
+ context.batch--;
300
317
  return;
301
318
  }
302
319
  try {
303
320
  triggerEffects();
304
321
  } finally {
305
- context_default.batch = 0;
306
- context_default.batchOperation = 0;
307
- context_default.previousEffect = null;
322
+ context.batch = 0;
323
+ context.batchOperation = 0;
324
+ context.previousEffect = null;
308
325
  }
309
326
  }
310
-
311
327
  //#endregion
312
328
  //#region src/watch/batchStart.ts
313
329
  function batchStart() {
314
- context_default.batch++;
330
+ context.batch++;
315
331
  }
316
-
317
332
  //#endregion
318
333
  //#region src/watch/runEffect.ts
319
334
  function runEffect(effect) {
320
- devContext_default.effectRun(effect);
321
- const oldActiveTarget = context_default.activeTarget;
322
- const oldExtent = context_default.extent;
335
+ devContext.effectRun(effect);
336
+ const oldActiveTarget = context.activeTarget;
337
+ const oldExtent = context.extent;
323
338
  effect.didError = false;
339
+ effect.errorSources = null;
340
+ effect.didSuspend = false;
341
+ effect.suspendSources = null;
324
342
  batchStart();
325
343
  try {
326
- if (context_default.previousEffect !== null) context_default.previousEffect.nextEffect = effect;
327
- context_default.activeTarget = effect;
328
- context_default.previousEffect = effect;
329
- context_default.extent = 1;
344
+ if (context.previousEffect !== null) context.previousEffect.nextEffect = effect;
345
+ context.activeTarget = effect;
346
+ context.previousEffect = effect;
347
+ context.extent = 1;
330
348
  effect.cleanup = effect.run();
331
- effect.extent = context_default.extent;
349
+ effect.extent = context.extent;
332
350
  } catch (err) {
333
351
  effect.didError = true;
352
+ let sources = null;
353
+ for (let sub = effect.firstSource; sub !== null; sub = sub.nextSource) (sources ??= []).push(sub.source);
354
+ effect.errorSources = sources;
334
355
  clearSources(effect);
335
356
  throw err;
336
357
  } finally {
337
- context_default.activeTarget = oldActiveTarget;
338
- context_default.extent += oldExtent - 1;
358
+ context.activeTarget = oldActiveTarget;
359
+ context.extent += oldExtent - 1;
339
360
  batchEnd();
340
361
  }
341
362
  }
342
-
343
363
  //#endregion
344
364
  //#region src/watch/$run.ts
345
365
  /**
346
366
  * Runs and re-runs a function that depends on a watched object
347
367
  *
348
368
  * @param fn The function to run, which may return a cleanup function.
369
+ * @param options Internal fields to set on the constructed Effect (e.g.
370
+ * `isMountEffect`, `forVarDeps`). Not part of the public API.
349
371
  */
350
- function $run(fn, name) {
372
+ function $run(fn, name, options) {
351
373
  let effect = {
352
- type: EFFECT_TYPE,
374
+ type: 2,
353
375
  run: fn,
354
376
  cleanup: void 0,
355
377
  firstSource: null,
356
378
  nextEffect: null,
357
379
  extent: 0,
358
- nextEffectToRun: null,
380
+ queued: false,
359
381
  didError: false,
360
- name
382
+ didSuspend: false,
383
+ region: null,
384
+ errorSources: null,
385
+ name,
386
+ isMountEffect: options?.isMountEffect,
387
+ forVarMask: options?.forVarMask
361
388
  };
362
- devContext_default.onRun(effect);
389
+ const region = context.activeRegion;
390
+ if (region !== null) {
391
+ region.effects.push(effect);
392
+ effect.region = region;
393
+ }
394
+ devContext.onRun(effect);
363
395
  runEffect(effect);
364
396
  return effect;
365
397
  }
366
-
398
+ //#endregion
399
+ //#region src/watch/$peek.ts
400
+ /**
401
+ * Runs a function without tracking the reactive values it reads: `$watch`'d
402
+ * state accessed inside `fn` does not become a dependency of the currently
403
+ * running effect or computed. Useful for reading state inside an effect
404
+ * without re-running it when that state changes.
405
+ *
406
+ * @param fn The function to run untracked.
407
+ */
408
+ function $peek(fn) {
409
+ const oldActiveTarget = context.activeTarget;
410
+ context.activeTarget = null;
411
+ const result = fn();
412
+ context.activeTarget = oldActiveTarget;
413
+ return result;
414
+ }
415
+ //#endregion
416
+ //#region src/watch/flushMountEffects.ts
417
+ /**
418
+ * Runs the collected `$onmount`/`onmount` callbacks — once, after the
419
+ * component's markup has been added to the DOM (or, when hydrating, after
420
+ * hydration is complete). Multiple callbacks run in the order they were
421
+ * added.
422
+ *
423
+ * Each callback becomes an effect with `isMountEffect` set, so the keyed-list
424
+ * reconciler's force-rerun path (`rerunRegionEffects`) skips it. Its body
425
+ * also runs untracked: mount callbacks are once-only, so reactive reads
426
+ * inside them do not subscribe. In dev builds a tracked pass warns when a
427
+ * callback reads reactive state, since that is almost always a mistake — the
428
+ * reactive part should be wrapped in `$run`.
429
+ */
430
+ function flushMountEffects() {
431
+ if (context.mountEffects.length === 0) return;
432
+ for (let fn of context.mountEffects) $run(() => runMountCallback(fn), void 0, { isMountEffect: true });
433
+ context.mountEffects.length = 0;
434
+ }
435
+ function runMountCallback(fn) {
436
+ if (!devContext.enabled) return $peek(fn);
437
+ const result = fn();
438
+ const effect = context.activeTarget;
439
+ if (effect !== null && effect.type === 2 && effect.firstSource !== null) {
440
+ console.warn("[torpor] A $onmount/onmount callback read reactive state, but mount callbacks run only once and will not re-run. Wrap the reactive part in $run(), e.g. $onmount(() => { $run(() => ...); });");
441
+ deactivateSources(effect);
442
+ clearSources(effect);
443
+ }
444
+ return result;
445
+ }
367
446
  //#endregion
368
447
  //#region src/render/animate.ts
369
448
  /**
@@ -384,76 +463,279 @@ async function animate(el, enter, keyframes, options) {
384
463
  setTimeout(() => {
385
464
  animation.timeline = el.ownerDocument.timeline;
386
465
  }, 1);
387
- const activeRegion = context_default.activeRegion;
466
+ const activeRegion = context.activeRegion;
388
467
  activeRegion.animations ??= /* @__PURE__ */ new Set();
389
468
  activeRegion.animations.add(animation);
390
469
  await animation.finished;
391
470
  }
392
-
471
+ //#endregion
472
+ //#region src/render/delegatedEvents.ts
473
+ /**
474
+ * Event delegation.
475
+ *
476
+ * For event types in `DELEGATED_EVENT_TYPES` (all natively bubbling), we
477
+ * register a single listener on `document` per type and dispatch by walking
478
+ * up from `event.target` to find the closest ancestor with a stashed handler.
479
+ * This replaces per-element `addEventListener` for delegated types.
480
+ *
481
+ * Why: in the js-framework-bench row template, every `<tr>` clones two `<a>`
482
+ * elements each with a `click` handler. With per-element `addEventListener`,
483
+ * `runlots` (10k rows) issues 20000 `addEventListener` calls and the browser
484
+ * allocates 20000 `Listener` objects. With delegation it's 2 calls total
485
+ * (one per delegated type, registered lazily on first use) and zero
486
+ * per-element `Listener` allocations — the handler is stored as a property
487
+ * on the element.
488
+ *
489
+ * Model: last-write-wins per element per type (same as Solid's
490
+ * `delegateEvents`). Repeated `t_event(el, "click", h)` calls with the same
491
+ * element reassign the property; calling with `null`/`undefined` clears it.
492
+ * Torpor's compiler emits `t_event(el, type, h)` exactly once per element
493
+ * lifetime (outside any tracked effect), so this is the natural shape.
494
+ *
495
+ * Non-bubbling events (`focus`, `blur`, `scroll`, `load`, `error`, `abort`,
496
+ * `unload`, `resize`, etc.) bypass delegation and use direct
497
+ * `addEventListener` on the element, preserving native semantics.
498
+ */
499
+ const DELEGATED_EVENT_TYPES = /* @__PURE__ */ new Set([
500
+ "auxclick",
501
+ "beforeinput",
502
+ "change",
503
+ "click",
504
+ "compositionend",
505
+ "compositionstart",
506
+ "compositionupdate",
507
+ "contextmenu",
508
+ "dblclick",
509
+ "drag",
510
+ "dragend",
511
+ "dragenter",
512
+ "dragleave",
513
+ "dragover",
514
+ "dragstart",
515
+ "drop",
516
+ "focusin",
517
+ "focusout",
518
+ "input",
519
+ "keydown",
520
+ "keypress",
521
+ "keyup",
522
+ "mousedown",
523
+ "mousemove",
524
+ "mouseout",
525
+ "mouseover",
526
+ "mouseup",
527
+ "pointerdown",
528
+ "pointermove",
529
+ "pointerout",
530
+ "pointerover",
531
+ "pointerup",
532
+ "reset",
533
+ "submit",
534
+ "touchend",
535
+ "touchmove",
536
+ "touchstart",
537
+ "wheel"
538
+ ]);
539
+ function isDelegatedEventType(type) {
540
+ return DELEGATED_EVENT_TYPES.has(type);
541
+ }
542
+ const DELEGATE_PREFIX = "torp.$$";
543
+ const DELEGATE_REGISTERED = Symbol.for("torp.delegated.registered");
544
+ function delegatedHandler(event) {
545
+ let node = event.target;
546
+ const stopAt = event.currentTarget;
547
+ const key = DELEGATE_PREFIX + event.type;
548
+ while (node != null && node !== stopAt) {
549
+ const handler = node[key];
550
+ if (handler !== void 0) {
551
+ Object.defineProperty(event, "currentTarget", {
552
+ configurable: true,
553
+ value: node,
554
+ writable: false
555
+ });
556
+ try {
557
+ handler.call(node, event);
558
+ } finally {
559
+ delete event.currentTarget;
560
+ }
561
+ return;
562
+ }
563
+ node = node.parentNode;
564
+ }
565
+ }
566
+ function ensureDelegatedListener(type) {
567
+ if (typeof document === "undefined") return;
568
+ let registered = document[DELEGATE_REGISTERED];
569
+ if (!registered) {
570
+ registered = /* @__PURE__ */ new Set();
571
+ document[DELEGATE_REGISTERED] = registered;
572
+ }
573
+ if (registered.has(type)) return;
574
+ registered.add(type);
575
+ document.addEventListener(type, delegatedHandler);
576
+ }
577
+ /**
578
+ * Attach a delegated event handler to an element.
579
+ *
580
+ * Stores the handler as a property on the element and ensures a single
581
+ * document-level listener exists for the type. Idempotent for the
582
+ * document-level listener: the first call for a given type registers it,
583
+ * subsequent calls are a Set-has check.
584
+ *
585
+ * Passing `null` or `undefined` as the listener clears any previously-set
586
+ * handler for that type on the element (mirroring `addEvent`'s existing
587
+ * null-skipping semantics, extended to support explicit removal).
588
+ */
589
+ function attachDelegatedEvent(el, type, listener) {
590
+ const key = DELEGATE_PREFIX + type;
591
+ if (listener === void 0 || listener === null) {
592
+ el[key] = void 0;
593
+ return;
594
+ }
595
+ ensureDelegatedListener(type);
596
+ el[key] = listener;
597
+ }
598
+ const DIRECT_LISTENERS = "torp.$directListeners";
599
+ /**
600
+ * Records a direct (non-delegated) listener on the element at attach time,
601
+ * so `copyEventListeners` can re-attach it after a dynamic-element tag
602
+ * swap. `addEventListener` listeners can't be discovered after the fact.
603
+ */
604
+ function recordDirectListener(el, type, listener) {
605
+ let list = el[DIRECT_LISTENERS];
606
+ if (!list) {
607
+ list = [];
608
+ el[DIRECT_LISTENERS] = list;
609
+ }
610
+ list.push({
611
+ type,
612
+ listener
613
+ });
614
+ }
615
+ /**
616
+ * Copies the event listeners attached to one element onto another — used by
617
+ * `t_dynamic` so a dynamic element keeps its listeners when its tag changes.
618
+ * Delegated handlers are `torp.$$<type>` properties, copied per known
619
+ * delegated type (swaps are rare, so the fixed set of checks is fine);
620
+ * direct listeners are re-registered from the list `recordDirectListener`
621
+ * built at attach time.
622
+ */
623
+ function copyEventListeners(from, to) {
624
+ for (const type of DELEGATED_EVENT_TYPES) {
625
+ const key = DELEGATE_PREFIX + type;
626
+ const handler = from[key];
627
+ if (handler !== void 0) to[key] = handler;
628
+ }
629
+ const direct = from[DIRECT_LISTENERS];
630
+ if (direct) {
631
+ to[DIRECT_LISTENERS] = direct;
632
+ for (const { type, listener } of direct) to.addEventListener(type, listener);
633
+ }
634
+ }
393
635
  //#endregion
394
636
  //#region src/render/isFragmentNode.ts
395
637
  function isFragmentNode(node) {
396
638
  return node.nodeType === 11;
397
639
  }
398
-
640
+ //#endregion
641
+ //#region src/render/runMountSideEffects.ts
642
+ /**
643
+ * Runs the mount-time side effects that both `addFragment` and `addElement`
644
+ * share: `$onmount` effects (only when not hydrating), stashed event
645
+ * listeners, and stashed animations. No-op when `parent` is itself a
646
+ * `DocumentFragment` (we're being inserted into a detached tree, so
647
+ * `$onmount` and event hookup would be premature — they'll run when the
648
+ * outer fragment attaches).
649
+ *
650
+ * Extracted so the `DocumentFragment` path (`addFragment`) and the
651
+ * single-element path (`addElement`) don't duplicate this logic.
652
+ */
653
+ function runMountSideEffects(parent, activeRegion, hydrationNode) {
654
+ if (isFragmentNode(parent)) return;
655
+ if (hydrationNode === null) flushMountEffects();
656
+ if (context.stashedEvents.length > 0) {
657
+ for (let event of context.stashedEvents) if (isDelegatedEventType(event.type)) attachDelegatedEvent(event.el, event.type, event.listener);
658
+ else {
659
+ event.el.addEventListener(event.type, event.listener);
660
+ recordDirectListener(event.el, event.type, event.listener);
661
+ }
662
+ context.stashedEvents.length = 0;
663
+ }
664
+ for (let animation of context.stashedAnimations) {
665
+ context.activeRegion = animation.region;
666
+ $run(function runMountSideEffectsAnimation() {
667
+ if (animation.in !== void 0) animate(animation.el, true, animation.in.keyframes, animation.in.options);
668
+ if (animation.out !== void 0) return () => {
669
+ animate(animation.el, false, animation.out.keyframes, animation.out.options);
670
+ };
671
+ });
672
+ }
673
+ context.stashedAnimations.length = 0;
674
+ context.activeRegion = activeRegion;
675
+ }
676
+ //#endregion
677
+ //#region src/render/addElement.ts
678
+ /**
679
+ * Companion to `addFragment` for the single-root-element codegen path. Wires
680
+ * a cloned root element (produced by `getElementFragment`) into the live DOM
681
+ * tree, sets the active region's start/end nodes, and runs the same
682
+ * mount-time side effects as `addFragment` (`$onmount` effects, stashed event
683
+ * listeners, stashed animations).
684
+ *
685
+ * Differences from `addFragment`:
686
+ * - `node` is both the start and end of the region (no
687
+ * `fragment.firstChild`/`fragment.lastChild` indirection).
688
+ * - When hydrating, the existing DOM node is reused (the cloned `node` is
689
+ * discarded); `endNode` is still `node` because `nodeRootElement` returns
690
+ * the hydration cursor's node in that case, not the cloned one.
691
+ *
692
+ * @param node The root element (cloned by `getElementFragment`, or the
693
+ * hydration cursor's node when hydrating).
694
+ * @param parent The intended parent element.
695
+ * @param before The sibling to insert before (or `null` to append).
696
+ */
697
+ function addElement(node, parent, before) {
698
+ const activeRegion = context.activeRegion;
699
+ const hydrationNode = context.hydrationNode;
700
+ activeRegion.startNode = node;
701
+ activeRegion.endNode = node;
702
+ parent = before?.parentNode ?? parent;
703
+ if (hydrationNode === null) parent.insertBefore(node, before);
704
+ runMountSideEffects(parent, activeRegion, hydrationNode);
705
+ }
399
706
  //#endregion
400
707
  //#region src/render/addFragment.ts
401
- function addFragment(fragment, parent, before, endNode) {
402
- const activeRegion = context_default.activeRegion;
403
- const hydrationNode = context_default.hydrationNode;
404
- if (hydrationNode !== null) activeRegion.endNode = endNode ?? hydrationNode;
405
- else {
708
+ function addFragment(fragment, parent, before, endNode, startNode) {
709
+ const activeRegion = context.activeRegion;
710
+ const hydrationNode = context.hydrationNode;
711
+ if (hydrationNode !== null) {
712
+ if (startNode !== void 0 && startNode.parentNode !== null) activeRegion.startNode = startNode;
713
+ activeRegion.endNode = endNode ?? hydrationNode;
714
+ } else {
406
715
  activeRegion.startNode = fragment.firstChild;
407
716
  activeRegion.endNode = fragment.lastChild;
408
717
  }
409
718
  parent = before?.parentNode ?? parent;
410
719
  if (hydrationNode === null) parent.insertBefore(fragment, before);
411
- if (!isFragmentNode(parent)) {
412
- if (hydrationNode === null) {
413
- for (let effect of context_default.mountEffects) $run(effect);
414
- context_default.mountEffects.length = 0;
415
- }
416
- if (context_default.stashedEvents.length > 0) {
417
- const events = [...context_default.stashedEvents];
418
- $run(function addFragmentEvents() {
419
- for (let event of events) event.el.addEventListener(event.type, event.listener);
420
- return () => {
421
- for (let event of events) event.el.removeEventListener(event.type, event.listener);
422
- };
423
- });
424
- context_default.stashedEvents.length = 0;
425
- }
426
- for (let animation of context_default.stashedAnimations) {
427
- context_default.activeRegion = animation.region;
428
- $run(function addFragmentAnimation() {
429
- if (animation.in !== void 0) animate(animation.el, true, animation.in.keyframes, animation.in.options);
430
- if (animation.out !== void 0) return () => {
431
- animate(animation.el, false, animation.out.keyframes, animation.out.options);
432
- };
433
- });
434
- }
435
- context_default.stashedAnimations.length = 0;
436
- context_default.activeRegion = activeRegion;
437
- }
720
+ runMountSideEffects(parent, activeRegion, hydrationNode);
438
721
  }
439
-
440
722
  //#endregion
441
723
  //#region src/render/applyProps.ts
442
724
  function applyProps(el, props, propNamesUsed) {
443
725
  if (props !== void 0) {
444
726
  for (let [name, value] of Object.entries(props)) if (!propNamesUsed.includes(name)) if (name.startsWith("on")) {
445
727
  const eventName = name.substring(2);
446
- el.addEventListener(eventName, value);
447
- } else $run(function setAttribute$1() {
728
+ if (isDelegatedEventType(eventName)) attachDelegatedEvent(el, eventName, value);
729
+ else el.addEventListener(eventName, value);
730
+ } else $run(function setAttribute() {
448
731
  el.setAttribute(name, value);
449
732
  });
450
733
  }
451
734
  }
452
-
453
735
  //#endregion
454
736
  //#region src/render/clearRegion.ts
455
737
  function clearRegion(region) {
456
- devContext_default.onRegionCleared(region);
738
+ devContext.onRegionCleared(region);
457
739
  let animations = region.animations !== null ? Array.from(region.animations) : void 0;
458
740
  let childRegion = region.nextRegion;
459
741
  while (childRegion !== null && childRegion.depth > region.depth) {
@@ -461,9 +743,10 @@ function clearRegion(region) {
461
743
  animations ??= [];
462
744
  animations.push(...childRegion.animations);
463
745
  }
746
+ const nextChild = childRegion.nextRegion;
464
747
  releaseRegion(childRegion);
465
748
  childRegion.depth = -2;
466
- childRegion = childRegion.nextRegion;
749
+ childRegion = nextChild;
467
750
  }
468
751
  region.nextRegion = childRegion;
469
752
  if (region.previousRegion !== null) region.previousRegion.nextRegion = region.nextRegion;
@@ -478,31 +761,83 @@ function clearRegion(region) {
478
761
  }
479
762
  function clearNodes(region) {
480
763
  if (region.startNode !== null && region.endNode !== null) {
764
+ const after = region.endNode.nextSibling;
765
+ let stop = region.startNode;
766
+ const startDetached = region.startNode.parentNode === null;
767
+ let fallback = false;
768
+ if (startDetached) {
769
+ let prev = region.previousRegion;
770
+ while (prev !== null && prev.depth > region.depth) prev = prev.previousRegion;
771
+ if (prev !== null && prev.endNode !== null && prev.endNode.parentNode !== null) {
772
+ stop = prev.endNode;
773
+ fallback = true;
774
+ }
775
+ }
481
776
  let currentNode = region.endNode;
482
- while (currentNode !== region.startNode) {
777
+ while (currentNode !== null && currentNode !== stop) {
483
778
  let previousNode = currentNode.previousSibling;
484
779
  currentNode.remove();
780
+ if (previousNode === null) break;
485
781
  currentNode = previousNode;
486
782
  }
487
- currentNode.remove();
783
+ if (!fallback && currentNode === region.startNode) currentNode.remove();
784
+ const firstRemaining = fallback ? stop : after;
785
+ if (firstRemaining !== null) {
786
+ let ancestor = region.previousRegion;
787
+ let ancestorDepth = region.depth;
788
+ while (ancestor !== null && ancestor.depth < ancestorDepth) {
789
+ if (ancestor.startNode !== null && ancestor.startNode.parentNode === null) ancestor.startNode = firstRemaining;
790
+ ancestorDepth = ancestor.depth;
791
+ ancestor = ancestor.previousRegion;
792
+ }
793
+ }
488
794
  }
489
795
  releaseRegion(region);
490
796
  }
491
797
  function releaseRegion(region) {
798
+ region.generation = -1;
492
799
  region.startNode = null;
493
800
  region.endNode = null;
494
801
  region.previousRegion = null;
495
802
  region.nextRegion = null;
496
803
  region.animations = null;
804
+ region.errorBoundary = void 0;
805
+ for (const effect of region.effects) {
806
+ if (typeof effect.cleanup === "function") {
807
+ effect.cleanup();
808
+ effect.cleanup = void 0;
809
+ }
810
+ let sub = effect.firstSource;
811
+ effect.firstSource = null;
812
+ while (sub !== null) {
813
+ const nextSource = sub.nextSource;
814
+ const prev = sub.previousTarget;
815
+ const next = sub.nextTarget;
816
+ if (prev === null) {
817
+ if (sub.source.firstTarget === sub) sub.source.firstTarget = next;
818
+ } else prev.nextTarget = next;
819
+ if (next !== null) next.previousTarget = prev;
820
+ sub.previousTarget = null;
821
+ sub.nextTarget = null;
822
+ sub.nextSource = null;
823
+ sub = nextSource;
824
+ }
825
+ }
826
+ region.effects.length = 0;
497
827
  }
498
-
499
828
  //#endregion
500
829
  //#region src/render/clearLayoutSlot.ts
830
+ /**
831
+ * Clears a region that was created by `fillLayoutSlot`: tears down the
832
+ * rendered content and its effects, leaving the surrounding layout intact
833
+ * so the slot can be refilled.
834
+ *
835
+ * @param region The region to clear
836
+ */
501
837
  function clearLayoutSlot(region) {
502
- context_default.previousRegion = region.previousRegion;
838
+ context.previousRegion = region.previousRegion;
503
839
  clearRegion(region);
504
840
  }
505
-
506
841
  //#endregion
507
842
  //#region src/render/newRegion.ts
508
843
  function newRegion(name) {
@@ -513,45 +848,58 @@ function newRegion(name) {
513
848
  nextRegion: null,
514
849
  depth: -1,
515
850
  animations: null,
516
- name
851
+ name,
852
+ effects: []
517
853
  };
518
854
  }
519
-
520
855
  //#endregion
521
856
  //#region src/render/popRegion.ts
522
857
  function popRegion(oldRegion) {
523
- context_default.activeRegion = oldRegion;
524
- devContext_default.onRegionPopped();
858
+ context.activeRegion = oldRegion;
859
+ devContext.onRegionPopped();
525
860
  }
526
-
527
861
  //#endregion
528
862
  //#region src/render/pushRegion.ts
529
863
  function pushRegion(region, toParent = false) {
530
- const activeRegion = context_default.activeRegion;
864
+ const activeRegion = context.activeRegion;
531
865
  if (toParent) {
532
- const previousRegion = context_default.previousRegion;
866
+ const previousRegion = context.previousRegion;
533
867
  region.previousRegion = previousRegion;
534
868
  const nextRegion = previousRegion.nextRegion;
535
869
  previousRegion.nextRegion = region;
536
870
  region.nextRegion = nextRegion;
871
+ if (nextRegion !== null) nextRegion.previousRegion = region;
537
872
  region.depth = activeRegion.depth + 1;
538
873
  }
539
- context_default.activeRegion = region;
540
- context_default.previousRegion = region;
541
- devContext_default.onRegionPushed(region, toParent);
874
+ context.activeRegion = region;
875
+ context.previousRegion = region;
876
+ devContext.onRegionPushed(region, toParent);
542
877
  return activeRegion;
543
878
  }
544
-
545
879
  //#endregion
546
880
  //#region src/render/fillLayoutSlot.ts
881
+ /**
882
+ * Renders a component into a persistent layout slot, and returns the region
883
+ * it was rendered into. Used by the layout engine during navigation: the
884
+ * component is created inside `parent` at `anchor`, with `slot` as its child
885
+ * slot render, and the returned region can later be passed to
886
+ * `clearLayoutSlot` to tear the page down — leaving the layout itself
887
+ * intact — before refilling the slot.
888
+ *
889
+ * @param component The component to render into the slot
890
+ * @param slot The slot render to pass to the component
891
+ * @param parent The parent node to render into
892
+ * @param anchor The node to insert content before, or null to append
893
+ * @param $props Optional props to pass to the component
894
+ * @param $context Optional context to pass to the component
895
+ */
547
896
  function fillLayoutSlot(component, slot, parent, anchor, $props, $context) {
548
- const region = newRegion(devContext_default.enabled ? "Layout slot" : void 0);
897
+ const region = newRegion(devContext.enabled ? "Layout slot" : void 0);
549
898
  const oldRegion = pushRegion(region, true);
550
899
  component(parent, anchor, $props, $context, { _: slot });
551
900
  popRegion(oldRegion);
552
901
  return region;
553
902
  }
554
-
555
903
  //#endregion
556
904
  //#region src/render/getFragment.ts
557
905
  function getFragment(document, array, index, html, ns) {
@@ -568,7 +916,44 @@ function getFragment(document, array, index, html, ns) {
568
916
  }
569
917
  return array[index].cloneNode(true);
570
918
  }
571
-
919
+ //#endregion
920
+ //#region src/render/getElementFragment.ts
921
+ /**
922
+ * Returns a clone of the cached template's single root element, bypassing the
923
+ * `DocumentFragment` wrapper that `getFragment` produces.
924
+ *
925
+ * Used by the compiler-emitted `createListItem` for `@for` bodies (and other
926
+ * fragments) whose template has exactly one rendering root child — e.g.
927
+ * `<tr><td>...</td>...</tr>`. Cloning `template.content.firstElementChild`
928
+ * directly saves the per-row `DocumentFragment` allocation that `getFragment`'s
929
+ * `cloneNode(true)` on the cached template's content would otherwise produce,
930
+ * which dominates the cost of bulk-row creates (`run`/`add`/`runlots`).
931
+ *
932
+ * Contract: the compiler only emits `t_fragment_el` for fragment indices whose
933
+ * template has exactly one rendering root, and that root is an `Element`. The
934
+ * cache array (`t_fragment_els`) is separate from `t_fragments` so each helper
935
+ * only ever sees its own cached type at a given index.
936
+ *
937
+ * @param document The owner document (used to build the cached `<template>`).
938
+ * @param array The per-component `t_fragment_els` cache.
939
+ * @param index The fragment's index in the cache.
940
+ * @param html The fragment's HTML source, with `#` placeholders for reactive
941
+ * text and `<!>` for anchor comments.
942
+ * @param ns When true, the template is created in the SVG namespace (its
943
+ * `firstElementChild` is then an SVG element).
944
+ */
945
+ function getElementFragment(document, array, index, html, ns) {
946
+ if (array[index] === void 0) if (ns === true) {
947
+ const template = document.createElementNS("http://www.w3.org/2000/svg", "template");
948
+ template.innerHTML = html;
949
+ array[index] = template.firstElementChild;
950
+ } else {
951
+ const template = document.createElement("template");
952
+ template.innerHTML = html;
953
+ array[index] = template.content.firstElementChild;
954
+ }
955
+ return array[index].cloneNode(true);
956
+ }
572
957
  //#endregion
573
958
  //#region src/render/hydrate.ts
574
959
  /**
@@ -578,16 +963,14 @@ function getFragment(document, array, index, html, ns) {
578
963
  * @param props An object containing component props
579
964
  */
580
965
  function hydrate(parent, component, props, slots) {
581
- context_default.hydrationNode = parent.firstChild;
582
- context_default.rootRegion = newRegion(devContext_default.enabled ? "Root" : void 0);
583
- context_default.previousRegion = context_default.rootRegion;
584
- pushRegion(context_default.rootRegion);
966
+ context.hydrationNode = parent.firstChild;
967
+ context.rootRegion = newRegion(devContext.enabled ? "Root" : void 0);
968
+ context.previousRegion = context.rootRegion;
969
+ pushRegion(context.rootRegion);
585
970
  component(parent, null, props, void 0, slots);
586
- context_default.hydrationNode = null;
587
- for (let effect of context_default.mountEffects) $run(effect);
588
- context_default.mountEffects.length = 0;
971
+ context.hydrationNode = null;
972
+ flushMountEffects();
589
973
  }
590
-
591
974
  //#endregion
592
975
  //#region src/render/mount.ts
593
976
  /**
@@ -600,14 +983,36 @@ function hydrate(parent, component, props, slots) {
600
983
  function mount(parent, component, props, slots) {
601
984
  if (parent.childElementCount > 0) throw new Error("The parent node must have no child elements");
602
985
  while (parent.firstChild !== null) parent.firstChild.remove();
603
- if (context_default.rootRegion === null) {
604
- context_default.rootRegion = newRegion(devContext_default.enabled ? "Root" : void 0);
605
- context_default.previousRegion = context_default.rootRegion;
606
- pushRegion(context_default.rootRegion);
986
+ if (context.rootRegion === null) {
987
+ context.rootRegion = newRegion(devContext.enabled ? "Root" : void 0);
988
+ context.previousRegion = context.rootRegion;
989
+ pushRegion(context.rootRegion);
607
990
  }
608
991
  component(parent, null, props, void 0, slots);
609
992
  }
610
-
993
+ //#endregion
994
+ //#region src/render/unmount.ts
995
+ /**
996
+ * Tears down the current UI: disposes the root region tree (running effect
997
+ * cleanups and detaching subscriptions), resets the render context, and
998
+ * removes any remaining child nodes from `parent`.
999
+ *
1000
+ * Call this before `mount`ing a fresh component tree into the same container
1001
+ * (e.g. when client-side navigating to a route whose layout chain differs
1002
+ * from the previous one). `mount` refuses to mount into a non-empty parent
1003
+ * and reuses an existing root region, so both must be cleared first.
1004
+ *
1005
+ * @param parent The container that the previous UI was mounted into
1006
+ */
1007
+ function unmount(parent) {
1008
+ if (context.rootRegion !== null) {
1009
+ clearRegion(context.rootRegion);
1010
+ context.rootRegion = null;
1011
+ context.previousRegion = null;
1012
+ context.activeRegion = null;
1013
+ }
1014
+ while (parent.firstChild !== null) parent.firstChild.remove();
1015
+ }
611
1016
  //#endregion
612
1017
  //#region src/render/newListItem.ts
613
1018
  function newListItem(data, key, name) {
@@ -620,28 +1025,20 @@ function newListItem(data, key, name) {
620
1025
  animations: null,
621
1026
  name,
622
1027
  data,
623
- key
1028
+ key,
1029
+ effects: []
624
1030
  };
625
1031
  }
626
-
627
- //#endregion
628
- //#region src/render/hydrationMarkers.ts
629
- const HYDRATION_START = "[";
630
- const HYDRATION_END = "]";
631
- const HYDRATION_BREAK = "^";
632
-
633
1032
  //#endregion
634
1033
  //#region src/render/isCommentNode.ts
635
1034
  function isCommentNode(node) {
636
1035
  return node.nodeType === 8;
637
1036
  }
638
-
639
1037
  //#endregion
640
1038
  //#region src/render/isTextNode.ts
641
1039
  function isTextNode(node) {
642
1040
  return node.nodeType === 3;
643
1041
  }
644
-
645
1042
  //#endregion
646
1043
  //#region src/render/nodeCheckHydrationBreak.ts
647
1044
  /**
@@ -653,17 +1050,14 @@ function isTextNode(node) {
653
1050
  * @returns The next node.
654
1051
  */
655
1052
  function nodeCheckHydrationBreak(node) {
656
- if (context_default.hydrationNode !== null) {
657
- if (node !== null && isCommentNode(node) && node.data === HYDRATION_BREAK) {
658
- let comment = node;
659
- node = node.nextSibling;
660
- comment.remove();
661
- }
662
- context_default.hydrationNode = node;
1053
+ if (node !== null && isCommentNode(node) && node.data === "^") {
1054
+ let comment = node;
1055
+ node = node.nextSibling;
1056
+ comment.remove();
663
1057
  }
1058
+ context.hydrationNode = node;
664
1059
  return node;
665
1060
  }
666
-
667
1061
  //#endregion
668
1062
  //#region src/render/nodeNext.ts
669
1063
  /**
@@ -675,10 +1069,18 @@ function nodeCheckHydrationBreak(node) {
675
1069
  * @param text Whether we require a text node.
676
1070
  */
677
1071
  function nodeNext(node, text = false) {
678
- if (context_default.hydrationNode !== null && text && isTextNode(node)) return node;
679
- return nodeCheckHydrationBreak(node.nextSibling);
1072
+ if (context.hydrationNode !== null) {
1073
+ if (text && isTextNode(node)) return node;
1074
+ let next = nodeCheckHydrationBreak(node.nextSibling);
1075
+ while (!text && next !== null && isTextNode(next) && next.textContent?.trim() === "") next = nodeCheckHydrationBreak(next.nextSibling);
1076
+ if (!text && next !== null && next.nodeName === "TBODY" && next.firstChild && isTextNode(node)) {
1077
+ next = next.firstChild;
1078
+ context.hydrationNode = next;
1079
+ }
1080
+ return next;
1081
+ }
1082
+ return node.nextSibling;
680
1083
  }
681
-
682
1084
  //#endregion
683
1085
  //#region src/render/nodeAnchor.ts
684
1086
  /**
@@ -696,32 +1098,42 @@ function nodeNext(node, text = false) {
696
1098
  * @returns The anchor node.
697
1099
  */
698
1100
  function nodeAnchor(node) {
699
- if (context_default.hydrationNode !== null) {
700
- if (isCommentNode(node) && node.data === HYDRATION_START) {
1101
+ if (context.hydrationNode !== null) {
1102
+ if (isCommentNode(node) && node.data === "[") {
1103
+ const startParent = node.parentNode;
701
1104
  let currentNode = nodeNext(node);
1105
+ const firstInside = currentNode;
702
1106
  node.remove();
703
1107
  let level = 1;
704
1108
  while (currentNode !== null) {
705
1109
  if (isCommentNode(currentNode)) {
706
- if (currentNode.data === HYDRATION_START) level += 1;
707
- else if (currentNode.data === HYDRATION_END) {
1110
+ if (currentNode.data === "[") level += 1;
1111
+ else if (currentNode.data === "]") {
708
1112
  level -= 1;
709
1113
  if (level === 0) {
710
1114
  const endNode = currentNode.nextSibling;
711
1115
  currentNode.remove();
712
- if (context_default.hydrationNode === currentNode) context_default.hydrationNode = endNode;
1116
+ if (context.hydrationNode === currentNode) context.hydrationNode = endNode;
1117
+ const region = context.activeRegion;
1118
+ if (region.startNode === null && firstInside !== null) region.startNode = firstInside;
713
1119
  return endNode;
714
1120
  }
715
1121
  }
716
1122
  }
717
- currentNode = currentNode.nextSibling;
1123
+ if (currentNode.firstChild) currentNode = currentNode.firstChild;
1124
+ else if (currentNode.nextSibling) currentNode = currentNode.nextSibling;
1125
+ else {
1126
+ let parent = currentNode.parentNode;
1127
+ while (parent !== null && parent.nextSibling === null && parent !== startParent) parent = parent.parentNode;
1128
+ if (parent === null || parent === startParent) currentNode = null;
1129
+ else currentNode = parent.nextSibling;
1130
+ }
718
1131
  }
719
1132
  if (currentNode === null) throw new Error("End hydration comment not found");
720
1133
  }
721
1134
  }
722
1135
  return node;
723
1136
  }
724
-
725
1137
  //#endregion
726
1138
  //#region src/render/nodeChild.ts
727
1139
  /**
@@ -733,40 +1145,104 @@ function nodeAnchor(node) {
733
1145
  */
734
1146
  function nodeChild(parent) {
735
1147
  let childNode = parent.firstChild;
736
- if (context_default.hydrationNode !== null) {
1148
+ if (context.hydrationNode !== null) {
737
1149
  if (childNode === null) childNode = parent.appendChild(parent.ownerDocument.createTextNode(""));
738
- context_default.hydrationNode = childNode;
1150
+ context.hydrationNode = childNode;
739
1151
  }
740
1152
  return childNode;
741
1153
  }
742
-
1154
+ //#endregion
1155
+ //#region src/render/walkHydrationRoot.ts
1156
+ /**
1157
+ * Walks the hydration cursor past skippable leading nodes (whitespace text,
1158
+ * branch-break markers, auto-inserted `<tbody>`) and lands on the first real
1159
+ * content node — the same walk `nodeRoot` performs for the non-text case.
1160
+ *
1161
+ * Sets `region.startNode` to the landed node (unless it's a control-start
1162
+ * marker, which `nodeAnchor` consumes next). Returns the landed node and
1163
+ * advances `context.hydrationNode` to it.
1164
+ *
1165
+ * Extracted so `nodeRoot` and `nodeRootElement` share one implementation of
1166
+ * the cursor walk.
1167
+ */
1168
+ function walkHydrationRoot() {
1169
+ let rootNode = context.hydrationNode;
1170
+ while (rootNode !== null && isSkippable(rootNode)) {
1171
+ const next = rootNode.nextSibling;
1172
+ if (isCommentNode(rootNode) && rootNode.data === "^") rootNode.remove();
1173
+ rootNode = next;
1174
+ context.hydrationNode = rootNode;
1175
+ }
1176
+ while (rootNode !== null && rootNode.nodeName === "TBODY" && rootNode.firstChild !== null) {
1177
+ rootNode = rootNode.firstChild;
1178
+ context.hydrationNode = rootNode;
1179
+ }
1180
+ const region = context.activeRegion;
1181
+ const isControlStart = rootNode !== null && isCommentNode(rootNode) && rootNode.data === "[";
1182
+ if (region.startNode === null && !isControlStart) region.startNode = rootNode;
1183
+ return rootNode;
1184
+ }
1185
+ function isSkippable(node) {
1186
+ if (isTextNode(node) && (node.textContent ?? "").trim() === "") return true;
1187
+ if (isCommentNode(node)) return node.data === "^" || node.data === "";
1188
+ return false;
1189
+ }
1190
+ //#endregion
1191
+ //#region src/render/nodeRootElement.ts
1192
+ /**
1193
+ * Gets the root element of a single-root fragment built via `getElementFragment`.
1194
+ *
1195
+ * Companion to `nodeRoot`, for the codegen path where the compiler has emitted
1196
+ * `t_fragment_el` (cloning the cached template's `firstElementChild` directly)
1197
+ * instead of `t_fragment` (cloning into a `DocumentFragment`). In the
1198
+ * non-hydrating case the cloned element is already the root, so this is a
1199
+ * no-op pass-through. In the hydrating case we perform the same cursor walk as
1200
+ * `nodeRoot`'s non-text branch so the region's `startNode` lands on the
1201
+ * existing DOM node and the hydration cursor advances past any leading
1202
+ * whitespace, branch-break markers, and auto-inserted `<tbody>` wrappers.
1203
+ *
1204
+ * @param node The cloned root element (ignored when hydrating).
1205
+ */
1206
+ function nodeRootElement(node) {
1207
+ if (context.hydrationNode !== null) return walkHydrationRoot();
1208
+ return node;
1209
+ }
743
1210
  //#endregion
744
1211
  //#region src/render/nodeRoot.ts
745
1212
  /**
746
1213
  * Gets the first child in a fragment.
747
1214
  *
748
- * When hydrating, also sets the active range's start node, while we have it.
1215
+ * When hydrating, also sets the active region's start node, while we have it.
1216
+ *
1217
+ * If the hydration cursor is on a control-start marker (`<![>`), the node is
1218
+ * left in place for `nodeAnchor` (called next as `t_anchor(t_root(...))`) to
1219
+ * walk — it consumes the matched `<![>...<!]>` pair, removes the markers and
1220
+ * sets the region's start node to the first node inside the block.
1221
+ *
1222
+ * For non-text fragments, leading branch-break markers (`<!^>`), empty anchor
1223
+ * comments left by preceding siblings, and residual whitespace-only text nodes
1224
+ * are skipped (break markers removed) so the region's start node lands on real
1225
+ * content. This replaces the per-sibling `t_next` calls that the codegen used
1226
+ * to emit when leading whitespace separated siblings; with whitespace trimmed
1227
+ * those calls are gone, so the advancement happens here.
749
1228
  *
750
1229
  * @param parent The parent of the fragment.
751
1230
  * @param text Whether we require a text node.
752
1231
  */
753
1232
  function nodeRoot(parent, text = false) {
754
- if (context_default.hydrationNode !== null) {
755
- let rootNode = context_default.hydrationNode;
1233
+ if (context.hydrationNode !== null) {
1234
+ let rootNode = context.hydrationNode;
756
1235
  if (text && !isTextNode(rootNode) && rootNode.previousSibling !== null && isTextNode(rootNode.previousSibling)) {
757
1236
  rootNode = rootNode.previousSibling;
758
- context_default.hydrationNode = rootNode;
759
- }
760
- if (isCommentNode(rootNode) && rootNode.data === HYDRATION_START) {
761
- rootNode = rootNode.nextSibling;
762
- context_default.hydrationNode = rootNode;
1237
+ context.hydrationNode = rootNode;
763
1238
  }
764
- const region = context_default.activeRegion;
765
- if (region.startNode === null) region.startNode = rootNode;
1239
+ if (!text) return walkHydrationRoot();
1240
+ const region = context.activeRegion;
1241
+ const isControlStart = rootNode !== null && isCommentNode(rootNode) && rootNode.data === "[";
1242
+ if (region.startNode === null && !isControlStart) region.startNode = rootNode;
766
1243
  return rootNode;
767
1244
  } else return parent.firstChild;
768
1245
  }
769
-
770
1246
  //#endregion
771
1247
  //#region src/render/nodeSkip.ts
772
1248
  /**
@@ -779,10 +1255,108 @@ function nodeRoot(parent, text = false) {
779
1255
  */
780
1256
  function nodeSkip(node, count) {
781
1257
  let nextNode = node;
782
- for (let i = 0; i < count; i++) nextNode = nodeCheckHydrationBreak(nextNode.nextSibling);
1258
+ if (context.hydrationNode !== null) for (let i = 0; i < count; i++) nextNode = nodeCheckHydrationBreak(nextNode.nextSibling);
1259
+ else for (let i = 0; i < count; i++) nextNode = nextNode.nextSibling;
783
1260
  return nextNode;
784
1261
  }
785
-
1262
+ //#endregion
1263
+ //#region src/render/rerunRegionEffects.ts
1264
+ /**
1265
+ * Force re-runs every effect owned by `region` and its descendant regions
1266
+ * that depends on at least one of the for-vars set in `changedMask`.
1267
+ *
1268
+ * Used by keyed-list `updateListItem` callbacks emitted by the compiler when
1269
+ * the `@for` body has been classified "no-proxy safe" — i.e. the per-item
1270
+ * data bag is *not* wrapped in a shallow `$watch` Proxy. Without that Proxy,
1271
+ * a write such as `t_old_item.data.row = newRow` doesn't propagate through a
1272
+ * signal, so any effects that read `data.row` must be re-run manually.
1273
+ *
1274
+ * `changedMask` is a bitmask of the for-var positions whose references
1275
+ * actually changed in this update. Each effect carries `forVarMask`
1276
+ * (computed at compile time) listing the for-vars its body reads; effects
1277
+ * whose `forVarMask & changedMask === 0` are skipped. Mount callbacks
1278
+ * (`isMountEffect` — `$onmount`/`onmount`) are always skipped: they run
1279
+ * once per DOM mount and their bodies are untracked.
1280
+ *
1281
+ * Equivalent to what `checkEffect` does when a signal has propagated, but
1282
+ * unconditional for the effects that do match: cleanup → deactivate sources
1283
+ * → run → clear unused sources, for every matching effect on the region and
1284
+ * on every descendant region (`depth > region.depth`, the same chain shape
1285
+ * `clearRegion` walks).
1286
+ */
1287
+ function rerunRegionEffects(region, changedMask) {
1288
+ rerunEffectsOnRegion(region, changedMask);
1289
+ let child = region.nextRegion;
1290
+ while (child !== null && child.depth > region.depth) {
1291
+ rerunEffectsOnRegion(child, changedMask);
1292
+ child = child.nextRegion;
1293
+ }
1294
+ }
1295
+ function rerunEffectsOnRegion(region, changedMask) {
1296
+ for (let effect of region.effects) {
1297
+ if (effect.isMountEffect) continue;
1298
+ if (effect.forVarMask !== void 0 && (effect.forVarMask & changedMask) === 0) continue;
1299
+ runCleanups(effect);
1300
+ deactivateSources(effect);
1301
+ runEffect(effect);
1302
+ clearSources(effect);
1303
+ }
1304
+ }
1305
+ //#endregion
1306
+ //#region src/render/restoreHydration.ts
1307
+ /**
1308
+ * Restores the hydration cursor to a previously-snapshotted node. Used by
1309
+ * `@try`/`@catch`: if the try branch throws partway through its hydration
1310
+ * walk, the catch branch must resume hydrating from where the try group
1311
+ * started, not from wherever the failed branch left the cursor.
1312
+ *
1313
+ * The cursor is set unconditionally: a failed try-branch build can walk the
1314
+ * cursor past the last node (setting it to `null` via `nodeNext`), and the
1315
+ * rewind must still restore it — otherwise the catch branch would think it
1316
+ * isn't hydrating and insert fresh nodes alongside the server's. When not
1317
+ * hydrating at all, the snapshot itself is `null`, so this is a no-op.
1318
+ *
1319
+ * @param node The snapshotted cursor node (from `saveHydration`).
1320
+ */
1321
+ function restoreHydration(node) {
1322
+ context.hydrationNode = node;
1323
+ }
1324
+ //#endregion
1325
+ //#region src/render/widenAncestorsAtAnchor.ts
1326
+ /**
1327
+ * Widens ancestor region node windows after a control re-run rendered
1328
+ * content at its anchor.
1329
+ *
1330
+ * A region that owns only a control's anchor comment captures
1331
+ * `startNode = endNode = anchor` when it mounts (e.g. a `@for` row whose
1332
+ * body is a single `@if`, or an outer `@if` whose body is a single control).
1333
+ * Content rendered by a LATER re-run of that control is inserted into the
1334
+ * live DOM just BEFORE the anchor — outside that window — so clearing the
1335
+ * owning region would remove only the anchor and orphan the branch content.
1336
+ * At mount time this can't happen (content is added to the region's detached
1337
+ * fragment first, so its `startNode` already covers it), which is why only
1338
+ * re-runs need this fix.
1339
+ *
1340
+ * The first node of the newly rendered content is the first DESCENDANT region
1341
+ * of the control that owns nodes (control regions themselves have null
1342
+ * `startNode`). Ancestors are the regions with strictly decreasing depth on
1343
+ * the `previousRegion` chain; only those whose window starts exactly at the
1344
+ * anchor are widened.
1345
+ */
1346
+ function widenAncestorsAtAnchor(region, anchor) {
1347
+ if (anchor === null) return;
1348
+ let child = region.nextRegion;
1349
+ while (child !== null && child.depth > region.depth && child.startNode === null) child = child.nextRegion;
1350
+ if (child === null || child.depth <= region.depth || child.startNode === anchor) return;
1351
+ const firstNode = child.startNode;
1352
+ let ancestor = region.previousRegion;
1353
+ let ancestorDepth = region.depth;
1354
+ while (ancestor !== null && ancestor.depth < ancestorDepth) {
1355
+ if (ancestor.startNode === anchor) ancestor.startNode = firstNode;
1356
+ ancestorDepth = ancestor.depth;
1357
+ ancestor = ancestor.previousRegion;
1358
+ }
1359
+ }
786
1360
  //#endregion
787
1361
  //#region src/render/runControl.ts
788
1362
  /**
@@ -791,92 +1365,136 @@ function nodeSkip(node, count) {
791
1365
  */
792
1366
  function runControl(region, anchor, create, name) {
793
1367
  let first = true;
794
- $run(function runControl$1() {
1368
+ const gen = region.generation = (region.generation ?? 0) + 1;
1369
+ $run(function runControl() {
1370
+ if (region.generation !== gen) return;
1371
+ if (region.depth === -2) region.recreate = true;
795
1372
  const oldRegion = pushRegion(region, first);
796
1373
  first = false;
797
1374
  create(anchor);
798
1375
  popRegion(oldRegion);
1376
+ widenAncestorsAtAnchor(region, anchor);
1377
+ if (context.hydrationNode !== null && anchor !== null) context.hydrationNode = anchor;
799
1378
  }, name);
800
- if (context_default.hydrationNode) anchor = context_default.hydrationNode.nextSibling;
801
1379
  }
802
-
803
1380
  //#endregion
804
1381
  //#region src/render/runControlBranch.ts
805
1382
  function runControlBranch(region, oldIndex, index) {
806
- if (oldIndex === index) return false;
807
- if (region.depth === -2) return false;
808
- if (region.nextRegion !== null && region.nextRegion.depth > region.depth) clearRegion(region.nextRegion);
1383
+ const recreate = region.recreate;
1384
+ if (recreate) region.recreate = false;
1385
+ const firstRender = region.hasRenderedBranch !== true;
1386
+ region.hasRenderedBranch = true;
1387
+ if (oldIndex === index) {
1388
+ if (recreate) {
1389
+ if (region.nextRegion !== null && region.nextRegion.depth > region.depth) clearRegion(region.nextRegion);
1390
+ return true;
1391
+ }
1392
+ return false;
1393
+ }
1394
+ if (!firstRender && region.nextRegion !== null && region.nextRegion.depth > region.depth) clearRegion(region.nextRegion);
809
1395
  return true;
810
1396
  }
811
-
1397
+ //#endregion
1398
+ //#region src/watch/deepWrap.ts
1399
+ /**
1400
+ * Wraps a value with `$watch` if it is a plain object (or Date/Map/Set) that
1401
+ * is not already watched and is not a promise. Returns the value unchanged
1402
+ * otherwise.
1403
+ *
1404
+ * Used by the `get` trap (wrap values on read) and the `set` trap (wrap
1405
+ * values on write), so a value stored in watched state is reactive whether
1406
+ * it's been read yet or not.
1407
+ */
1408
+ const proxyCache = /* @__PURE__ */ new WeakMap();
1409
+ function deepWrap(value) {
1410
+ if (value === void 0 || value === null || typeof value !== "object") return value;
1411
+ if (value[proxyDataSymbol] !== void 0) return proxyCache.get(value) ?? value;
1412
+ if (value.then !== void 0) return value;
1413
+ if (value.nodeType !== void 0) return value;
1414
+ const cached = proxyCache.get(value);
1415
+ if (cached !== void 0) return cached;
1416
+ const proxy = $watch(value);
1417
+ proxyCache.set(value, proxy);
1418
+ return proxy;
1419
+ }
812
1420
  //#endregion
813
1421
  //#region src/watch/propagateSignal.ts
814
1422
  /**
815
- * When a signal is changed, we need to
816
- * - Set the signal's subscriptions to recalc
817
- * - Set any dependent computed values to recalc
818
- * - Gather dependent effects that will need to be re-run
1423
+ * Walk the target graph from a signal source (ProxySignal or Computed),
1424
+ * marking each dependent as needing recalculation and queuing effects for
1425
+ * re-run. Shared between `propagateSignal` (for proxy writes) and `$async`'s
1426
+ * promise-resolve handler (for async value resolution).
1427
+ *
1428
+ * Must be called inside `batchStart`/`batchEnd` — callers own the batch.
819
1429
  */
820
- function propagateSignal(proxy, key) {
821
- let signal = proxy.signals.get(key);
1430
+ function propagateFromSignal(signal) {
1431
+ const first = signal.firstTarget;
1432
+ if (first === null) return;
1433
+ let targetSub = first;
1434
+ let computed;
1435
+ outer: while (true) {
1436
+ targetSub.active = false;
1437
+ targetSub.recalc = true;
1438
+ const target = targetSub.target;
1439
+ if (target.type === 2) {
1440
+ if (!target.queued) {
1441
+ target.queued = true;
1442
+ context.effectsToRun.push(target);
1443
+ }
1444
+ } else if (!target.recalc) {
1445
+ target.recalc = true;
1446
+ if (target.firstTarget !== null) {
1447
+ computed = target;
1448
+ computed.rollback = targetSub;
1449
+ targetSub = target.firstTarget;
1450
+ continue;
1451
+ }
1452
+ }
1453
+ while (targetSub.nextTarget === null && computed !== void 0) {
1454
+ const rollback = computed.rollback;
1455
+ if (rollback === null) break;
1456
+ targetSub = rollback;
1457
+ if (targetSub.source.type !== 1) {
1458
+ computed = void 0;
1459
+ continue outer;
1460
+ }
1461
+ computed = targetSub.source;
1462
+ }
1463
+ if (targetSub.nextTarget === null) break;
1464
+ targetSub = targetSub.nextTarget;
1465
+ }
1466
+ }
1467
+ /**
1468
+ * When a signal is changed, we need to
1469
+ * - Set the signal's subscriptions to recalc
1470
+ * - Set any dependent computed values to recalc
1471
+ * - Gather dependent effects that will need to be re-run
1472
+ */
1473
+ function propagateSignal(proxy, key) {
1474
+ let signal = proxy.signals.get(key);
822
1475
  if (signal !== void 0 && signal.firstTarget !== null) {
823
1476
  batchStart();
824
1477
  try {
825
- if (context_default.firstSignalToUpdate === null) context_default.firstSignalToUpdate = signal;
826
- else if (signal.nextSignalToUpdate === null) {
827
- let lastSignal = context_default.firstSignalToUpdate;
828
- while (lastSignal.nextSignalToUpdate !== null) lastSignal = lastSignal.nextSignalToUpdate;
829
- lastSignal.nextSignalToUpdate = signal;
830
- }
831
- let lastEffect = context_default.firstEffectToRun;
832
- if (lastEffect !== null) while (lastEffect.nextEffectToRun !== null) lastEffect = lastEffect.nextEffectToRun;
833
- let targetSub = signal.firstTarget;
834
- let computed;
835
- outer: while (true) {
836
- targetSub.active = false;
837
- targetSub.recalc = true;
838
- const target = targetSub.target;
839
- if (target.type === EFFECT_TYPE) {
840
- if (lastEffect === null) {
841
- context_default.firstEffectToRun = target;
842
- lastEffect = target;
843
- } else if (target.nextEffectToRun === null && lastEffect !== target) {
844
- lastEffect.nextEffectToRun = target;
845
- lastEffect = target;
846
- }
847
- } else if (!target.recalc) {
848
- target.recalc = true;
849
- if (target.firstTarget !== null) {
850
- computed = target;
851
- computed.rollback = targetSub;
852
- targetSub = target.firstTarget;
853
- continue;
854
- }
855
- }
856
- while (targetSub.nextTarget === null && computed !== void 0) {
857
- targetSub = computed.rollback;
858
- if (targetSub.source.type !== COMPUTED_TYPE) {
859
- computed = void 0;
860
- continue outer;
861
- }
862
- computed = targetSub.source;
863
- }
864
- if (targetSub.nextTarget === null) break;
865
- targetSub = targetSub.nextTarget;
1478
+ if (context.lastSignalToUpdate === null) {
1479
+ context.firstSignalToUpdate = signal;
1480
+ context.lastSignalToUpdate = signal;
1481
+ } else if (signal.nextSignalToUpdate === null) {
1482
+ context.lastSignalToUpdate.nextSignalToUpdate = signal;
1483
+ context.lastSignalToUpdate = signal;
866
1484
  }
1485
+ propagateFromSignal(signal);
867
1486
  } finally {
868
1487
  batchEnd();
869
1488
  }
870
1489
  }
871
1490
  }
872
-
873
1491
  //#endregion
874
1492
  //#region src/watch/trackProxySignal.ts
875
1493
  function trackProxySignal(data, key) {
876
1494
  let signal = data.signals.get(key);
877
1495
  if (signal === void 0) {
878
1496
  signal = {
879
- type: SIGNAL_TYPE,
1497
+ type: 0,
880
1498
  firstTarget: null,
881
1499
  nextSignalToUpdate: null
882
1500
  };
@@ -884,12 +1502,53 @@ function trackProxySignal(data, key) {
884
1502
  }
885
1503
  trackSignal(signal);
886
1504
  }
887
-
888
1505
  //#endregion
889
1506
  //#region src/watch/proxyGet.ts
1507
+ /**
1508
+ * Called when a read hits a `didSuspend` computed. Subscribes the reader (so
1509
+ * resolve re-runs it through the normal reactive graph), propagates the
1510
+ * suspend up the cache chain by tainting the active reader, and notifies the
1511
+ * nearest `@await` boundary.
1512
+ *
1513
+ * Return value (stale-while-revalidate, ASYNC.md §6.2): on a refresh suspend
1514
+ * (the computed has resolved before) returns the previously resolved value
1515
+ * held in `staleValue`, so readers keep displaying the old content instead of
1516
+ * a placeholder — an `@await` boundary thus keeps its content mounted
1517
+ * rather than flashing the `with` branch. On a first load (`staleValue` is
1518
+ * undefined) returns `undefined`, which the boundary replaces with its
1519
+ * `with` branch. In peek mode (used by `$pending`) always returns `undefined`
1520
+ * — the value isn't needed, only the subscription.
1521
+ */
1522
+ function suspendRead(signal) {
1523
+ trackSignal(signal);
1524
+ if (context.suspendPeek) {
1525
+ if (!signal.suspendQuiet) context.suspendPeekHit = true;
1526
+ return;
1527
+ }
1528
+ if (context.activeTarget !== null) {
1529
+ context.activeTarget.didSuspend = true;
1530
+ if (context.activeTarget.type === 2) {
1531
+ const effect = context.activeTarget;
1532
+ (effect.suspendSources ??= /* @__PURE__ */ new Set()).add(signal);
1533
+ }
1534
+ }
1535
+ if (context.awaitBoundary !== null) {
1536
+ context.awaitBoundary.suspended = true;
1537
+ context.awaitBoundary.pending.add(signal);
1538
+ const boundaryEffect = context.awaitBoundary.effect;
1539
+ if (boundaryEffect !== null && boundaryEffect !== context.activeTarget) {
1540
+ const oldActive = context.activeTarget;
1541
+ context.activeTarget = boundaryEffect;
1542
+ trackSignal(signal);
1543
+ context.activeTarget = oldActive;
1544
+ }
1545
+ }
1546
+ return signal.staleValue;
1547
+ }
890
1548
  function proxyGet(target, key, receiver) {
891
1549
  let data = target[proxyDataSymbol];
892
1550
  if (key === proxyDataSymbol) return data;
1551
+ if ((data.isDate || data.isMap || data.isSet) && !Object.prototype.hasOwnProperty.call(target, key)) return collectionGet(data, target, key, receiver);
893
1552
  let signal = data.signals.get(key);
894
1553
  if (signal === void 0) {
895
1554
  const propDescriptor = Object.getOwnPropertyDescriptor(target, key);
@@ -897,38 +1556,92 @@ function proxyGet(target, key, receiver) {
897
1556
  if (propDescriptor.writable) {
898
1557
  if (data.shallow !== true) {
899
1558
  const value = target[key];
900
- if (value !== void 0 && value !== null && typeof value === "object" && value[proxyDataSymbol] === void 0 && value.then === void 0) target[key] = $watch(value);
1559
+ const wrapped = deepWrap(value);
1560
+ if (wrapped !== value) target[key] = wrapped;
901
1561
  }
902
1562
  trackProxySignal(data, key);
903
1563
  } else if (propDescriptor.get) {
904
- const oldRegisterComputed = context_default.registerComputed;
1564
+ const oldRegisterComputed = context.registerComputed;
905
1565
  try {
906
- context_default.registerComputed = (computed) => {
1566
+ context.registerComputed = (computed) => {
907
1567
  data.signals.set(key, computed);
908
1568
  };
909
- return Reflect.get(target, key, receiver);
1569
+ const result = Reflect.get(target, key, receiver);
1570
+ const registered = data.signals.get(key);
1571
+ if (context.refreshSignals !== null && registered !== void 0 && registered.type === 1 && registered.isAsync) {
1572
+ context.refreshSignals.push(registered);
1573
+ context.refreshInitialized?.add(registered);
1574
+ }
1575
+ if (registered !== void 0 && registered.type === 1 && registered.didSuspend) return suspendRead(registered);
1576
+ return result;
910
1577
  } finally {
911
- context_default.registerComputed = oldRegisterComputed;
1578
+ context.registerComputed = oldRegisterComputed;
912
1579
  }
913
1580
  }
914
1581
  } else if (data.isArray) {
915
1582
  if (arrayWrapper[key] !== void 0) return arrayWrapper[key](data, target, key);
916
1583
  } else trackProxySignal(data, key);
917
- } else if (signal.type === SIGNAL_TYPE) trackProxySignal(data, key);
918
- else if (signal.type === COMPUTED_TYPE) {
1584
+ } else if (signal.type === 0) {
1585
+ if (data.shallow !== true) {
1586
+ const value = target[key];
1587
+ if (typeof value === "object" && value !== null) {
1588
+ const propDescriptor = Object.getOwnPropertyDescriptor(target, key);
1589
+ if (propDescriptor !== void 0 && propDescriptor.writable) {
1590
+ const wrapped = deepWrap(value);
1591
+ if (wrapped !== value) target[key] = wrapped;
1592
+ }
1593
+ }
1594
+ }
1595
+ trackProxySignal(data, key);
1596
+ } else if (signal.type === 1) {
1597
+ if (context.refreshSignals !== null && signal.isAsync) {
1598
+ context.refreshSignals.push(signal);
1599
+ return signal.value;
1600
+ }
919
1601
  if (signal.running) throw new Error("Cycle detected");
920
1602
  else if (signal.recalc) checkComputed(signal);
921
- else if (signal.didError) throw signal.value;
922
- else trackSignal(signal);
1603
+ if (signal.didError) {
1604
+ trackSignal(signal);
1605
+ throw signal.value;
1606
+ }
1607
+ if (signal.didSuspend) return suspendRead(signal);
1608
+ trackSignal(signal);
923
1609
  return signal.value;
924
1610
  }
925
- return Reflect.get(target, key, receiver);
1611
+ return target[key];
926
1612
  }
927
1613
  const arrayWrapper = {
928
- [Symbol.iterator]: function(data, target, key) {
1614
+ [Symbol.iterator]: function(data, target, _key) {
929
1615
  trackProxySignal(data, "length");
930
- context_default.activeTarget = null;
931
- return target[key];
1616
+ context.activeTarget = null;
1617
+ return function arrayIterator() {
1618
+ let index = 0;
1619
+ return {
1620
+ next() {
1621
+ if (index >= target.length) return {
1622
+ value: void 0,
1623
+ done: true
1624
+ };
1625
+ let value = target[index];
1626
+ if (data.shallow !== true) {
1627
+ const wrapped = deepWrap(value);
1628
+ if (wrapped !== value) {
1629
+ value = wrapped;
1630
+ target[index] = value;
1631
+ }
1632
+ }
1633
+ trackProxySignal(data, String(index));
1634
+ index++;
1635
+ return {
1636
+ value,
1637
+ done: false
1638
+ };
1639
+ },
1640
+ [Symbol.iterator]() {
1641
+ return this;
1642
+ }
1643
+ };
1644
+ };
932
1645
  },
933
1646
  pop: arrayHandle,
934
1647
  push: arrayHandle,
@@ -936,8 +1649,35 @@ const arrayWrapper = {
936
1649
  shift: arrayHandle,
937
1650
  sort: arrayHandle,
938
1651
  splice: arrayHandle,
939
- unshift: arrayHandle
1652
+ unshift: arrayHandle,
1653
+ slice: readHandle,
1654
+ concat: readHandle,
1655
+ filter: readHandle,
1656
+ map: readHandle,
1657
+ toReversed: readHandle,
1658
+ toSorted: readHandle,
1659
+ toSpliced: readHandle,
1660
+ indexOf: readHandle,
1661
+ lastIndexOf: readHandle,
1662
+ includes: readHandle,
1663
+ find: readHandle,
1664
+ findIndex: readHandle,
1665
+ some: readHandle,
1666
+ every: readHandle,
1667
+ join: readHandle,
1668
+ forEach: readHandle,
1669
+ reduce: readHandle,
1670
+ reduceRight: readHandle,
1671
+ flat: readHandle,
1672
+ flatMap: readHandle,
1673
+ at: readHandle
940
1674
  };
1675
+ function readHandle(data, target, key) {
1676
+ return function(...args) {
1677
+ trackProxySignal(data, "length");
1678
+ return target[key].apply(target, args);
1679
+ };
1680
+ }
941
1681
  function arrayHandle(data, target, key) {
942
1682
  return function(...args) {
943
1683
  const result = target[key].apply(target, args);
@@ -945,27 +1685,254 @@ function arrayHandle(data, target, key) {
945
1685
  return result;
946
1686
  };
947
1687
  }
948
-
1688
+ const COLLECTION_KEY = Symbol("torp.collection");
1689
+ const TIME_KEY = "#time";
1690
+ /**
1691
+ * Routes a `get` on a watched Date/Map/Set to the right wrapper handler.
1692
+ *
1693
+ * Note the two levels of indirection for methods: the handler runs at get-trap
1694
+ * time (returning the method to call), and the returned function runs when the
1695
+ * method is invoked. `size` is the exception -- it returns its value at
1696
+ * get-trap time, because it's a readonly property, not a method.
1697
+ */
1698
+ function collectionGet(data, target, key, receiver) {
1699
+ if (data.isDate) return dateGet(data, target, key);
1700
+ const handle = (data.isMap ? mapWrapper : setWrapper)[key];
1701
+ if (handle !== void 0) return handle(data, target, key, receiver);
1702
+ return target[key];
1703
+ }
1704
+ /**
1705
+ * Returns the signal key for a Map/Set operation: primitives get a per-key
1706
+ * signal, so reading one key doesn't re-run effects when an unrelated key
1707
+ * changes. Anything else shares the collection-wide signal.
1708
+ */
1709
+ function collectionKey(key) {
1710
+ const type = typeof key;
1711
+ return type === "string" || type === "number" || type === "symbol" ? key : COLLECTION_KEY;
1712
+ }
1713
+ /**
1714
+ * Propagates a Map/Set mutation: the per-key signal (when the key is a
1715
+ * primitive) plus the collection-wide signal. The collection signal is always
1716
+ * enough for object keys, and propagating the same signal twice would re-run
1717
+ * effects twice (they re-subscribe between flushes).
1718
+ */
1719
+ function propagateCollection(data, key) {
1720
+ if (key !== COLLECTION_KEY) propagateSignal(data, key);
1721
+ propagateSignal(data, COLLECTION_KEY);
1722
+ }
1723
+ function dateGet(data, target, key) {
1724
+ if (typeof key === "symbol") {
1725
+ if (key === Symbol.toPrimitive) return function(hint) {
1726
+ const result = target[Symbol.toPrimitive](hint);
1727
+ trackProxySignal(data, TIME_KEY);
1728
+ return result;
1729
+ };
1730
+ return target[key];
1731
+ }
1732
+ if (typeof key !== "string") return target[key];
1733
+ const value = target[key];
1734
+ if (typeof value !== "function") return value;
1735
+ if (key.startsWith("set")) return function(...args) {
1736
+ const before = target.getTime();
1737
+ const result = value.apply(target, args);
1738
+ if (target.getTime() !== before) propagateSignal(data, TIME_KEY);
1739
+ return result;
1740
+ };
1741
+ if (key.startsWith("get") || key.startsWith("to") || key === "valueOf") return function(...args) {
1742
+ const result = value.apply(target, args);
1743
+ trackProxySignal(data, TIME_KEY);
1744
+ return result;
1745
+ };
1746
+ return value;
1747
+ }
1748
+ const mapWrapper = {
1749
+ size: function(data, target) {
1750
+ trackProxySignal(data, COLLECTION_KEY);
1751
+ return target.size;
1752
+ },
1753
+ get: function(data, target) {
1754
+ return function(key) {
1755
+ key = $unwrap(key);
1756
+ trackProxySignal(data, collectionKey(key));
1757
+ const value = target.get(key);
1758
+ if (data.shallow !== true) {
1759
+ const wrapped = deepWrap(value);
1760
+ if (wrapped !== value) target.set(key, wrapped);
1761
+ return wrapped;
1762
+ }
1763
+ return value;
1764
+ };
1765
+ },
1766
+ has: function(data, target) {
1767
+ return function(key) {
1768
+ key = $unwrap(key);
1769
+ trackProxySignal(data, collectionKey(key));
1770
+ return target.has(key);
1771
+ };
1772
+ },
1773
+ set: function(data, target, _key, receiver) {
1774
+ return function(key, value) {
1775
+ key = $unwrap(key);
1776
+ if (target.has(key) && $unwrap(target.get(key)) === $unwrap(value)) return receiver ?? target;
1777
+ const result = target.set(key, value);
1778
+ propagateCollection(data, collectionKey(key));
1779
+ return receiver ?? result;
1780
+ };
1781
+ },
1782
+ delete: function(data, target) {
1783
+ return function(key) {
1784
+ key = $unwrap(key);
1785
+ const result = target.delete(key);
1786
+ propagateCollection(data, collectionKey(key));
1787
+ return result;
1788
+ };
1789
+ },
1790
+ clear: function(data, target) {
1791
+ return function() {
1792
+ const result = target.clear();
1793
+ batchStart();
1794
+ try {
1795
+ for (const key of data.signals.keys()) propagateSignal(data, key);
1796
+ } finally {
1797
+ batchEnd();
1798
+ }
1799
+ return result;
1800
+ };
1801
+ },
1802
+ keys: function(data, target) {
1803
+ return function() {
1804
+ trackProxySignal(data, COLLECTION_KEY);
1805
+ return [...target.keys()][Symbol.iterator]();
1806
+ };
1807
+ },
1808
+ values: function(data, target) {
1809
+ return function() {
1810
+ trackProxySignal(data, COLLECTION_KEY);
1811
+ let values = [...target.values()];
1812
+ if (data.shallow !== true) values = values.map(deepWrap);
1813
+ return values[Symbol.iterator]();
1814
+ };
1815
+ },
1816
+ entries: mapEntries,
1817
+ [Symbol.iterator]: mapEntries,
1818
+ forEach: function(data, target, _key, receiver) {
1819
+ return function(callback, thisArg) {
1820
+ trackProxySignal(data, COLLECTION_KEY);
1821
+ const wrap = data.shallow !== true;
1822
+ target.forEach((value, key) => {
1823
+ callback.call(thisArg, wrap ? deepWrap(value) : value, key, receiver ?? target);
1824
+ });
1825
+ };
1826
+ }
1827
+ };
1828
+ function mapEntries(data, target) {
1829
+ return function() {
1830
+ trackProxySignal(data, COLLECTION_KEY);
1831
+ const wrap = data.shallow !== true;
1832
+ return [...target.entries()].map((entry) => [entry[0], wrap ? deepWrap(entry[1]) : entry[1]])[Symbol.iterator]();
1833
+ };
1834
+ }
1835
+ const setWrapper = {
1836
+ size: function(data, target) {
1837
+ trackProxySignal(data, COLLECTION_KEY);
1838
+ return target.size;
1839
+ },
1840
+ has: function(data, target) {
1841
+ return function(value) {
1842
+ value = $unwrap(value);
1843
+ trackProxySignal(data, collectionKey(value));
1844
+ return target.has(value);
1845
+ };
1846
+ },
1847
+ add: function(data, target, _key, receiver) {
1848
+ return function(value) {
1849
+ value = $unwrap(value);
1850
+ if (target.has(value)) return receiver ?? target;
1851
+ const result = target.add(value);
1852
+ propagateCollection(data, collectionKey(value));
1853
+ return receiver ?? result;
1854
+ };
1855
+ },
1856
+ delete: function(data, target) {
1857
+ return function(value) {
1858
+ value = $unwrap(value);
1859
+ const result = target.delete(value);
1860
+ propagateCollection(data, collectionKey(value));
1861
+ return result;
1862
+ };
1863
+ },
1864
+ clear: function(data, target) {
1865
+ return function() {
1866
+ const result = target.clear();
1867
+ batchStart();
1868
+ try {
1869
+ for (const key of data.signals.keys()) propagateSignal(data, key);
1870
+ } finally {
1871
+ batchEnd();
1872
+ }
1873
+ return result;
1874
+ };
1875
+ },
1876
+ keys: setValues,
1877
+ values: setValues,
1878
+ [Symbol.iterator]: setValues,
1879
+ entries: setEntries,
1880
+ forEach: function(data, target, _key, receiver) {
1881
+ return function(callback, thisArg) {
1882
+ trackProxySignal(data, COLLECTION_KEY);
1883
+ const wrap = data.shallow !== true;
1884
+ target.forEach((value) => {
1885
+ const wrapped = wrap ? deepWrap(value) : value;
1886
+ callback.call(thisArg, wrapped, wrapped, receiver ?? target);
1887
+ });
1888
+ };
1889
+ }
1890
+ };
1891
+ function setValues(data, target) {
1892
+ return function() {
1893
+ trackProxySignal(data, COLLECTION_KEY);
1894
+ let values = [...target.values()];
1895
+ if (data.shallow !== true) values = values.map(deepWrap);
1896
+ return values[Symbol.iterator]();
1897
+ };
1898
+ }
1899
+ function setEntries(data, target) {
1900
+ return function() {
1901
+ trackProxySignal(data, COLLECTION_KEY);
1902
+ const wrap = data.shallow !== true;
1903
+ return [...target.values()].map((value) => {
1904
+ const wrapped = wrap ? deepWrap(value) : value;
1905
+ return [wrapped, wrapped];
1906
+ })[Symbol.iterator]();
1907
+ };
1908
+ }
949
1909
  //#endregion
950
1910
  //#region src/watch/proxySet.ts
951
1911
  function proxySet(target, key, value, receiver) {
952
1912
  const oldValue = target[key];
953
1913
  if (value !== oldValue) {
954
- if (context_default.batchOperation > 100) throw new Error("Cycle detected");
1914
+ if (context.batchOperation > 100) throw new Error("Cycle detected");
955
1915
  let data = target[proxyDataSymbol];
956
- devContext_default.signalSet(data, key);
957
- if (oldValue && oldValue[proxyDataSymbol] !== void 0) value = $watch(value, { shallow: oldValue[proxyDataSymbol].shallow });
1916
+ devContext.signalSet(data, key);
1917
+ if (data.shallow !== true) value = deepWrap(value);
958
1918
  Reflect.set(target, key, value, receiver);
959
1919
  propagateSignal(data, key);
960
- if (data.isArray && !isNaN(+String(key))) propagateSignal(data, "length");
1920
+ if (data.isArray && typeof key !== "symbol" && !isNaN(+key)) propagateSignal(data, "length");
961
1921
  }
962
1922
  return true;
963
1923
  }
964
-
965
1924
  //#endregion
966
1925
  //#region src/watch/$watch.ts
1926
+ const sharedHandler = {
1927
+ get: proxyGet,
1928
+ set: proxySet
1929
+ };
967
1930
  /**
968
- * Watches an object and runs effects when its properties are changed
1931
+ * Watches an object and runs effects when its properties are changed.
1932
+ *
1933
+ * Dates, Maps and Sets are supported too: their methods are reactive (reads
1934
+ * track, writes notify), so `new Date()`, `new Map()` and `new Set()` can be
1935
+ * used in watched state directly.
969
1936
  *
970
1937
  * @param object The object to watch
971
1938
  */
@@ -974,19 +1941,75 @@ function $watch(object, options) {
974
1941
  const data = {
975
1942
  target: object,
976
1943
  isArray: Array.isArray(object),
1944
+ isDate: object instanceof Date,
1945
+ isMap: object instanceof Map,
1946
+ isSet: object instanceof Set,
977
1947
  shallow: options?.shallow === true,
978
1948
  signals: /* @__PURE__ */ new Map()
979
1949
  };
980
- object[proxyDataSymbol] = data;
981
- const handler = {
982
- get: proxyGet,
983
- set: proxySet
984
- };
985
- const proxy = new Proxy(object, handler);
986
- devContext_default.onWatch(data);
1950
+ Object.defineProperty(object, proxyDataSymbol, {
1951
+ value: data,
1952
+ writable: true,
1953
+ enumerable: false,
1954
+ configurable: true
1955
+ });
1956
+ const proxy = new Proxy(object, sharedHandler);
1957
+ devContext.onWatch(data);
987
1958
  return proxy;
988
1959
  }
989
-
1960
+ //#endregion
1961
+ //#region src/render/getSequence.ts
1962
+ /**
1963
+ * Computes the indices (into `arr`) of a longest strictly-increasing
1964
+ * subsequence, skipping zero entries. Adapted from Vue 3's `getSequence`.
1965
+ *
1966
+ * Used by the keyed-list reconciler (`runListItems`): `arr` is the
1967
+ * `newIndexToOldIndex` map where `arr[i] = oldIndex + 1` (so `0` marks a
1968
+ * new/unmounted slot, which is skipped). The returned indices identify the
1969
+ * items that are already in correct relative order and therefore don't need
1970
+ * to be moved — everything else is moved or mounted.
1971
+ *
1972
+ * Runs in O(n log n).
1973
+ */
1974
+ function getSequence(arr) {
1975
+ const p = arr.slice();
1976
+ const result = [0];
1977
+ let i;
1978
+ let j;
1979
+ let u;
1980
+ let v;
1981
+ let c;
1982
+ const len = arr.length;
1983
+ for (i = 0; i < len; i++) {
1984
+ const arrI = arr[i];
1985
+ if (arrI !== 0) {
1986
+ j = result[result.length - 1];
1987
+ if (arr[j] < arrI) {
1988
+ p[i] = j;
1989
+ result.push(i);
1990
+ continue;
1991
+ }
1992
+ u = 0;
1993
+ v = result.length - 1;
1994
+ while (u < v) {
1995
+ c = u + v >> 1;
1996
+ if (arr[result[c]] < arrI) u = c + 1;
1997
+ else v = c;
1998
+ }
1999
+ if (arrI < arr[result[u]]) {
2000
+ if (u > 0) p[i] = result[u - 1];
2001
+ result[u] = i;
2002
+ }
2003
+ }
2004
+ }
2005
+ u = result.length;
2006
+ v = result[u - 1];
2007
+ while (u-- > 0) {
2008
+ result[u] = v;
2009
+ v = p[v];
2010
+ }
2011
+ return result;
2012
+ }
990
2013
  //#endregion
991
2014
  //#region src/render/moveRegion.ts
992
2015
  function moveRegion(parent, region, before) {
@@ -1000,145 +2023,416 @@ function moveRegion(parent, region, before) {
1000
2023
  currentNode = nextNode;
1001
2024
  }
1002
2025
  }
1003
-
1004
2026
  //#endregion
1005
2027
  //#region src/render/runListItems.ts
2028
+ const SHALLOW_WATCH_OPTIONS = { shallow: true };
2029
+ /**
2030
+ * Mounts a new list item from a spec: allocates the ListItem, establishes it
2031
+ * as the active region (so the compiler-emitted `create` callback attaches
2032
+ * effects/events to the right region), sets its depth, optionally wraps its
2033
+ * data in a shallow watch, then runs `create`. Returns the mounted item so the
2034
+ * caller can slot it into the live list. The sibling chain is left for the
2035
+ * end-of-pass relink.
2036
+ */
2037
+ function mountSpec(spec, region, before, create, noWatch) {
2038
+ const item = newListItem(spec.data, spec.key);
2039
+ const savedActiveRegion = context.activeRegion;
2040
+ item.depth = region.depth + 1;
2041
+ context.activeRegion = item;
2042
+ if (noWatch !== true) item.data = $watch(item.data, SHALLOW_WATCH_OPTIONS);
2043
+ create(item, before);
2044
+ context.activeRegion = savedActiveRegion;
2045
+ return item;
2046
+ }
1006
2047
  /**
2048
+ * Reconciles an old keyed list against a new keyed list, reusing old
2049
+ * ListItems for survivors and producing the minimal set of DOM moves /
2050
+ * mounts / clears.
2051
+ *
2052
+ * @returns The new live list of ListItems (reused old items + freshly
2053
+ * mounted ones), which the caller keeps for the next reconciliation.
2054
+ *
1007
2055
  * @param region The list's region
1008
2056
  * @param parent The parent DOM element
1009
- * @param anchor The DOM element to create new items before
1010
- * @param oldItems The list of current items
1011
- * @param newItems The list of future items
2057
+ * @param anchor The DOM node to create new items before (the list's trailing anchor)
2058
+ * @param oldItems The list of current (live) items from the previous run
2059
+ * @param newSpecs The lightweight {key, data} specs for the new data
1012
2060
  * @param create A function that creates the DOM elements for a new item
2061
+ * @param update A function that syncs a matched item's data from its spec
2062
+ * (re-runs effects if a loop variable's reference actually changed)
2063
+ * @param noWatch When true, skip the per-item shallow `$watch` wrap. The
2064
+ * compiler guarantees the `@for` body never writes to its loop variables,
2065
+ * and emits an `update` callback that re-runs item effects manually when a
2066
+ * loop variable's reference actually changes (via `t_rerun_region_effects`).
1013
2067
  */
1014
- function runListItems(region, parent, anchor, oldItems, newItems, create, update) {
1015
- let oldStartIndex = 0;
1016
- let oldEndIndex = oldItems.length - 1;
1017
- let oldStartItem = oldItems[0];
1018
- let oldEndItem = oldItems[oldEndIndex];
1019
- let newStartIndex = 0;
1020
- let newEndIndex = newItems.length - 1;
1021
- let newStartItem = newItems[0];
1022
- let newEndItem = newItems[newEndIndex];
1023
- let oldKeyToIndex;
1024
- let newKeyToIndex;
1025
- while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) if (oldStartItem === null) oldStartItem = oldItems[++oldStartIndex];
1026
- else if (oldEndItem === null) oldEndItem = oldItems[--oldEndIndex];
1027
- else if (newStartItem === null) newStartItem = newItems[++newStartIndex];
1028
- else if (newEndItem === null) newEndItem = newItems[--newEndIndex];
1029
- else if (oldStartItem.key === newStartItem.key) {
1030
- transferListItemData(oldStartItem, newStartItem, update);
1031
- oldStartItem = oldItems[++oldStartIndex];
1032
- newStartItem = newItems[++newStartIndex];
1033
- } else if (oldEndItem.key === newEndItem.key) {
1034
- transferListItemData(oldEndItem, newEndItem, update);
1035
- oldEndItem = oldItems[--oldEndIndex];
1036
- newEndItem = newItems[--newEndIndex];
1037
- } else if (oldStartItem.key === newEndItem.key) {
1038
- moveRegion(parent, oldStartItem, oldEndItem.endNode.nextSibling);
1039
- transferListItemData(oldStartItem, newEndItem, update);
1040
- oldStartItem = oldItems[++oldStartIndex];
1041
- newEndItem = newItems[--newEndIndex];
1042
- } else if (oldEndItem.key === newStartItem.key) {
1043
- moveRegion(parent, oldEndItem, oldStartItem.startNode);
1044
- transferListItemData(oldEndItem, newStartItem, update);
1045
- oldEndItem = oldItems[--oldEndIndex];
1046
- newStartItem = newItems[++newStartIndex];
1047
- } else {
1048
- if (oldKeyToIndex === void 0 || newKeyToIndex === void 0) {
1049
- oldKeyToIndex = /* @__PURE__ */ new Map();
1050
- for (let i = oldStartIndex; i < oldEndIndex; i++) oldKeyToIndex.set(oldItems[i].key, i);
1051
- newKeyToIndex = /* @__PURE__ */ new Map();
1052
- for (let i = newStartIndex; i < newEndIndex; i++) newKeyToIndex.set(newItems[i].key, i);
2068
+ function runListItems(region, parent, anchor, oldItems, newSpecs, create, update, noWatch) {
2069
+ const newItems = Array.from({ length: newSpecs.length });
2070
+ let nextSibling;
2071
+ if (oldItems.length > 0) {
2072
+ const lastOld = oldItems[oldItems.length - 1];
2073
+ nextSibling = lastOld.nextRegion;
2074
+ while (nextSibling !== null && nextSibling.depth > lastOld.depth) nextSibling = nextSibling.nextRegion;
2075
+ } else nextSibling = region.nextRegion;
2076
+ let oldStart = 0;
2077
+ let oldEnd = oldItems.length - 1;
2078
+ let newStart = 0;
2079
+ let newEnd = newSpecs.length - 1;
2080
+ let chainNeedsRelink = false;
2081
+ while (oldStart <= oldEnd && newStart <= newEnd) if (oldItems[oldStart].key === newSpecs[newStart].key) {
2082
+ const item = oldItems[oldStart];
2083
+ update(item, newSpecs[newStart]);
2084
+ newItems[newStart] = item;
2085
+ oldStart++;
2086
+ newStart++;
2087
+ } else if (oldItems[oldEnd].key === newSpecs[newEnd].key) {
2088
+ const item = oldItems[oldEnd];
2089
+ update(item, newSpecs[newEnd]);
2090
+ newItems[newEnd] = item;
2091
+ oldEnd--;
2092
+ newEnd--;
2093
+ } else if (oldItems[oldStart].key === newSpecs[newEnd].key) {
2094
+ const item = oldItems[oldStart];
2095
+ moveRegion(parent, item, oldItems[oldEnd].endNode?.nextSibling ?? anchor);
2096
+ chainNeedsRelink = true;
2097
+ update(item, newSpecs[newEnd]);
2098
+ newItems[newEnd] = item;
2099
+ oldStart++;
2100
+ newEnd--;
2101
+ } else if (oldItems[oldEnd].key === newSpecs[newStart].key) {
2102
+ const item = oldItems[oldEnd];
2103
+ moveRegion(parent, item, oldItems[oldStart].startNode);
2104
+ chainNeedsRelink = true;
2105
+ update(item, newSpecs[newStart]);
2106
+ newItems[newStart] = item;
2107
+ oldEnd--;
2108
+ newStart++;
2109
+ } else break;
2110
+ if (oldStart > oldEnd && newStart <= newEnd) {
2111
+ const before = newEnd + 1 < newSpecs.length ? newItems[newEnd + 1].startNode : anchor;
2112
+ for (let i = newStart; i <= newEnd; i++) newItems[i] = mountSpec(newSpecs[i], region, before, create, noWatch);
2113
+ chainNeedsRelink = true;
2114
+ } else if (newStart > newEnd) for (let i = oldStart; i <= oldEnd; i++) clearRegion(oldItems[i]);
2115
+ else {
2116
+ const newKeyToIndex = /* @__PURE__ */ new Map();
2117
+ for (let i = newStart; i <= newEnd; i++) newKeyToIndex.set(newSpecs[i].key, i);
2118
+ let anyOverlap = false;
2119
+ for (let i = oldStart; i <= oldEnd; i++) if (newKeyToIndex.has(oldItems[i].key)) {
2120
+ anyOverlap = true;
2121
+ break;
1053
2122
  }
1054
- let oldIndex = oldKeyToIndex.get(newStartItem.key);
1055
- let newIndex = newKeyToIndex.get(oldStartItem.key);
1056
- if (oldIndex === void 0 && newIndex === void 0) {
1057
- const oldRegion = pushRegion(newStartItem, true);
1058
- newStartItem.data = $watch(newStartItem.data, { shallow: true });
1059
- create(newStartItem, oldStartItem.startNode);
1060
- popRegion(oldRegion);
1061
- newStartItem.previousRegion = oldStartItem.previousRegion;
1062
- newStartItem.nextRegion = oldStartItem;
1063
- oldStartItem.previousRegion = newStartItem;
1064
- clearRegion(oldStartItem);
1065
- oldStartItem = oldItems[++oldStartIndex];
1066
- newStartItem = newItems[++newStartIndex];
1067
- } else if (oldIndex === void 0) {
1068
- const oldRegion = pushRegion(newStartItem, true);
1069
- newStartItem.data = $watch(newStartItem.data, { shallow: true });
1070
- create(newStartItem, oldStartItem.startNode);
1071
- popRegion(oldRegion);
1072
- newStartItem = newItems[++newStartIndex];
1073
- } else if (newIndex === void 0) {
1074
- clearRegion(oldStartItem);
1075
- oldStartItem = oldItems[++oldStartIndex];
2123
+ if (!anyOverlap) {
2124
+ let before = oldItems[oldEnd]?.endNode?.nextSibling ?? anchor;
2125
+ for (let i = oldEnd; i >= oldStart; i--) clearRegion(oldItems[i]);
2126
+ for (let i = newStart; i <= newEnd; i++) {
2127
+ newItems[i] = mountSpec(newSpecs[i], region, before, create, noWatch);
2128
+ before = newItems[i].endNode.nextSibling;
2129
+ }
2130
+ chainNeedsRelink = true;
1076
2131
  } else {
1077
- const oldData = oldItems[oldIndex];
1078
- moveRegion(parent, oldData, oldStartItem.startNode);
1079
- transferListItemData(oldData, newStartItem, update);
1080
- oldItems[oldIndex] = null;
1081
- newStartItem = newItems[++newStartIndex];
2132
+ const newMidLen = newEnd - newStart + 1;
2133
+ const newIndexToOld = Array.from({ length: newMidLen }).fill(0);
2134
+ for (let i = oldStart; i <= oldEnd; i++) {
2135
+ const oldItem = oldItems[i];
2136
+ const newIdx = newKeyToIndex.get(oldItem.key);
2137
+ if (newIdx !== void 0) {
2138
+ newIndexToOld[newIdx - newStart] = i + 1;
2139
+ update(oldItem, newSpecs[newIdx]);
2140
+ newItems[newIdx] = oldItem;
2141
+ } else clearRegion(oldItem);
2142
+ }
2143
+ const seq = getSequence(newIndexToOld);
2144
+ let j = seq.length - 1;
2145
+ for (let i = newMidLen - 1; i >= 0; i--) {
2146
+ const newIdx = newStart + i;
2147
+ const before = newIdx + 1 < newSpecs.length ? newItems[newIdx + 1].startNode : anchor;
2148
+ if (newIndexToOld[i] === 0) {
2149
+ newItems[newIdx] = mountSpec(newSpecs[newIdx], region, before, create, noWatch);
2150
+ chainNeedsRelink = true;
2151
+ } else if (j < 0 || i !== seq[j]) {
2152
+ moveRegion(parent, newItems[newIdx], before);
2153
+ chainNeedsRelink = true;
2154
+ } else j--;
2155
+ }
1082
2156
  }
1083
2157
  }
1084
- if (oldStartIndex <= oldEndIndex || newStartIndex <= newEndIndex) if (oldStartIndex > oldEndIndex) {
1085
- let before = oldStartItem?.startNode ?? oldItems[oldItems.length - 1]?.endNode?.nextSibling ?? anchor;
1086
- for (; newStartIndex <= newEndIndex; newStartItem = newItems[++newStartIndex]) {
1087
- const oldRegion = pushRegion(newStartItem, true);
1088
- newStartItem.data = $watch(newStartItem.data, { shallow: true });
1089
- create(newStartItem, before);
1090
- popRegion(oldRegion);
1091
- before = newStartItem.endNode.nextSibling;
2158
+ if (!chainNeedsRelink && newItems.length > 0) {
2159
+ let lastRegion = newItems[newItems.length - 1];
2160
+ let next = lastRegion.nextRegion;
2161
+ while (next !== null && next.depth > lastRegion.depth) {
2162
+ lastRegion = next;
2163
+ next = next.nextRegion;
1092
2164
  }
1093
- } else for (; oldEndIndex >= oldStartIndex; oldStartItem = oldItems[oldEndIndex--]) clearRegion(oldStartItem);
1094
- if (newItems.length > 0) region.nextRegion = newItems[0];
1095
- else if (oldItems.length > 0) region.nextRegion = oldItems[oldItems.length - 1].nextRegion;
1096
- }
1097
- function transferListItemData(oldItem, newItem, update) {
1098
- newItem.startNode = oldItem.startNode;
1099
- newItem.endNode = oldItem.endNode;
1100
- update(oldItem, newItem);
1101
- newItem.data = oldItem.data;
1102
- newItem.name = oldItem.name;
2165
+ context.previousRegion = lastRegion;
2166
+ return newItems;
2167
+ }
2168
+ if (newItems.length > 0) {
2169
+ let prevTail = region;
2170
+ for (let i = 0; i < newItems.length; i++) {
2171
+ const item = newItems[i];
2172
+ item.previousRegion = prevTail;
2173
+ prevTail.nextRegion = item;
2174
+ let last = item;
2175
+ let next = item.nextRegion;
2176
+ while (next !== null && next.depth > item.depth) {
2177
+ last = next;
2178
+ next = next.nextRegion;
2179
+ }
2180
+ prevTail = last;
2181
+ }
2182
+ prevTail.nextRegion = nextSibling;
2183
+ if (nextSibling !== null) nextSibling.previousRegion = prevTail;
2184
+ region.nextRegion = newItems[0];
2185
+ context.previousRegion = prevTail;
2186
+ } else {
2187
+ region.nextRegion = nextSibling;
2188
+ if (nextSibling !== null) nextSibling.previousRegion = region;
2189
+ context.previousRegion = region;
2190
+ }
2191
+ return newItems;
1103
2192
  }
1104
-
1105
2193
  //#endregion
1106
2194
  //#region src/render/runList.ts
1107
2195
  /**
1108
2196
  * Runs a `for` control statement
2197
+ * @param buildItems A function that returns the current list of lightweight
2198
+ * `{key, data}` specs (one per row of the source data)
1109
2199
  * @param create A function that creates the control statement's branches
2200
+ * @param noWatch When true, the compiler has determined the `@for` body never
2201
+ * writes to its loop variables, so each item's `data` bag can be left
2202
+ * unwrapped (skipping the per-item shallow `$watch` Proxy + ProxyData +
2203
+ * signals Map allocations). The compiler-emitted `update` callback is then
2204
+ * responsible for re-running item effects when a loop variable's reference
2205
+ * actually changes (via `t_rerun_region_effects`).
1110
2206
  */
1111
- function runList(region, parent, anchor, buildItems, create, update) {
2207
+ function runList(region, parent, anchor, buildItems, create, update, noWatch) {
1112
2208
  let first = true;
1113
2209
  let listItems = [];
1114
- $run(function runList$1() {
2210
+ $run(function runList() {
2211
+ if (region.depth === -2) return;
1115
2212
  const oldRegion = pushRegion(region, first);
1116
2213
  first = false;
1117
- const newItems = buildItems();
1118
- context_default.activeTarget = null;
1119
- runListItems(region, parent, anchor, listItems, newItems, create, update);
1120
- listItems = newItems;
2214
+ const newSpecs = buildItems();
2215
+ context.activeTarget = null;
2216
+ listItems = runListItems(region, parent, anchor, listItems, newSpecs, create, update, noWatch);
1121
2217
  popRegion(oldRegion);
1122
2218
  });
1123
- if (context_default.hydrationNode !== null) anchor = context_default.hydrationNode.nextSibling;
1124
2219
  }
1125
-
2220
+ //#endregion
2221
+ //#region src/render/runAwait.ts
2222
+ /**
2223
+ * Renders an `@await` boundary. On the first run, content is rendered
2224
+ * speculatively; if any read inside suspends (`didSuspend`), the boundary
2225
+ * discards the partial render and shows the `with` branch instead.
2226
+ *
2227
+ * Fine-grained updates: on subsequent runs the boundary only decides whether
2228
+ * to SWITCH branches. It does so from its `pending` set — the suspended
2229
+ * computeds recorded by `suspendRead` — dropping entries that resolved and
2230
+ * re-subscribing the still-suspended ones (a re-run deactivates all of the
2231
+ * effect's source subscriptions, so they must be re-tracked to survive
2232
+ * `clearSources`). That check is O(pending reads), not O(all sources), and
2233
+ * non-suspend dependency changes inside content are left to the child
2234
+ * effects that read them — the boundary isn't re-run by them at all.
2235
+ *
2236
+ * Stale-while-revalidate (ASYNC.md §6.2): once content has been produced
2237
+ * (`hasContent`), a subsequent suspend during a refresh does NOT switch to
2238
+ * the `with` branch — the boundary keeps the stale content mounted. `$async`
2239
+ * retains the previous resolved value (`Computed.staleValue`) and
2240
+ * `suspendRead` returns it, so child effects keep displaying the old value
2241
+ * until the new promise resolves and updates them in place. The `with` branch
2242
+ * is shown only on the first load, before content has ever rendered
2243
+ * successfully.
2244
+ *
2245
+ * @param renderContent Builds the content children (may read $async getters).
2246
+ * @param renderWith Builds the `with`-branch children, or null for empty.
2247
+ */
2248
+ function runAwait(region, anchor, renderContent, renderWith, name) {
2249
+ const boundary = {
2250
+ suspended: false,
2251
+ pending: /* @__PURE__ */ new Set(),
2252
+ effect: null
2253
+ };
2254
+ let index = -1;
2255
+ let first = true;
2256
+ let hasContent = false;
2257
+ let theEffect = null;
2258
+ const gen = region.generation = (region.generation ?? 0) + 1;
2259
+ $run(function runAwait() {
2260
+ if (region.generation !== gen) return;
2261
+ theEffect = context.activeTarget;
2262
+ boundary.effect = theEffect;
2263
+ const oldRegion = pushRegion(region, first);
2264
+ first = false;
2265
+ const renderBranch = (targetIndex, fn) => {
2266
+ runControlBranch(region, index, targetIndex);
2267
+ context.previousRegion = region;
2268
+ const oldR = pushRegion(newRegion(targetIndex === 0 ? "await_content" : "await_with"), true);
2269
+ fn(anchor);
2270
+ popRegion(oldR);
2271
+ index = targetIndex;
2272
+ };
2273
+ const attemptContent = () => {
2274
+ const oldBoundary = context.awaitBoundary;
2275
+ context.awaitBoundary = boundary;
2276
+ boundary.suspended = false;
2277
+ const savedHydration = context.hydrationNode;
2278
+ if (savedHydration !== null) context.hydrationNode = null;
2279
+ renderBranch(0, renderContent);
2280
+ if (savedHydration !== null) context.hydrationNode = savedHydration;
2281
+ context.awaitBoundary = oldBoundary;
2282
+ if (boundary.suspended && renderWith !== null) renderBranch(1, renderWith);
2283
+ else if (!boundary.suspended) hasContent = true;
2284
+ };
2285
+ if (theEffect === null) attemptContent();
2286
+ else {
2287
+ let suspended = false;
2288
+ for (const signal of boundary.pending) if (signal.didSuspend) {
2289
+ suspended = true;
2290
+ trackSignal(signal);
2291
+ } else boundary.pending.delete(signal);
2292
+ const targetIndex = suspended ? 1 : 0;
2293
+ if (targetIndex !== index) {
2294
+ if (targetIndex === 0) attemptContent();
2295
+ else if (!hasContent) if (renderWith !== null) renderBranch(1, renderWith);
2296
+ else {
2297
+ runControlBranch(region, index, 1);
2298
+ index = 1;
2299
+ }
2300
+ }
2301
+ }
2302
+ popRegion(oldRegion);
2303
+ widenAncestorsAtAnchor(region, anchor);
2304
+ }, name);
2305
+ }
2306
+ //#endregion
2307
+ //#region src/render/saveHydration.ts
2308
+ /**
2309
+ * Snapshots the hydration cursor so a `@try` branch's partial hydration walk
2310
+ * can be rewound if the branch throws and the `@catch` branch renders instead.
2311
+ *
2312
+ * @returns The current hydration cursor node, or `null` when not hydrating.
2313
+ */
2314
+ function saveHydration() {
2315
+ return context.hydrationNode;
2316
+ }
2317
+ //#endregion
2318
+ //#region src/render/runTry.ts
2319
+ /**
2320
+ * Renders a `@try`/`@catch` group, or a top-level `@error` block (which the
2321
+ * compiler wraps around the whole render).
2322
+ *
2323
+ * Like `runAwait`, the boundary is a control effect that renders one branch
2324
+ * at a time into a fresh child region (`runControlBranch` clears the old
2325
+ * branch on switch). Unlike the old compiled form, the try/catch lives in
2326
+ * this runtime, which gives it three capabilities the compiled form lacked:
2327
+ *
2328
+ * - **Effect-rerun error routing.** The boundary registers itself on its
2329
+ * region (`region.errorBoundary`); when an effect inside the try content
2330
+ * throws on a later re-run, `triggerEffects` → `routeEffectError` stores
2331
+ * the error here and force re-runs the boundary effect, which renders the
2332
+ * catch branch with it.
2333
+ * - **Recovery outside direct reads.** Reactive reads wrapped in nested
2334
+ * `$run` effects (text/attribute interpolation) are tracked by those
2335
+ * effects, not the boundary. When an error is routed, the boundary holds
2336
+ * the erroring effect's source signals (`heldSignals`) and re-subscribes
2337
+ * to them on every run that shows the catch branch, so a later change
2338
+ * re-attempts the try branch.
2339
+ * - **Top-level `@error` recovery.** Because the whole render is a
2340
+ * re-runnable branch, a later re-render error (from a nested control
2341
+ * re-running after a prop change) is caught by the same routing, and a
2342
+ * recovery re-render clears the error content and restores the normal
2343
+ * content.
2344
+ *
2345
+ * Semantics preserved from the compiled form:
2346
+ *
2347
+ * - Synchronous build errors in the try content render the catch branch
2348
+ * (rewinding the hydration cursor and restoring the control region, which
2349
+ * the failed partial build left active).
2350
+ * - While the try branch is showing, a boundary re-run with no routed error
2351
+ * does NOT rebuild it (`runControlBranch` same-index skip) — exactly like
2352
+ * `@if`/`@switch` branches.
2353
+ * - While the catch branch is showing, any boundary re-run re-attempts the
2354
+ * try branch (the recovery path for errors read directly by the boundary
2355
+ * effect, e.g. via `@const`).
2356
+ * - With no catch branch, errors propagate up to the nearest outer boundary
2357
+ * and no boundary is registered on the region.
2358
+ *
2359
+ * @param renderTry Builds the try content. May read reactive state.
2360
+ * @param renderCatch Builds the catch content, or null for `@try` without
2361
+ * `@catch`. Receives the error (the `@catch (err)` variable).
2362
+ */
2363
+ function runTry(region, anchor, renderTry, renderCatch, name) {
2364
+ const boundary = {
2365
+ error: void 0,
2366
+ hasError: false,
2367
+ effect: null,
2368
+ heldSignals: null
2369
+ };
2370
+ if (renderCatch !== null) region.errorBoundary = boundary;
2371
+ let index = -1;
2372
+ let first = true;
2373
+ const gen = region.generation = (region.generation ?? 0) + 1;
2374
+ $run(function runTry() {
2375
+ if (region.generation !== gen) return;
2376
+ const theEffect = context.activeTarget;
2377
+ boundary.effect = theEffect;
2378
+ const oldRegion = pushRegion(region, first);
2379
+ first = false;
2380
+ const renderBranch = (targetIndex, fn) => {
2381
+ runControlBranch(region, index, targetIndex);
2382
+ context.previousRegion = region;
2383
+ const oldR = pushRegion(newRegion(targetIndex === 0 ? "try" : "catch"), true);
2384
+ fn(anchor);
2385
+ popRegion(oldR);
2386
+ index = targetIndex;
2387
+ };
2388
+ const holdSignals = () => {
2389
+ if (boundary.heldSignals === null || theEffect === null) return;
2390
+ for (const signal of boundary.heldSignals) trackSignal(signal);
2391
+ };
2392
+ const hasRoutedError = boundary.hasError;
2393
+ const routedError = boundary.error;
2394
+ boundary.hasError = false;
2395
+ if (hasRoutedError && renderCatch !== null) {
2396
+ index = -1;
2397
+ renderBranch(1, (a) => renderCatch(a, routedError));
2398
+ holdSignals();
2399
+ } else if (index !== 0) {
2400
+ const snapshot = saveHydration();
2401
+ try {
2402
+ renderBranch(0, renderTry);
2403
+ boundary.heldSignals = null;
2404
+ } catch (err) {
2405
+ pushRegion(region);
2406
+ restoreHydration(snapshot);
2407
+ if (renderCatch !== null) {
2408
+ index = -1;
2409
+ renderBranch(1, (a) => renderCatch(a, err));
2410
+ holdSignals();
2411
+ } else throw err;
2412
+ }
2413
+ }
2414
+ popRegion(oldRegion);
2415
+ widenAncestorsAtAnchor(region, anchor);
2416
+ if (context.hydrationNode !== null && anchor !== null) context.hydrationNode = anchor;
2417
+ }, name);
2418
+ }
1126
2419
  //#endregion
1127
2420
  //#region src/render/setAttribute.ts
1128
2421
  function setAttribute(el, name, value) {
1129
2422
  if (value === false || value === void 0 || value === null) el.removeAttribute(name);
1130
2423
  else el.setAttribute(name, value);
1131
2424
  }
1132
-
1133
2425
  //#endregion
1134
2426
  //#region src/render/setDynamicElement.ts
1135
2427
  function setDynamicElement(el, tag) {
1136
- if (context_default.hydrationNode !== null) return el;
2428
+ if (context.hydrationNode !== null) return el;
1137
2429
  let newElement = el.ownerDocument.createElement(tag);
2430
+ for (const attr of el.attributes) newElement.setAttributeNS(null, attr.name, attr.value);
2431
+ copyEventListeners(el, newElement);
2432
+ newElement.replaceChildren(...el.childNodes.values());
1138
2433
  el.replaceWith(newElement);
1139
2434
  return newElement;
1140
2435
  }
1141
-
1142
2436
  //#endregion
1143
2437
  //#region src/watch/$batch.ts
1144
2438
  /**
@@ -1148,7 +2442,7 @@ function setDynamicElement(el, tag) {
1148
2442
  * @param fn The function containing the batched updates to run.
1149
2443
  */
1150
2444
  function $batch(fn) {
1151
- if (context_default.batch > 0) return fn();
2445
+ if (context.batch > 0) return fn();
1152
2446
  batchStart();
1153
2447
  try {
1154
2448
  return fn();
@@ -1156,7 +2450,127 @@ function $batch(fn) {
1156
2450
  batchEnd();
1157
2451
  }
1158
2452
  }
1159
-
2453
+ //#endregion
2454
+ //#region src/watch/$async.ts
2455
+ /**
2456
+ * Caches an async computed value from a thunk that returns a Promise. A peer
2457
+ * of `$cache` for async getters: the returned Computed carries a `didSuspend`
2458
+ * indicator that the proxy get trap reads to suspend callers until the promise
2459
+ * resolves.
2460
+ *
2461
+ * The thenable check and `.then` wiring live inside `computed.run` (a closure
2462
+ * over the computed object), so they fire on both the initial run and every
2463
+ * recalculation — `runComputed` and `checkComputed` are unchanged.
2464
+ *
2465
+ * @param fn A thunk returning the Promise to await. Signal reads inside `fn`
2466
+ * are tracked, so the fetch re-runs when dependencies change.
2467
+ */
2468
+ function $async(fn) {
2469
+ if (context.registerComputed === null) throw new Error("$async must be used in a getter");
2470
+ const computed = {
2471
+ type: 1,
2472
+ isAsync: true,
2473
+ value: null,
2474
+ run: null,
2475
+ firstSource: null,
2476
+ firstTarget: null,
2477
+ recalc: false,
2478
+ running: false,
2479
+ didError: false,
2480
+ didSuspend: false,
2481
+ generation: 0,
2482
+ hasResolved: false,
2483
+ lastErrored: false,
2484
+ suspendQuiet: false,
2485
+ staleValue: void 0,
2486
+ rollback: null
2487
+ };
2488
+ computed.run = () => {
2489
+ const value = fn();
2490
+ if (value !== null && value !== void 0 && typeof value.then === "function") {
2491
+ const gen = ++computed.generation;
2492
+ computed.didSuspend = true;
2493
+ computed.suspendQuiet = computed.hasResolved && !computed.recalc;
2494
+ value.then((v) => {
2495
+ if (computed.generation !== gen) return;
2496
+ computed.value = v;
2497
+ computed.didSuspend = false;
2498
+ computed.hasResolved = true;
2499
+ computed.lastErrored = false;
2500
+ computed.staleValue = v;
2501
+ batchStart();
2502
+ propagateFromSignal(computed);
2503
+ batchEnd();
2504
+ }, (e) => {
2505
+ if (computed.generation !== gen) return;
2506
+ computed.value = e;
2507
+ computed.didError = true;
2508
+ computed.didSuspend = false;
2509
+ computed.hasResolved = true;
2510
+ computed.lastErrored = true;
2511
+ computed.staleValue = void 0;
2512
+ batchStart();
2513
+ propagateFromSignal(computed);
2514
+ batchEnd();
2515
+ });
2516
+ }
2517
+ return value;
2518
+ };
2519
+ context.registerComputed(computed);
2520
+ runComputed(computed);
2521
+ return computed.value;
2522
+ }
2523
+ //#endregion
2524
+ //#region src/watch/$bind.ts
2525
+ /**
2526
+ * Establishes two-way reactive sync between matching keys on a state object
2527
+ * and a props object.
2528
+ *
2529
+ * For each key, `$bind` creates:
2530
+ * - **Forward sync** (`props → state`): when the parent pushes a new value via
2531
+ * `$props[key]`, it flows into `$state[key]`. Skips `undefined` values so
2532
+ * that defaults set in the initial `$watch(...)` are preserved when the
2533
+ * parent doesn't provide a value.
2534
+ * - **Backward sync** (`state → props`): when the component mutates
2535
+ * `$state[key]`, the new value is written back to `$props[key]`, which the
2536
+ * call site's `&key={...}` binding picks up and propagates to the parent.
2537
+ *
2538
+ * Same-value writes are no-ops (handled by the proxy layer), so two-way sync
2539
+ * stabilises without infinite loops.
2540
+ *
2541
+ * `$bind` syncs same-named keys, so the key must exist on the state object.
2542
+ * A state key named differently from the prop (e.g. `values` against a
2543
+ * `value` prop) would silently do nothing -- the binding reads and writes
2544
+ * keys that nothing else touches -- which is almost impossible to debug. The
2545
+ * key is therefore required to exist on the state object, and `$bind` throws
2546
+ * at setup otherwise.
2547
+ *
2548
+ * The prop does *not* need to exist on `props` yet: when the parent doesn't
2549
+ * pass an optional prop, the compiled props object has no key for it, and
2550
+ * the binding is simply inert (uncontrolled mode) until a value is pushed.
2551
+ *
2552
+ * @param state The component's reactive state (created via `$watch`).
2553
+ * @param props The component's `$props` proxy. When `undefined` (component
2554
+ * called with no props), `$bind` does nothing.
2555
+ * @param keys Keys to sync. Each key must exist on `state`.
2556
+ * @throws When a key does not exist on the state object.
2557
+ */
2558
+ function $bind(state, props, ...keys) {
2559
+ if (props === void 0) return;
2560
+ for (let key of keys.flat()) {
2561
+ if (!(key in state)) throw new Error(`$bind: the key "${key}" does not exist on the state object. $bind syncs same-named keys, so the state object must declare it (e.g. in the initial $watch).`);
2562
+ $run(() => {
2563
+ const v = props[key];
2564
+ if (v !== void 0) state[key] = v;
2565
+ }, `bind:forward:${key}`);
2566
+ let firstBackward = true;
2567
+ $run(() => {
2568
+ const value = state[key];
2569
+ if (!firstBackward) props[key] = value;
2570
+ firstBackward = false;
2571
+ }, `bind:backward:${key}`);
2572
+ }
2573
+ }
1160
2574
  //#endregion
1161
2575
  //#region src/watch/$cache.ts
1162
2576
  /**
@@ -1165,9 +2579,13 @@ function $batch(fn) {
1165
2579
  * @param fn The function containing the signals to compute and cache.
1166
2580
  */
1167
2581
  function $cache(fn) {
1168
- if (context_default.registerComputed === null) throw new Error("$cache must be used in a getter");
2582
+ if (context.registerComputed === null) {
2583
+ if (typeof window !== "undefined") throw new Error("$cache must be used in a getter");
2584
+ return fn();
2585
+ }
1169
2586
  let computed = {
1170
- type: COMPUTED_TYPE,
2587
+ type: 1,
2588
+ isAsync: false,
1171
2589
  value: null,
1172
2590
  run: fn,
1173
2591
  firstSource: null,
@@ -1175,74 +2593,219 @@ function $cache(fn) {
1175
2593
  recalc: false,
1176
2594
  running: false,
1177
2595
  didError: false,
2596
+ didSuspend: false,
2597
+ generation: 0,
2598
+ hasResolved: false,
2599
+ lastErrored: false,
2600
+ suspendQuiet: false,
2601
+ staleValue: void 0,
1178
2602
  rollback: null
1179
2603
  };
1180
- context_default.registerComputed(computed);
2604
+ context.registerComputed(computed);
1181
2605
  runComputed(computed);
2606
+ if (computed.value !== null && computed.value !== void 0 && typeof computed.value.then === "function") throw new Error("$cache returned a Promise — use $async for async getters");
1182
2607
  return computed.value;
1183
2608
  }
1184
-
1185
2609
  //#endregion
1186
- //#region src/watch/$mount.ts
2610
+ //#region src/watch/$onmount.ts
1187
2611
  /**
1188
- * Runs and re-runs a function on component mount
2612
+ * Runs `fn` once after the component is mounted to the DOM. May return a
2613
+ * cleanup function that runs on unmount / region clear.
2614
+ *
2615
+ * The callback is **not reactive**: it runs exactly once per mount, and
2616
+ * reactive values it reads are not tracked. To set up something that should
2617
+ * keep updating, wrap the reactive part in `$run`:
2618
+ *
2619
+ * ```ts
2620
+ * $onmount(() => {
2621
+ * $run(() => {
2622
+ * label.textContent = $state.count > 0 ? "+" : "";
2623
+ * });
2624
+ * });
2625
+ * ```
2626
+ *
2627
+ * The element-level equivalent is the `onmount` attribute:
2628
+ * `<input onmount={(el) => el.focus()} />`.
1189
2629
  *
1190
2630
  * @param fn The function to run, which may return a cleanup function
1191
2631
  */
1192
- function $mount(fn) {
1193
- context_default.mountEffects.push(fn);
2632
+ function $onmount(fn) {
2633
+ context.mountEffects.push(fn);
1194
2634
  }
1195
-
1196
2635
  //#endregion
1197
- //#region src/watch/$peek.ts
1198
- function $peek(fn) {
1199
- const oldActiveTarget = context_default.activeTarget;
1200
- context_default.activeTarget = null;
1201
- const result = fn();
1202
- context_default.activeTarget = oldActiveTarget;
1203
- return result;
2636
+ //#region src/watch/$pending.ts
2637
+ /**
2638
+ * Returns `true` if any value read inside `fn` is currently suspended in a
2639
+ * "loud" way — a `$async` getter whose promise hasn't resolved yet. A reactive
2640
+ * query for inline "loading…" indicators: the calling effect subscribes to the
2641
+ * same computeds, so `$pending` re-evaluates when they resolve.
2642
+ *
2643
+ * Uses a "peek mode" internally: reads during `fn` are tracked for
2644
+ * subscription but don't propagate taint or notify a `@await` boundary.
2645
+ * This lets `$pending` return a plain boolean without itself suspending.
2646
+ *
2647
+ * Quiet-on-refresh semantics (ASYNC.md §7.4): a suspend is *quiet* — and
2648
+ * therefore `$pending` returns `false` for it — when the computed has resolved
2649
+ * before and the re-fetch was a *silent* `$refresh(fn, { silent: true })` (a
2650
+ * bare refresh with no tracked dependency change, used for background
2651
+ * revalidation). This matches Solid's stale-while-revalidate default: quietly
2652
+ * re-asking the same question shouldn't ping the user. First loads and
2653
+ * dependency-change refreshes are loud (`true`), as is the default (loud)
2654
+ * `$refresh(fn)`. Quiet-ness is captured per computed at suspend time via
2655
+ * `suspendQuiet` (computed in `$async`'s run from `hasResolved` and `recalc`).
2656
+ *
2657
+ * @param fn A function that reads the reactive values to check.
2658
+ */
2659
+ function $pending(fn) {
2660
+ const oldPeek = context.suspendPeek;
2661
+ context.suspendPeek = true;
2662
+ context.suspendPeekHit = false;
2663
+ try {
2664
+ fn();
2665
+ return context.suspendPeekHit;
2666
+ } finally {
2667
+ context.suspendPeek = oldPeek;
2668
+ }
1204
2669
  }
1205
-
1206
2670
  //#endregion
1207
- //#region src/wrappers/ReactiveDate.ts
1208
- let initialized = false;
2671
+ //#region src/watch/$refresh.ts
1209
2672
  /**
1210
- * Wraps a Date object for $watching.
2673
+ * Re-fetches the `$async` getters read inside `fn` without changing a
2674
+ * dependency. A companion to `$pending` (ASYNC.md §6.2): pull-to-refresh,
2675
+ * refresh buttons, refetch-on-focus, polling, retry-after-error.
2676
+ *
2677
+ * `fn` is run in a tracking context that collects every `$async` computed it
2678
+ * reads (`$cache` computeds are ignored). Each collected computed is then
2679
+ * re-run with `recalc` left true — a "bare refresh" — so the suspend is
2680
+ * *loud*: `$pending(fn)` returns `true` while the re-fetch is in flight and
2681
+ * subscribers are notified at suspend *start*, so an inline "updating…"
2682
+ * indicator appears immediately. Readers keep displaying the previous
2683
+ * resolved value (`Computed.staleValue`) until the new promise resolves and
2684
+ * propagates through the reactive graph; an `@await` boundary keeps its
2685
+ * content mounted instead of flashing fallback.
2686
+ *
2687
+ * Pass `{ silent: true }` to re-fetch quietly (stale-while-revalidate):
2688
+ * `$pending(fn)` stays `false` and nothing re-runs until the new promise
2689
+ * resolves. For background revalidation where feedback would be noise —
2690
+ * polling, refetch-on-focus:
2691
+ *
2692
+ * ```torp
2693
+ * // poll quietly every 30s
2694
+ * $run(() => {
2695
+ * const id = setInterval(() => $refresh(() => $state.data, { silent: true }), 30_000);
2696
+ * return () => clearInterval(id);
2697
+ * });
2698
+ * ```
2699
+ *
2700
+ * A refresh is loud regardless of `silent` when the computed has never
2701
+ * resolved — e.g. retrying after an error that left `didError` set on a first
2702
+ * load — or when the re-fetch is triggered by a dependency change, which
2703
+ * flows through the normal `recalc` path instead of here.
2704
+ *
2705
+ * If `fn` reads a getter the UI has never read, the collection read
2706
+ * initializes it — and that fetch IS the refresh: the computed is not re-run
2707
+ * (a second fetch would be a duplicate whose resolve the generation guard
2708
+ * drops). Getters already read (pending, resolved, or errored) are re-run as
2709
+ * usual.
2710
+ *
2711
+ * The returned value of `fn` is ignored; use `fn` to reference the getters.
1211
2712
  */
1212
- var ReactiveDate = class ReactiveDate extends Date {
1213
- constructor(...params) {
1214
- super(...params);
1215
- if (!initialized) this.#init();
1216
- this[proxyHandledSymbol] = true;
1217
- this[proxyDataSymbol] = {
1218
- target: this,
1219
- isArray: false,
1220
- shallow: true,
1221
- signals: /* @__PURE__ */ new Map()
1222
- };
2713
+ function $refresh(fn, options) {
2714
+ const silent = options?.silent === true;
2715
+ const oldRefreshSignals = context.refreshSignals;
2716
+ const oldPeek = context.suspendPeek;
2717
+ const oldInitialized = context.refreshInitialized;
2718
+ context.refreshSignals = [];
2719
+ context.suspendPeek = true;
2720
+ context.refreshInitialized = /* @__PURE__ */ new Set();
2721
+ let unique;
2722
+ let initialized;
2723
+ try {
2724
+ fn();
2725
+ unique = new Set(context.refreshSignals ?? []);
2726
+ initialized = context.refreshInitialized;
2727
+ } finally {
2728
+ context.refreshSignals = oldRefreshSignals;
2729
+ context.suspendPeek = oldPeek;
2730
+ context.refreshInitialized = oldInitialized;
1223
2731
  }
1224
- #init() {
1225
- initialized = true;
1226
- let reactivePrototype = ReactiveDate.prototype;
1227
- let datePrototype = Date.prototype;
1228
- const methods = Object.getOwnPropertyNames(datePrototype);
1229
- for (const method of methods) {
1230
- if (method.startsWith("get") || method.startsWith("to") || method === "valueOf") reactivePrototype[method] = function(...args) {
1231
- const data = this[proxyDataSymbol];
1232
- const result = datePrototype[method].apply(this, args);
1233
- trackProxySignal(data, "#time");
1234
- return result;
1235
- };
1236
- if (method.startsWith("set")) reactivePrototype[method] = function(...args) {
1237
- const data = this[proxyDataSymbol];
1238
- const result = datePrototype[method].apply(this, args);
1239
- propagateSignal(data, "#time");
1240
- return result;
1241
- };
2732
+ for (const computed of unique) {
2733
+ if (initialized.has(computed)) continue;
2734
+ computed.recalc = !silent;
2735
+ deactivateSources(computed);
2736
+ const oldActiveTarget = context.activeTarget;
2737
+ context.activeTarget = null;
2738
+ try {
2739
+ runComputed(computed);
2740
+ } finally {
2741
+ context.activeTarget = oldActiveTarget;
2742
+ }
2743
+ computed.recalc = false;
2744
+ clearSources(computed);
2745
+ if (!silent) {
2746
+ batchStart();
2747
+ propagateFromSignal(computed);
2748
+ batchEnd();
1242
2749
  }
1243
2750
  }
1244
- };
1245
-
2751
+ }
1246
2752
  //#endregion
1247
- export { $batch, $cache, $mount, $peek, $run, $unwrap, $watch, ReactiveDate, clearLayoutSlot, fillLayoutSlot, hydrate, mount, addFragment as t_add_fragment, nodeAnchor as t_anchor, addAnimation as t_animate, applyProps as t_apply_props, setAttribute as t_attribute, nodeChild as t_child, buildClasses as t_class, setDynamicElement as t_dynamic, addEvent as t_event, formatText as t_fmt, getFragment as t_fragment, newListItem as t_list_item, nodeNext as t_next, popRegion as t_pop_region, pushRegion as t_push_region, newRegion as t_region, nodeRoot as t_root, runControlBranch as t_run_branch, runControl as t_run_control, runList as t_run_list, nodeSkip as t_skip, buildStyles as t_style };
2753
+ //#region src/watch/$stream.ts
2754
+ /**
2755
+ * Subscribes to an external source of events — server-sent events,
2756
+ * WebSockets, DOM events, or any custom `StreamSource` — and calls
2757
+ * `handler` for each event it pushes. Events are typically written into
2758
+ * reactive state, which keeps templates, computeds and effects updating
2759
+ * through the normal reactivity model:
2760
+ *
2761
+ * ```torp
2762
+ * let $state = $watch({ messages: [] as string[] });
2763
+ *
2764
+ * $stream(fromServer(`/sse/${$props.id}`), (e) => {
2765
+ * $state.messages.push(e.data);
2766
+ * });
2767
+ * ```
2768
+ *
2769
+ * The subscription is managed by the framework:
2770
+ *
2771
+ * - It starts when the component is mounted to the DOM (so `&ref`-bound
2772
+ * elements are available), and is unsubscribed when the component
2773
+ * unmounts or its region is cleared.
2774
+ * - Reactive state read *inside* the source is tracked: when it changes,
2775
+ * the source is unsubscribed and re-subscribed with fresh values (e.g.
2776
+ * re-opening a connection when a user id changes). A pending debounced
2777
+ * call is dropped on re-subscribe.
2778
+ * - The source is never invoked during a server render, so browser-only
2779
+ * APIs are safe to use.
2780
+ *
2781
+ * Errors are values like any other: a source that can fail should push an
2782
+ * error-shaped value and own its own reconnection (e.g. `EventSource`
2783
+ * reconnects automatically).
2784
+ *
2785
+ * @param source The stream source to subscribe to
2786
+ * @param handler Called for each event the source pushes
2787
+ * @param options Timing options. `debounce` delays each handler call until
2788
+ * the source has been quiet for that many milliseconds, resetting on
2789
+ * every event (only the last event in a burst is handled).
2790
+ */
2791
+ function $stream(source, handler, options) {
2792
+ context.mountEffects.push(() => {
2793
+ let timer;
2794
+ $run(() => {
2795
+ const unsub = source((value) => {
2796
+ if (options?.debounce) {
2797
+ clearTimeout(timer);
2798
+ timer = setTimeout(() => handler(value), options.debounce);
2799
+ } else handler(value);
2800
+ });
2801
+ return () => {
2802
+ unsub?.();
2803
+ clearTimeout(timer);
2804
+ };
2805
+ });
2806
+ });
2807
+ }
2808
+ //#endregion
2809
+ export { $async, $batch, $bind, $cache, $onmount, $peek, $pending, $refresh, $run, $stream, $unwrap, $watch, clearLayoutSlot, fillLayoutSlot, fromElement, fromServer, fromWebSocket, hydrate, mount, addElement as t_add_element, addFragment as t_add_fragment, nodeAnchor as t_anchor, addAnimation as t_animate, applyProps as t_apply_props, setAttribute as t_attribute, nodeChild as t_child, buildClasses as t_class, setDynamicElement as t_dynamic, addEvent as t_event, formatText as t_fmt, getFragment as t_fragment, getElementFragment as t_fragment_el, newListItem as t_list_item, nodeNext as t_next, popRegion as t_pop_region, pushRegion as t_push_region, newRegion as t_region, rerunRegionEffects as t_rerun_region_effects, restoreHydration as t_restore_hydration, nodeRoot as t_root, nodeRootElement as t_root_el, runAwait as t_run_await, runControlBranch as t_run_branch, runControl as t_run_control, runList as t_run_list, runTry as t_run_try, saveHydration as t_save_hydration, nodeSkip as t_skip, buildStyles as t_style, unmount };
2810
+
1248
2811
  //# sourceMappingURL=index.mjs.map