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