ripple 0.3.127 → 0.4.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 (192) hide show
  1. package/CHANGELOG.md +293 -0
  2. package/package.json +10 -4
  3. package/src/runtime/element.js +23 -6
  4. package/src/runtime/index-client.js +81 -41
  5. package/src/runtime/index-server.js +1 -0
  6. package/src/runtime/internal/client/blocks.js +192 -114
  7. package/src/runtime/internal/client/component.js +4 -30
  8. package/src/runtime/internal/client/composite.js +12 -13
  9. package/src/runtime/internal/client/constants.js +14 -5
  10. package/src/runtime/internal/client/context.js +12 -19
  11. package/src/runtime/internal/client/css.js +7 -9
  12. package/src/runtime/internal/client/events.js +178 -90
  13. package/src/runtime/internal/client/expression.js +185 -124
  14. package/src/runtime/internal/client/for.js +616 -264
  15. package/src/runtime/internal/client/head.js +10 -5
  16. package/src/runtime/internal/client/html.js +1 -0
  17. package/src/runtime/internal/client/hydration.js +152 -10
  18. package/src/runtime/internal/client/if.js +239 -50
  19. package/src/runtime/internal/client/index.js +9 -13
  20. package/src/runtime/internal/client/operations.js +44 -51
  21. package/src/runtime/internal/client/portal.js +163 -85
  22. package/src/runtime/internal/client/render.js +44 -25
  23. package/src/runtime/internal/client/rpc.js +3 -3
  24. package/src/runtime/internal/client/runtime.js +799 -576
  25. package/src/runtime/internal/client/selector.js +77 -0
  26. package/src/runtime/internal/client/template.js +133 -158
  27. package/src/runtime/internal/client/transport.js +38 -0
  28. package/src/runtime/internal/client/try.js +465 -426
  29. package/src/runtime/internal/client/types.d.ts +65 -21
  30. package/src/runtime/internal/client/utils.js +1 -3
  31. package/src/runtime/internal/server/dynamic.js +20 -21
  32. package/src/runtime/internal/server/index.js +327 -275
  33. package/src/runtime/internal/server/rpc.js +3 -2
  34. package/src/runtime/internal/server/transport.js +16 -0
  35. package/src/runtime/transport.js +21 -0
  36. package/tests/client/__snapshots__/for.test.tsrx.snap +0 -5
  37. package/tests/client/array/array.copy-within.test.tsrx +12 -12
  38. package/tests/client/array/array.derived.test.tsrx +46 -46
  39. package/tests/client/array/array.iteration.test.tsrx +10 -10
  40. package/tests/client/array/array.mutations.test.tsrx +20 -20
  41. package/tests/client/array/array.static.test.tsrx +11 -11
  42. package/tests/client/array/array.to-methods.test.tsrx +6 -6
  43. package/tests/client/async-suspend.test.tsrx +74 -75
  44. package/tests/client/basic/basic.attributes.test.tsrx +86 -83
  45. package/tests/client/basic/basic.collections.test.tsrx +12 -12
  46. package/tests/client/basic/basic.components.test.tsrx +99 -25
  47. package/tests/client/basic/basic.errors.test.tsrx +18 -18
  48. package/tests/client/basic/basic.events.test.tsrx +42 -42
  49. package/tests/client/basic/basic.get-set.test.tsrx +8 -8
  50. package/tests/client/basic/basic.reactivity.test.tsrx +97 -29
  51. package/tests/client/basic/basic.rendering.test.tsrx +25 -25
  52. package/tests/client/basic/basic.utilities.test.tsrx +3 -3
  53. package/tests/client/boundaries.test.tsrx +8 -8
  54. package/tests/client/boxed-locals.test.tsrx +452 -0
  55. package/tests/client/compiler/__snapshots__/compiler.assignments.test.tsrx.snap +6 -6
  56. package/tests/client/compiler/compiler.assignments.test.tsrx +9 -73
  57. package/tests/client/compiler/compiler.attributes.test.tsrx +23 -23
  58. package/tests/client/compiler/compiler.basic.test.tsrx +15 -60
  59. package/tests/client/compiler/compiler.tracked-access.test.tsrx +18 -43
  60. package/tests/client/compiler/compiler.try-in-function.test.tsrx +2 -2
  61. package/tests/client/composite/__snapshots__/composite.render.test.tsrx.snap +0 -1
  62. package/tests/client/composite/composite.dynamic-components.test.tsrx +77 -6
  63. package/tests/client/composite/composite.props.test.tsrx +149 -12
  64. package/tests/client/composite/composite.reactivity.test.tsrx +51 -52
  65. package/tests/client/composite/composite.render.test.tsrx +26 -9
  66. package/tests/client/css/scoped-styles.test.tsrx +8 -8
  67. package/tests/client/date.test.tsrx +42 -42
  68. package/tests/client/derived-release.test.tsrx +370 -0
  69. package/tests/client/dynamic-elements.test.tsrx +45 -45
  70. package/tests/client/events.test.tsrx +97 -71
  71. package/tests/client/for-selector.test.tsrx +221 -0
  72. package/tests/client/for-single-root-items.test.tsrx +118 -3
  73. package/tests/client/for.test.tsrx +332 -31
  74. package/tests/client/head.test.tsrx +19 -19
  75. package/tests/client/html.test.tsrx +11 -9
  76. package/tests/client/if-block.test.tsrx +80 -0
  77. package/tests/client/map.test.tsrx +16 -16
  78. package/tests/client/media-query.test.tsrx +7 -7
  79. package/tests/client/portal.test.tsrx +224 -26
  80. package/tests/client/primitive-text.test.tsrx +50 -0
  81. package/tests/client/prop-kind-switch.test.tsrx +141 -0
  82. package/tests/client/ref.test.tsrx +17 -17
  83. package/tests/client/return.test.tsrx +26 -3
  84. package/tests/client/root-boundary.test.tsrx +42 -0
  85. package/tests/client/scoped-flush.test.tsrx +8 -8
  86. package/tests/client/set.test.tsrx +6 -6
  87. package/tests/client/svg.test.tsrx +11 -11
  88. package/tests/client/switch.test.tsrx +44 -44
  89. package/tests/client/track-async-hydration.test.tsrx +4 -4
  90. package/tests/client/try.test.tsrx +151 -83
  91. package/tests/client/tsx.test.tsrx +43 -40
  92. package/tests/client/url/url.derived.test.tsrx +6 -6
  93. package/tests/client/url-search-params/url-search-params.derived.test.tsrx +8 -8
  94. package/tests/client/url-search-params/url-search-params.iteration.test.tsrx +10 -10
  95. package/tests/client/url-search-params/url-search-params.mutation.test.tsrx +10 -10
  96. package/tests/client/url-search-params/url-search-params.retrieval.test.tsrx +18 -18
  97. package/tests/client/url-search-params/url-search-params.serialization.test.tsrx +2 -2
  98. package/tests/hydration/basic.test.js +74 -1
  99. package/tests/hydration/build-components.js +22 -1
  100. package/tests/hydration/compiled/client/basic.js +1142 -713
  101. package/tests/hydration/compiled/client/composite.js +151 -100
  102. package/tests/hydration/compiled/client/events.js +190 -150
  103. package/tests/hydration/compiled/client/for.js +1472 -1126
  104. package/tests/hydration/compiled/client/fragment-cursor.js +2341 -0
  105. package/tests/hydration/compiled/client/head.js +255 -199
  106. package/tests/hydration/compiled/client/hmr.js +54 -41
  107. package/tests/hydration/compiled/client/html-in-template.js +54 -39
  108. package/tests/hydration/compiled/client/html.js +1633 -1263
  109. package/tests/hydration/compiled/client/if-children.js +420 -270
  110. package/tests/hydration/compiled/client/if-fragment-controlflow.js +363 -317
  111. package/tests/hydration/compiled/client/if.js +229 -219
  112. package/tests/hydration/compiled/client/mixed-control-flow.js +420 -400
  113. package/tests/hydration/compiled/client/nested-control-flow.js +1326 -1255
  114. package/tests/hydration/compiled/client/portal.js +87 -95
  115. package/tests/hydration/compiled/client/reactivity.js +140 -83
  116. package/tests/hydration/compiled/client/return.js +28 -19
  117. package/tests/hydration/compiled/client/streaming.js +390 -306
  118. package/tests/hydration/compiled/client/switch.js +258 -266
  119. package/tests/hydration/compiled/client/track-async-serialization.js +448 -280
  120. package/tests/hydration/compiled/client/try.js +173 -129
  121. package/tests/hydration/compiled/client/typed-text.js +71 -0
  122. package/tests/hydration/compiled/fixtures/money.js +10 -0
  123. package/tests/hydration/compiled/server/basic.js +123 -6
  124. package/tests/hydration/compiled/server/composite.js +16 -30
  125. package/tests/hydration/compiled/server/events.js +19 -19
  126. package/tests/hydration/compiled/server/for.js +109 -49
  127. package/tests/hydration/compiled/server/fragment-cursor.js +1687 -0
  128. package/tests/hydration/compiled/server/head.js +20 -28
  129. package/tests/hydration/compiled/server/hmr.js +2 -2
  130. package/tests/hydration/compiled/server/html.js +71 -247
  131. package/tests/hydration/compiled/server/if-children.js +107 -15
  132. package/tests/hydration/compiled/server/if.js +11 -15
  133. package/tests/hydration/compiled/server/mixed-control-flow.js +8 -8
  134. package/tests/hydration/compiled/server/portal.js +14 -22
  135. package/tests/hydration/compiled/server/reactivity.js +14 -46
  136. package/tests/hydration/compiled/server/streaming.js +19 -19
  137. package/tests/hydration/compiled/server/switch.js +8 -8
  138. package/tests/hydration/compiled/server/track-async-serialization.js +150 -23
  139. package/tests/hydration/compiled/server/try.js +8 -8
  140. package/tests/hydration/compiled/server/typed-text.js +17 -0
  141. package/tests/hydration/components/basic.tsrx +44 -8
  142. package/tests/hydration/components/composite.tsrx +2 -2
  143. package/tests/hydration/components/events.tsrx +25 -25
  144. package/tests/hydration/components/for.tsrx +115 -67
  145. package/tests/hydration/components/fragment-cursor.tsrx +650 -0
  146. package/tests/hydration/components/head.tsrx +16 -16
  147. package/tests/hydration/components/hmr.tsrx +2 -2
  148. package/tests/hydration/components/html.tsrx +7 -7
  149. package/tests/hydration/components/if-children.tsrx +72 -25
  150. package/tests/hydration/components/if.tsrx +18 -18
  151. package/tests/hydration/components/mixed-control-flow.tsrx +11 -11
  152. package/tests/hydration/components/portal.tsrx +3 -3
  153. package/tests/hydration/components/reactivity.tsrx +16 -16
  154. package/tests/hydration/components/streaming.tsrx +20 -20
  155. package/tests/hydration/components/switch.tsrx +20 -20
  156. package/tests/hydration/components/text-types.ts +4 -0
  157. package/tests/hydration/components/track-async-serialization.tsrx +80 -26
  158. package/tests/hydration/components/try.tsrx +8 -8
  159. package/tests/hydration/components/typed-text.tsrx +22 -0
  160. package/tests/hydration/fixtures/money.js +10 -0
  161. package/tests/hydration/for.test.js +36 -0
  162. package/tests/hydration/fragment-cursor.test.js +111 -0
  163. package/tests/hydration/if-children.test.js +24 -0
  164. package/tests/hydration/streaming.test.js +19 -0
  165. package/tests/hydration/track-async-serialization.test.js +87 -2
  166. package/tests/hydration/tsconfig.text-types.json +10 -0
  167. package/tests/hydration/typed-text.test.js +34 -0
  168. package/tests/server/await.test.tsrx +3 -3
  169. package/tests/server/basic.attributes.test.tsrx +42 -42
  170. package/tests/server/basic.components.test.tsrx +64 -8
  171. package/tests/server/basic.test.tsrx +34 -32
  172. package/tests/server/compiler.test.tsrx +1 -70
  173. package/tests/server/composite.props.test.tsrx +6 -6
  174. package/tests/server/dynamic-elements.test.tsrx +31 -8
  175. package/tests/server/for.test.tsrx +94 -0
  176. package/tests/server/head.test.tsrx +11 -11
  177. package/tests/server/scoped-styles.test.tsrx +2 -2
  178. package/tests/server/streaming-ssr.test.tsrx +48 -43
  179. package/tests/server/track-async-serialization.test.tsrx +204 -21
  180. package/tests/server/try.test.tsrx +48 -48
  181. package/tests/types/transport.ts +40 -0
  182. package/tests/utils/vite-plugin-config.test.js +23 -0
  183. package/types/index.d.ts +48 -7
  184. package/types/server.d.ts +3 -0
  185. package/types/transport.d.ts +15 -0
  186. package/src/runtime/internal/client/switch.js +0 -113
  187. package/src/utils/errors.js +0 -13
  188. package/tests/client/lazy-array.test.tsrx +0 -31
  189. package/tests/client/lazy-destructuring.test.tsrx +0 -438
  190. package/tests/client/tracked-index-access.test.tsrx +0 -113
  191. package/tests/server/lazy-destructuring.test.tsrx +0 -595
  192. package/tests/server/tracked-index-access.test.tsrx +0 -76
