ripple 0.3.128 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/CHANGELOG.md +294 -0
  2. package/package.json +11 -4
  3. package/src/jsx-runtime.d.ts +12 -2
  4. package/src/runtime/element.js +23 -6
  5. package/src/runtime/index-client.js +81 -41
  6. package/src/runtime/index-server.js +1 -0
  7. package/src/runtime/internal/client/blocks.js +173 -91
  8. package/src/runtime/internal/client/component.js +4 -30
  9. package/src/runtime/internal/client/composite.js +12 -13
  10. package/src/runtime/internal/client/constants.js +9 -0
  11. package/src/runtime/internal/client/context.js +12 -19
  12. package/src/runtime/internal/client/css.js +7 -9
  13. package/src/runtime/internal/client/events.js +178 -90
  14. package/src/runtime/internal/client/expression.js +45 -25
  15. package/src/runtime/internal/client/for.js +409 -129
  16. package/src/runtime/internal/client/head.js +10 -5
  17. package/src/runtime/internal/client/html.js +1 -0
  18. package/src/runtime/internal/client/hydration.js +152 -10
  19. package/src/runtime/internal/client/if.js +239 -50
  20. package/src/runtime/internal/client/index.js +4 -12
  21. package/src/runtime/internal/client/operations.js +40 -61
  22. package/src/runtime/internal/client/portal.js +163 -85
  23. package/src/runtime/internal/client/render.js +44 -43
  24. package/src/runtime/internal/client/rpc.js +3 -3
  25. package/src/runtime/internal/client/runtime.js +592 -433
  26. package/src/runtime/internal/client/selector.js +5 -2
  27. package/src/runtime/internal/client/template.js +133 -158
  28. package/src/runtime/internal/client/transport.js +38 -0
  29. package/src/runtime/internal/client/try.js +465 -426
  30. package/src/runtime/internal/client/types.d.ts +56 -23
  31. package/src/runtime/internal/client/utils.js +1 -3
  32. package/src/runtime/internal/server/dynamic.js +20 -21
  33. package/src/runtime/internal/server/index.js +329 -277
  34. package/src/runtime/internal/server/rpc.js +3 -2
  35. package/src/runtime/internal/server/transport.js +16 -0
  36. package/src/runtime/transport.js +21 -0
  37. package/tests/client/__snapshots__/for.test.tsrx.snap +0 -5
  38. package/tests/client/array/array.copy-within.test.tsrx +12 -12
  39. package/tests/client/array/array.derived.test.tsrx +46 -46
  40. package/tests/client/array/array.iteration.test.tsrx +10 -10
  41. package/tests/client/array/array.mutations.test.tsrx +20 -20
  42. package/tests/client/array/array.static.test.tsrx +11 -11
  43. package/tests/client/array/array.to-methods.test.tsrx +6 -6
  44. package/tests/client/async-suspend.test.tsrx +74 -75
  45. package/tests/client/basic/basic.attributes.test.tsrx +123 -83
  46. package/tests/client/basic/basic.collections.test.tsrx +12 -12
  47. package/tests/client/basic/basic.components.test.tsrx +99 -25
  48. package/tests/client/basic/basic.errors.test.tsrx +18 -18
  49. package/tests/client/basic/basic.events.test.tsrx +42 -42
  50. package/tests/client/basic/basic.get-set.test.tsrx +8 -8
  51. package/tests/client/basic/basic.reactivity.test.tsrx +73 -38
  52. package/tests/client/basic/basic.rendering.test.tsrx +25 -25
  53. package/tests/client/basic/basic.utilities.test.tsrx +3 -3
  54. package/tests/client/boundaries.test.tsrx +8 -8
  55. package/tests/client/boxed-locals.test.tsrx +452 -0
  56. package/tests/client/compiler/__snapshots__/compiler.assignments.test.tsrx.snap +6 -6
  57. package/tests/client/compiler/compiler.assignments.test.tsrx +9 -73
  58. package/tests/client/compiler/compiler.attributes.test.tsrx +23 -23
  59. package/tests/client/compiler/compiler.basic.test.tsrx +15 -60
  60. package/tests/client/compiler/compiler.tracked-access.test.tsrx +18 -43
  61. package/tests/client/compiler/compiler.try-in-function.test.tsrx +2 -2
  62. package/tests/client/composite/__snapshots__/composite.render.test.tsrx.snap +0 -1
  63. package/tests/client/composite/composite.dynamic-components.test.tsrx +77 -6
  64. package/tests/client/composite/composite.props.test.tsrx +149 -12
  65. package/tests/client/composite/composite.reactivity.test.tsrx +51 -52
  66. package/tests/client/composite/composite.render.test.tsrx +26 -9
  67. package/tests/client/css/scoped-styles.test.tsrx +8 -8
  68. package/tests/client/date.test.tsrx +42 -42
  69. package/tests/client/derived-release.test.tsrx +370 -0
  70. package/tests/client/dynamic-elements.test.tsrx +45 -45
  71. package/tests/client/events.test.tsrx +97 -71
  72. package/tests/client/for-selector.test.tsrx +14 -14
  73. package/tests/client/for-single-root-items.test.tsrx +118 -3
  74. package/tests/client/for.test.tsrx +332 -31
  75. package/tests/client/head.test.tsrx +19 -19
  76. package/tests/client/html.test.tsrx +11 -9
  77. package/tests/client/if-block.test.tsrx +80 -0
  78. package/tests/client/map.test.tsrx +16 -16
  79. package/tests/client/media-query.test.tsrx +7 -7
  80. package/tests/client/portal.test.tsrx +224 -26
  81. package/tests/client/primitive-text.test.tsrx +50 -0
  82. package/tests/client/prop-kind-switch.test.tsrx +141 -0
  83. package/tests/client/ref.test.tsrx +17 -17
  84. package/tests/client/return.test.tsrx +26 -3
  85. package/tests/client/root-boundary.test.tsrx +42 -0
  86. package/tests/client/scoped-flush.test.tsrx +8 -8
  87. package/tests/client/set.test.tsrx +6 -6
  88. package/tests/client/svg.test.tsrx +11 -11
  89. package/tests/client/switch.test.tsrx +44 -44
  90. package/tests/client/track-async-hydration.test.tsrx +4 -4
  91. package/tests/client/try.test.tsrx +151 -83
  92. package/tests/client/tsx.test.tsrx +43 -40
  93. package/tests/client/url/url.derived.test.tsrx +6 -6
  94. package/tests/client/url-search-params/url-search-params.derived.test.tsrx +8 -8
  95. package/tests/client/url-search-params/url-search-params.iteration.test.tsrx +10 -10
  96. package/tests/client/url-search-params/url-search-params.mutation.test.tsrx +10 -10
  97. package/tests/client/url-search-params/url-search-params.retrieval.test.tsrx +18 -18
  98. package/tests/client/url-search-params/url-search-params.serialization.test.tsrx +2 -2
  99. package/tests/hydration/basic.test.js +74 -1
  100. package/tests/hydration/build-components.js +22 -1
  101. package/tests/hydration/compiled/client/basic.js +1142 -713
  102. package/tests/hydration/compiled/client/composite.js +151 -100
  103. package/tests/hydration/compiled/client/events.js +184 -171
  104. package/tests/hydration/compiled/client/for.js +1472 -1132
  105. package/tests/hydration/compiled/client/fragment-cursor.js +2341 -0
  106. package/tests/hydration/compiled/client/head.js +254 -201
  107. package/tests/hydration/compiled/client/hmr.js +54 -41
  108. package/tests/hydration/compiled/client/html-in-template.js +54 -39
  109. package/tests/hydration/compiled/client/html.js +1633 -1263
  110. package/tests/hydration/compiled/client/if-children.js +420 -270
  111. package/tests/hydration/compiled/client/if-fragment-controlflow.js +363 -317
  112. package/tests/hydration/compiled/client/if.js +229 -219
  113. package/tests/hydration/compiled/client/mixed-control-flow.js +420 -404
  114. package/tests/hydration/compiled/client/nested-control-flow.js +1326 -1277
  115. package/tests/hydration/compiled/client/portal.js +87 -95
  116. package/tests/hydration/compiled/client/reactivity.js +135 -101
  117. package/tests/hydration/compiled/client/return.js +28 -19
  118. package/tests/hydration/compiled/client/streaming.js +390 -306
  119. package/tests/hydration/compiled/client/switch.js +258 -266
  120. package/tests/hydration/compiled/client/track-async-serialization.js +448 -280
  121. package/tests/hydration/compiled/client/try.js +173 -129
  122. package/tests/hydration/compiled/client/typed-text.js +71 -0
  123. package/tests/hydration/compiled/fixtures/money.js +10 -0
  124. package/tests/hydration/compiled/server/basic.js +123 -6
  125. package/tests/hydration/compiled/server/composite.js +16 -30
  126. package/tests/hydration/compiled/server/events.js +66 -18
  127. package/tests/hydration/compiled/server/for.js +117 -41
  128. package/tests/hydration/compiled/server/fragment-cursor.js +1687 -0
  129. package/tests/hydration/compiled/server/head.js +20 -20
  130. package/tests/hydration/compiled/server/hmr.js +2 -2
  131. package/tests/hydration/compiled/server/html.js +70 -238
  132. package/tests/hydration/compiled/server/if-children.js +107 -15
  133. package/tests/hydration/compiled/server/if.js +11 -15
  134. package/tests/hydration/compiled/server/mixed-control-flow.js +8 -8
  135. package/tests/hydration/compiled/server/portal.js +14 -22
  136. package/tests/hydration/compiled/server/reactivity.js +14 -14
  137. package/tests/hydration/compiled/server/streaming.js +19 -19
  138. package/tests/hydration/compiled/server/switch.js +8 -8
  139. package/tests/hydration/compiled/server/track-async-serialization.js +150 -23
  140. package/tests/hydration/compiled/server/try.js +8 -8
  141. package/tests/hydration/compiled/server/typed-text.js +17 -0
  142. package/tests/hydration/components/basic.tsrx +44 -8
  143. package/tests/hydration/components/composite.tsrx +2 -2
  144. package/tests/hydration/components/events.tsrx +25 -25
  145. package/tests/hydration/components/for.tsrx +115 -67
  146. package/tests/hydration/components/fragment-cursor.tsrx +650 -0
  147. package/tests/hydration/components/head.tsrx +16 -16
  148. package/tests/hydration/components/hmr.tsrx +2 -2
  149. package/tests/hydration/components/html.tsrx +7 -7
  150. package/tests/hydration/components/if-children.tsrx +72 -25
  151. package/tests/hydration/components/if.tsrx +18 -18
  152. package/tests/hydration/components/mixed-control-flow.tsrx +11 -11
  153. package/tests/hydration/components/portal.tsrx +3 -3
  154. package/tests/hydration/components/reactivity.tsrx +16 -16
  155. package/tests/hydration/components/streaming.tsrx +20 -20
  156. package/tests/hydration/components/switch.tsrx +20 -20
  157. package/tests/hydration/components/text-types.ts +4 -0
  158. package/tests/hydration/components/track-async-serialization.tsrx +80 -26
  159. package/tests/hydration/components/try.tsrx +8 -8
  160. package/tests/hydration/components/typed-text.tsrx +22 -0
  161. package/tests/hydration/fixtures/money.js +10 -0
  162. package/tests/hydration/for.test.js +36 -0
  163. package/tests/hydration/fragment-cursor.test.js +111 -0
  164. package/tests/hydration/if-children.test.js +24 -0
  165. package/tests/hydration/streaming.test.js +19 -0
  166. package/tests/hydration/track-async-serialization.test.js +87 -2
  167. package/tests/hydration/tsconfig.text-types.json +10 -0
  168. package/tests/hydration/typed-text.test.js +34 -0
  169. package/tests/server/await.test.tsrx +3 -3
  170. package/tests/server/basic.attributes.test.tsrx +70 -42
  171. package/tests/server/basic.components.test.tsrx +64 -8
  172. package/tests/server/basic.test.tsrx +34 -32
  173. package/tests/server/compiler.test.tsrx +1 -70
  174. package/tests/server/composite.props.test.tsrx +6 -6
  175. package/tests/server/dynamic-elements.test.tsrx +31 -8
  176. package/tests/server/for.test.tsrx +94 -0
  177. package/tests/server/head.test.tsrx +11 -11
  178. package/tests/server/scoped-styles.test.tsrx +2 -2
  179. package/tests/server/streaming-ssr.test.tsrx +48 -43
  180. package/tests/server/track-async-serialization.test.tsrx +204 -21
  181. package/tests/server/try.test.tsrx +48 -48
  182. package/tests/types/transport.ts +40 -0
  183. package/tests/utils/jsx-runtime-types.test.js +67 -2
  184. package/tests/utils/tracked-types.test.js +117 -0
  185. package/tests/utils/vite-plugin-config.test.js +23 -0
  186. package/types/index.d.ts +49 -9
  187. package/types/server.d.ts +3 -0
  188. package/types/transport.d.ts +15 -0
  189. package/src/runtime/internal/client/switch.js +0 -113
  190. package/src/utils/errors.js +0 -13
  191. package/tests/client/compiler/__snapshots__/compiler.assignments.test.rsrx.snap +0 -12
  192. package/tests/client/compiler/__snapshots__/compiler.typescript.test.rsrx.snap +0 -46
  193. package/tests/client/lazy-array.test.tsrx +0 -31
  194. package/tests/client/lazy-destructuring.test.tsrx +0 -438
  195. package/tests/client/tracked-index-access.test.tsrx +0 -113
  196. package/tests/server/lazy-destructuring.test.tsrx +0 -595
  197. package/tests/server/tracked-index-access.test.tsrx +0 -76
@@ -16,14 +16,15 @@ import {
16
16
  set_tracking,
17
17
  tracking,
18
18
  } from './runtime.js';
19
- import { array_from, define_property, is_array } from '@tsrx/core/runtime/language-helpers';
19
+ import { define_property, is_array } from '@tsrx/core/runtime/language-helpers';
20
20
  import { render } from './blocks.js';
21
21
 
22
22
  /** @type {Set<string>} */
23
23
  var all_registered_events = new Set();
24
24
 
25
- /** @type {Set<(events: Array<string>) => void>} */
26
- var root_event_handles = new Set();
25
+ /**
26
+ * @typedef {{ count: number, registered_events: Set<string>, target: Element }} RootTargetRef
27
+ */
27
28
 
28
29
  /**
29
30
  * Active root delegation targets, ref-counted per target. Multiple callers of
@@ -32,10 +33,13 @@ var root_event_handles = new Set();
32
33
  * nested mounts. The shared delegated listeners for a target may only be torn
33
34
  * down once every caller for that target has released it, otherwise
34
35
  * unmounting one Portal silently kills event delegation for its siblings.
35
- * @type {Map<Element, { count: number, registered_events: Set<string> }>}
36
+ * @type {Map<Element, RootTargetRef>}
36
37
  */
37
38
  var root_target_refs = new Map();