@@ -1,4 +1,4 @@
1
- /** @import { Component } from '#client' */
1
+ /** @import { Component, ContextEntry } from '#client' */
2
2
 
3
3
  import { active_component } from './runtime.js';
4
4
 
@@ -16,25 +16,25 @@ export class Context {
16
16
 
17
17
  get() {
18
18
  const component = active_component;
19
- const context = this;
20
19
 
21
20
  if (component === null) {
22
21
  throw new Error('No active component found, cannot get context');
23
22
  }
24
- /** @type {Component | null} */
25
- let current_component = component;
26
23
 
27
- while (current_component !== null) {
28
- const context_map = current_component.c;
24
+ // A component inherits its parent's chain of set entries when it is
25
+ // created, so only the values that were set are visited, not every
26
+ // ancestor; the most recent set of a context is nearest the head.
27
+ /** @type {ContextEntry | null} */
28
+ let entry = component.c;
29
29
 
30
- if (context_map?.has(context)) {
31
- return context_map.get(context);
30
+ while (entry !== null) {
31
+ if (entry.k === this) {
32
+ return entry.v;
32
33
  }
33
-
34
- current_component = current_component.p;
34
+ entry = entry.n;
35
35
  }
36
36
 
37
- return context._v;
37
+ return this._v;
38
38
  }
39
39
 
40
40
  /**
@@ -43,19 +43,12 @@ export class Context {
43
43
  */
44
44
  set(value) {
45
45
  const component = active_component;
46
- const context = this;
47
46
 
48
47
  if (component === null) {
49
48
  throw new Error('No active component found, cannot set context');
50
49
  }
51
50
 
52
- let current_context = component.c;
53
-
54
- if (current_context === null) {
55
- current_context = component.c = new Map();
56
- }
57
-
58
- current_context.set(context, value);
51
+ component.c = { k: this, v: value, n: component.c };
59
52
  }
60
53
  }
61
54
 
@@ -1,14 +1,12 @@
1
1
  import { DEV } from 'esm-env';
2
2
 
3
- export function remove_ssr_css() {
4
- if (!document || typeof requestAnimationFrame !== 'function') {
5
- return;
6
- }
7
-
8
- remove_styles();
9
- }
10
-
11
- function remove_styles() {
3
+ /**
4
+ * Removes the server's inline styles once the client stylesheets are in.
5
+ * Schedule it with `requestAnimationFrame`: the removal already waits for a
6
+ * frame after the stylesheets load, and starting from a frame keeps mounting
7
+ * and hydrating free of stylesheet queries.
8
+ */
9
+ export function remove_styles() {
12
10
  if (DEV) {
13
11
  const styles = document.querySelector('style[data-vite-dev-id]');
14
12
  if (styles) {
@@ -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
  }