38
39
 
40
+ /** @type {RootTargetRef | null} */
41
+ var last_root_ref = null;
42
+
39
43
  /**
40
44
  * Delegated handling only works for elements strictly below a root delegation
41
45
  * target: the root listener's propagation walk never visits the target itself
@@ -271,30 +275,10 @@ function create_event(event_name, dom, handler, options) {
271
275
  if (is_delegated) {
272
276
  var prop = '__' + event_name;
273
277
  var target = /** @type {DelegatedEventTarget} */ (dom);
274
- var current = target[prop];
275
-
276
- if (current === undefined) {
277
- target[prop] = handler;
278
- } else if (is_array(current)) {
279
- if (!current.includes(handler)) {
280
- current.push(handler);
281
- }
282
- } else {
283
- if (current !== handler) {
284
- target[prop] = [current, handler];
285
- }
286
- }
287
-
278
+ add_delegated_handler(target, prop, handler);
288
279
  delegate([event_name]);
289
280
  return () => {
290
- var handlers = target[prop];
291
- if (is_array(handlers)) {
292
- var filtered = handlers.filter((h) => h !== handler);
293
- target[prop] =
294
- filtered.length === 0 ? undefined : filtered.length === 1 ? filtered[0] : filtered;
295
- } else {
296
- target[prop] = undefined;
297
- }
281
+ remove_delegated_handler(target, prop, handler);
298
282
  };
299
283
  }
300
284
 
@@ -336,12 +320,107 @@ function create_event(event_name, dom, handler, options) {
336
320
  }
337
321
 
338
322
  /**
323
+ * Delegated event name resolution per compiler-emitted name (`"Click"`,
324
+ * `"PointerMove"`, ...): the lowercase DOM name, or null when the event cannot
325
+ * be delegated (capture variants, non-bubbling events).
326
+ * @type {Map<string, string | null>}
327
+ */
328
+ var delegated_event_names = new Map();
329
+
330
+ /**
331
+ * @param {string} event_name
332
+ * @returns {string | null}
333
+ */
334
+ function get_delegated_event_name(event_name) {
335
+ var name = delegated_event_names.get(event_name);
336
+
337
+ if (name === undefined) {
338
+ if (is_capture_event(event_name)) {
339
+ name = null;
340
+ } else {
341
+ name = event_name.toLowerCase();
342
+ if (is_non_delegated(name)) {
343
+ name = null;
344
+ }
345
+ }
346
+ delegated_event_names.set(event_name, name);
347
+ }
348
+
349
+ return name;
350
+ }
351
+
352
+ /**
353
+ * @param {DelegatedEventTarget} target
354
+ * @param {string} prop
355
+ * @param {EventListener} handler
356
+ * @returns {void}
357
+ */
358
+ function add_delegated_handler(target, prop, handler) {
359
+ var current = target[prop];
360
+
361
+ if (current === undefined) {
362
+ target[prop] = handler;
363
+ } else if (is_array(current)) {
364
+ if (!current.includes(handler)) {
365
+ current.push(handler);
366
+ }
367
+ } else {
368
+ if (current !== handler) {
369
+ target[prop] = [current, handler];
370
+ }
371
+ }
372
+ }
373
+
374
+ /**
375
+ * @param {DelegatedEventTarget} target
376
+ * @param {string} prop
377
+ * @param {EventListener} handler
378
+ * @returns {void}
379
+ */
380
+ function remove_delegated_handler(target, prop, handler) {
381
+ var handlers = target[prop];
382
+ if (is_array(handlers)) {
383
+ var filtered = handlers.filter((h) => h !== handler);
384
+ target[prop] =
385
+ filtered.length === 0 ? undefined : filtered.length === 1 ? filtered[0] : filtered;
386
+ } else {
387
+ target[prop] = undefined;
388
+ }
389
+ }
390
+
391
+ /**
392
+ * Compiler-emitted one-shot listener for a static `onEvent={handler}`: the
393
+ * element's lifetime owns the handler, so nothing is returned for cleanup.
339
394
  * @param {string} event_name
340
395
  * @param {Element} dom
341
396
  * @param {EventListener | AddEventObject} handler
342
- * @returns {() => void}
397
+ * @returns {void}
343
398
  */
344
399
  export function event(event_name, dom, handler) {
400
+ if (typeof handler === 'function') {
401
+ // Plain handler: the common compiled `onClick={fn}` shape. Resolve the
402
+ // delegated name from the cache and skip the options plumbing.
403
+ var name = get_delegated_event_name(event_name);
404
+
405
+ if (name !== null) {
406
+ add_delegated_handler(/** @type {DelegatedEventTarget} */ (dom), '__' + name, handler);
407
+ delegate([name]);
408
+ return;
409
+ }
410
+ }
411
+
412
+ event_listener(event_name, dom, handler);
413
+ }
414
+
415
+ /**
416
+ * Attaches a listener and returns its remover; `event()` is the one-shot
417
+ * compiled form of this.
418
+ * @param {string} event_name
419
+ * @param {Element} dom
420
+ * @param {EventListener | AddEventObject} handler
421
+ * @returns {() => void}
422
+ */
423
+ export function event_listener(event_name, dom, handler) {
345
424
  /** @type AddEventOptions */
346
425
  var options = {};
347
426
  /** @type {EventListener} */
@@ -350,7 +429,7 @@ export function event(event_name, dom, handler) {
350
429
  if (typeof handler === 'object' && 'handleEvent' in handler) {
351
430
  ({ handleEvent: event_handler, ...options } = handler);
352
431
  } else {
353
- event_handler = handler;
432
+ event_handler = /** @type {EventListener} */ (handler);
354
433
  }
355
434
 
356
435
  return create_event(event_name, dom, event_handler, options);
@@ -381,7 +460,7 @@ export function render_event(event_name, dom, get_handler) {
381
460
  prev = handler;
382
461
 
383
462
  if (handler) {
384
- remove_listener = event(event_name, dom, handler);
463
+ remove_listener = event_listener(event_name, dom, handler);
385
464
  }
386
465
  }
387
466
  });
@@ -392,77 +471,86 @@ export function render_event(event_name, dom, get_handler) {
392
471
  * @returns {void}
393
472
  */
394
473
  export function delegate(events) {
474
+ var added = false;
475
+
395
476
  for (var i = 0; i < events.length; i++) {
396
- all_registered_events.add(events[i]);
477
+ var event_name = events[i];
478
+
479
+ if (!all_registered_events.has(event_name)) {
480
+ all_registered_events.add(event_name);
481
+ added = true;
482
+ }
397
483
  }
398
484
 
399
- for (var fn of root_event_handles) {
400
- fn(events);
485
+ // Every root target already listens for every previously registered event,
486
+ // so only a new event name needs to fan out to the targets.
487
+ if (added) {
488
+ for (var ref of root_target_refs.values()) {
489
+ register_root_events(ref, events);
490
+ }
401
491
  }
402
492
  }
403
493
 
404
- /** @param {Element} target */
405
- export function handle_root_events(target) {
406
- var ref = root_target_refs.get(target) ?? {
407
- count: 0,
408
- registered_events: /** @type {Set<string>} */ (new Set()),
409
- };
410
- ref.count += 1;
411
- root_target_refs.set(target, ref);
494
+ /**
495
+ * @param {RootTargetRef} ref
496
+ * @param {Iterable<string>} events
497
+ */
498
+ function register_root_events(ref, events) {
412
499
  var registered_events = ref.registered_events;
500
+ var target = ref.target;
413
501
 
414
- /**
415
- * @typedef {Object} EventHandleOptions
416
- * @property {boolean} [passive]
417
- */
418
-
419
- /**
420
- * @typedef {(
421
- * events: Array<string>
422
- * ) => void} EventHandle
423
- */
424
-
425
- /** @type {EventHandle} */
426
- var event_handle = (/** @type {Array<string>} */ events) => {
427
- for (var i = 0; i < events.length; i++) {
428
- var event_name = events[i];
429
-
430
- if (registered_events.has(event_name)) continue;
431
- registered_events.add(event_name);
502
+ for (var event_name of events) {
503
+ if (registered_events.has(event_name)) continue;
504
+ registered_events.add(event_name);
432
505
 
433
- /** @type {boolean} */
434
- var passive = is_passive_event(event_name);
435
-
436
- /** @type {EventHandleOptions} */
437
- var options = { passive };
438
-
439
- target.addEventListener(event_name, handle_event_propagation, options);
440
- }
441
- };
506
+ target.addEventListener(event_name, handle_event_propagation, {
507
+ passive: is_passive_event(event_name),
508
+ });
509
+ }
510
+ }
442
511
 
443
- event_handle(array_from(all_registered_events));
444
- root_event_handles.add(event_handle);
512
+ /**
513
+ * Acquires the delegated root listeners for `target` and returns the shared
514
+ * ref; every acquire must be paired with one `release_root_events` call.
515
+ * @param {Element} target
516
+ * @returns {RootTargetRef}
517
+ */
518
+ export function handle_root_events(target) {
519
+ // Sibling portals mostly share one target, so check the last ref first.
520
+ /** @type {RootTargetRef | undefined} */
521
+ var ref =
522
+ last_root_ref !== null && last_root_ref.target === target
523
+ ? last_root_ref
524
+ : root_target_refs.get(target);
525
+
526
+ if (ref === undefined) {
527
+ ref = { count: 0, registered_events: new Set(), target };
528
+ root_target_refs.set(target, ref);
529
+ register_root_events(ref, all_registered_events);
530
+ }
445
531
 
446
- var released = false;
532
+ ref.count += 1;
533
+ last_root_ref = ref;
534
+ return ref;
535
+ }
447
536
 
448
- return () => {
449
- if (released) return;
450
- released = true;
451
-
452
- root_event_handles.delete(event_handle);
453
-
454
- // The map entry for `target` is always this `ref`: it is only deleted
455
- // when the count hits 0, which requires this cleanup to have run.
456
- ref.count -= 1;
457
- if (ref.count > 0) return;
458
-
459
- // Last caller for this target: actually tear down the shared listeners.
460
- root_target_refs.delete(target);
461
- for (var event_name of registered_events) {
462
- target.removeEventListener(
463
- event_name,
464
- /** @type {EventListener} */ (handle_event_propagation),
465
- );
466
- }
467
- };
537
+ /**
538
+ * @param {RootTargetRef} ref
539
+ * @returns {void}
540
+ */
541
+ export function release_root_events(ref) {
542
+ // The map entry for `ref.target` is always this `ref`: it is only deleted
543
+ // when the count hits 0, which requires every acquirer to have released.
544
+ ref.count -= 1;
545
+ if (ref.count > 0) return;
546
+
547
+ // Last caller for this target: actually tear down the shared listeners.
548
+ var target = ref.target;
549
+ root_target_refs.delete(target);
550
+ if (last_root_ref === ref) {
551
+ last_root_ref = null;
552
+ }
553
+ for (var event_name of ref.registered_events) {
554
+ target.removeEventListener(event_name, /** @type {EventListener} */ (handle_event_propagation));
555
+ }
468
556
  }
@@ -1,23 +1,24 @@
1
- /** @import { Block } from '#client' */
1
+ /** @import { AppendIntoAnchor, Block } from '#client' */
2
2
 
3
3
  import { is_array } from '@tsrx/core/runtime/language-helpers';
4
4
  import { branch, destroy_block, render } from './blocks.js';
5
- import { BRANCH_BLOCK, UNINITIALIZED } from './constants.js';
5
+ import { BRANCH_BLOCK, IF_BLOCK, UNINITIALIZED } from './constants.js';
6
6
  import { create_text, get_next_sibling } from './operations.js';
7
7
  import { assign_nodes } from './template.js';
8
8
  import { active_block } from './runtime.js';
9
9
  import { hydrate_node, hydrating, set_hydrate_node } from './hydration.js';
10
10
  import { COMMENT_NODE, HYDRATION_END, HYDRATION_START, TEXT_NODE } from '../../../constants.js';
11
- import { is_tsrx_element } from '../../element.js';
11
+ import { is_tsrx_element, TSRX_ELEMENT } from '../../element.js';
12
12
 
13
13
  /**
14
- * Finds the nearest enclosing BRANCH_BLOCK in the block hierarchy.
14
+ * Finds the nearest enclosing block that owns a DOM range (a branch or an if
15
+ * block) in the block hierarchy.
15
16
  * @param {Block | null} block
16
17
  * @returns {Block | null}
17
18
  */
18
19
  function find_enclosing_branch(block) {
19
20
  while (block !== null) {
20
- if ((block.f & BRANCH_BLOCK) !== 0) {
21
+ if ((block.f & (BRANCH_BLOCK | IF_BLOCK)) !== 0) {
21
22
  return block;
22
23
  }
23
24
  block = block.p;
@@ -32,13 +33,19 @@ function find_enclosing_branch(block) {
32
33
  * @returns {void}
33
34
  */
34
35
  export function render_value(value, anchor, block) {
35
- if (is_tsrx_element(value)) {
36
- render_tsrx_element(value, anchor, block);
37
- } else if (is_array(value)) {
36
+ // render_tsrx_element, inline: an element's render may return another
37
+ // element (a wrapper), an array, or text.
38
+ var rendered = false;
39
+ // is_tsrx_element, inline: every component render passes through here.
40
+ while (value != null && value[TSRX_ELEMENT] === true) {
41
+ value = value.render(anchor, block, value.p);
42
+ rendered = true;
43
+ }
44
+ if (is_array(value)) {
38
45
  render_tsrx_collection(value, anchor, block);
39
46
  } else if (value != null) {
40
47
  var text = value + '';
41
- if (text !== '') {
48
+ if (rendered || text !== '') {
42
49
  render_tsrx_collection_text(text, anchor, true);
43
50
  }
44
51
  }
@@ -60,11 +67,27 @@ function render_tsrx_collection(value, anchor, block) {
60
67
  var start = document.createComment('');
61
68
  var end = document.createComment('');
62
69
 
63
- anchor.before(start, end);
70
+ insert_before(anchor, start);
71
+ insert_before(anchor, end);
64
72
  assign_nodes(start, end);
65
73
  render_tsrx_collection_items(value, end, block);
66
74
  }
67
75
 
76
+ /**
77
+ * Inserts `node` before `anchor`, or appends it into the parent when `anchor`
78
+ * is an append-into sentinel (see `append_into`).
79
+ * @param {ChildNode | AppendIntoAnchor} anchor
80
+ * @param {Node} node
81
+ * @returns {void}
82
+ */
83
+ function insert_before(anchor, node) {
84
+ if (/** @type {AppendIntoAnchor} */ (anchor).into === true) {
85
+ /** @type {AppendIntoAnchor} */ (anchor).parent.appendChild(node);
86
+ } else {
87
+ /** @type {ChildNode} */ (anchor).before(node);
88
+ }
89
+ }
90
+
68
91
  /**
69
92
  * @param {any[]} value
70
93
  * @param {ChildNode} anchor
@@ -92,7 +115,7 @@ function render_tsrx_collection_items(value, anchor, block) {
92
115
  * @returns {void}
93
116
  */
94
117
  function render_tsrx_element(value, anchor, block) {
95
- var result = value.render(anchor, block);
118
+ var result = value.render(anchor, block, value.p);
96
119
 
97
120
  if (is_tsrx_element(result)) {
98
121
  render_tsrx_element(result, anchor, block);
@@ -112,7 +135,7 @@ function render_tsrx_element(value, anchor, block) {
112
135
  function render_tsrx_collection_text(value, anchor, assign = false) {
113
136
  if (!hydrating) {
114
137
  var text = create_text(value);
115
- anchor.before(text);
138
+ insert_before(anchor, text);
116
139
  if (assign) {
117
140
  assign_nodes(text, text);
118
141
  }
@@ -241,7 +264,7 @@ function run_expression(s) {
241
264
  if (next_is_collection || is_tsrx_element(next_value)) {
242
265
  if (s.i && s.e && s.v === next_value) {
243
266
  if (end !== null) {
244
- advance_hydration(end);
267
+ settle_hydration(end);
245
268
  }
246
269
  return;
247
270
  }
@@ -310,7 +333,7 @@ function run_expression(s) {
310
333
  s.e = true;
311
334
  s.i = true;
312
335
  if (end !== null) {
313
- advance_hydration(end);
336
+ settle_hydration(end);
314
337
  }
315
338
  return;
316
339
  }
@@ -320,7 +343,7 @@ function run_expression(s) {
320
343
 
321
344
  if (s.i && !s.e && s.v === next_text) {
322
345
  if (end !== null) {
323
- advance_hydration(end);
346
+ settle_hydration(end);
324
347
  }
325
348
  return;
326
349
  }
@@ -368,7 +391,7 @@ function run_expression(s) {
368
391
  s.e = false;
369
392
  s.i = true;
370
393
  if (end !== null) {
371
- advance_hydration(end);
394
+ settle_hydration(end);
372
395
  }
373
396
  }
374
397
 
@@ -457,17 +480,14 @@ function clear_expression_range(anchor, end) {
457
480
  }
458
481
 
459
482
  /**
483
+ * Leaves the hydration cursor on the expression's end marker, the last node
484
+ * the expression owns: the parent steps past it with its own sibling
485
+ * navigation, exactly as it does after an element or a control-flow block.
460
486
  * @param {Comment} end
461
487
  * @returns {void}
462
488
  */
463
- function advance_hydration(end) {
464
- if (!hydrating) {
465
- return;
466
- }
467
-
468
- var next = get_next_sibling(end);
469
-
470
- if (next !== null) {
471
- set_hydrate_node(next);
489
+ function settle_hydration(end) {
490
+ if (hydrating) {
491
+ set_hydrate_node(end);
472
492
  }
473
493
  }