ripple 0.4.2 → 0.4.3

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 (94) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/package.json +9 -3
  3. package/src/jsx-runtime.d.ts +2 -1
  4. package/src/runtime/array.js +15 -9
  5. package/src/runtime/index-client.js +25 -7
  6. package/src/runtime/index-server.js +1 -0
  7. package/src/runtime/internal/client/attributes.js +313 -0
  8. package/src/runtime/internal/client/blocks.js +4 -14
  9. package/src/runtime/internal/client/component.js +2 -5
  10. package/src/runtime/internal/client/composite.js +14 -6
  11. package/src/runtime/internal/client/constants.js +6 -6
  12. package/src/runtime/internal/client/context.js +3 -2
  13. package/src/runtime/internal/client/css.js +11 -26
  14. package/src/runtime/internal/client/errors.js +188 -0
  15. package/src/runtime/internal/client/events.js +79 -34
  16. package/src/runtime/internal/client/expression.js +21 -116
  17. package/src/runtime/internal/client/for.js +108 -232
  18. package/src/runtime/internal/client/head.js +4 -3
  19. package/src/runtime/internal/client/html.js +5 -3
  20. package/src/runtime/internal/client/hydrate.js +484 -0
  21. package/src/runtime/internal/client/hydration-enabled.js +7 -0
  22. package/src/runtime/internal/client/hydration.js +16 -81
  23. package/src/runtime/internal/client/if.js +13 -9
  24. package/src/runtime/internal/client/index.js +14 -6
  25. package/src/runtime/internal/client/operations.js +11 -7
  26. package/src/runtime/internal/client/portal.js +3 -2
  27. package/src/runtime/internal/client/render.js +42 -325
  28. package/src/runtime/internal/client/root-boundary-enabled.js +7 -0
  29. package/src/runtime/internal/client/runtime.js +87 -363
  30. package/src/runtime/internal/client/template-ns.js +47 -0
  31. package/src/runtime/internal/client/template.js +63 -140
  32. package/src/runtime/internal/client/track-async.js +356 -0
  33. package/src/runtime/internal/client/try.js +133 -319
  34. package/src/runtime/internal/client/types.d.ts +48 -24
  35. package/src/runtime/internal/server/index.js +31 -29
  36. package/src/runtime/internal/server/prerender.js +27 -0
  37. package/src/server/index.js +1 -0
  38. package/src/utils/class-name.js +28 -0
  39. package/tests/client/basic/basic.errors.test.tsrx +3 -3
  40. package/tests/client/compiler/__snapshots__/compiler.typescript.test.tsrx.snap +0 -2
  41. package/tests/client/compiler/compiler.basic.test.tsrx +2 -2
  42. package/tests/client/compiler/compiler.tracked-access.test.tsrx +3 -3
  43. package/tests/client/composite/composite.props.test.tsrx +10 -10
  44. package/tests/client/derived-release.test.tsrx +3 -3
  45. package/tests/client/for-item.test.tsrx +39 -0
  46. package/tests/client/for-selector.test.tsrx +95 -0
  47. package/tests/hydration/compiled/client/basic.js +87 -87
  48. package/tests/hydration/compiled/client/composite.js +5 -5
  49. package/tests/hydration/compiled/client/events.js +40 -48
  50. package/tests/hydration/compiled/client/for.js +162 -181
  51. package/tests/hydration/compiled/client/fragment-cursor.js +521 -289
  52. package/tests/hydration/compiled/client/head.js +22 -22
  53. package/tests/hydration/compiled/client/hmr.js +4 -4
  54. package/tests/hydration/compiled/client/html-in-template.js +5 -5
  55. package/tests/hydration/compiled/client/html.js +152 -162
  56. package/tests/hydration/compiled/client/if-children.js +58 -53
  57. package/tests/hydration/compiled/client/if-fragment-controlflow.js +39 -39
  58. package/tests/hydration/compiled/client/if.js +26 -26
  59. package/tests/hydration/compiled/client/mixed-control-flow.js +56 -68
  60. package/tests/hydration/compiled/client/nested-control-flow.js +139 -271
  61. package/tests/hydration/compiled/client/portal.js +10 -10
  62. package/tests/hydration/compiled/client/reactivity.js +29 -29
  63. package/tests/hydration/compiled/client/return.js +2 -2
  64. package/tests/hydration/compiled/client/streaming.js +33 -35
  65. package/tests/hydration/compiled/client/switch.js +25 -25
  66. package/tests/hydration/compiled/client/track-async-serialization.js +41 -41
  67. package/tests/hydration/compiled/client/try.js +15 -15
  68. package/tests/hydration/compiled/client/typed-text.js +7 -7
  69. package/tests/hydration/compiled/server/basic.js +6 -6
  70. package/tests/hydration/compiled/server/events.js +8 -8
  71. package/tests/hydration/compiled/server/for.js +22 -22
  72. package/tests/hydration/compiled/server/fragment-cursor.js +188 -52
  73. package/tests/hydration/compiled/server/head.js +6 -6
  74. package/tests/hydration/compiled/server/hmr.js +1 -1
  75. package/tests/hydration/compiled/server/html.js +1 -1
  76. package/tests/hydration/compiled/server/if-children.js +9 -9
  77. package/tests/hydration/compiled/server/if.js +5 -5
  78. package/tests/hydration/compiled/server/mixed-control-flow.js +4 -4
  79. package/tests/hydration/compiled/server/portal.js +1 -1
  80. package/tests/hydration/compiled/server/reactivity.js +9 -9
  81. package/tests/hydration/compiled/server/streaming.js +9 -9
  82. package/tests/hydration/compiled/server/switch.js +4 -4
  83. package/tests/hydration/compiled/server/track-async-serialization.js +16 -16
  84. package/tests/hydration/compiled/server/try.js +4 -4
  85. package/tests/hydration/compiled/server/typed-text.js +1 -1
  86. package/tests/hydration/components/fragment-cursor.tsrx +60 -0
  87. package/tests/hydration/components/track-async-serialization.tsrx +2 -2
  88. package/tests/hydration/fragment-cursor.test.js +34 -1
  89. package/tests/hydration/track-async-serialization.test.js +1 -1
  90. package/tests/server/basic.components.test.tsrx +3 -3
  91. package/tests/server/track-async-serialization.test.tsrx +6 -6
  92. package/tests/utils/tracked-types.test.js +3 -3
  93. package/types/index.d.ts +8 -8
  94. package/types/server.d.ts +17 -0
@@ -18,7 +18,6 @@ import {
18
18
  } from './constants.js';
19
19
  import { hydrating } from './hydration.js';
20
20
  import { next_sibling } from './operations.js';
21
- import { apply_element_spread } from './render.js';
22
21
  import { is_array } from '@tsrx/core/runtime/language-helpers';
23
22
  import {
24
23
  active_block,
@@ -34,15 +33,15 @@ import {
34
33
  untrack,
35
34
  } from './runtime.js';
36
35
  import { is_ripple_object } from './utils.js';
36
+ import { effect_orphan } from './errors.js';
37
+ import { HYDRATION } from 'ripple/internal/client/hydration-enabled';
37
38
 
38
39
  /**
39
40
  * @param {Function} fn
40
41
  */
41
42
  export function user_effect(fn) {
42
43
  if (active_block === null) {
43
- throw new Error(
44
- 'effect() must be called within an active context, such as a component or effect',
45
- );
44
+ effect_orphan();
46
45
  }
47
46
 
48
47
  var component = active_component;
@@ -85,15 +84,6 @@ export function render(fn, state, flags = 0) {
85
84
  return block;
86
85
  }
87
86
 
88
- /**
89
- * @param {any} element
90
- * @param {any} fn
91
- * @param {number} [flags]
92
- */
93
- export function render_spread(element, fn, flags = 0) {
94
- return block(RENDER_BLOCK | flags, apply_element_spread(element, fn));
95
- }
96
-
97
87
  /**
98
88
  * @param {Function} fn
99
89
  * @param {number} [flags]
@@ -117,7 +107,7 @@ function noop() {}
117
107
  * @param {Node} anchor
118
108
  */
119
109
  export function own_anchor(node, anchor) {
120
- if (anchor === node || hydrating) return;
110
+ if (anchor === node || (HYDRATION && hydrating)) return;
121
111
  branch(noop, 0, { start: anchor, end: anchor });
122
112
  }
123
113
 
@@ -3,6 +3,7 @@
3
3
  import { is_tsrx_element } from '../../element.js';
4
4
  import { render_value } from './expression.js';
5
5
  import { active_block } from './runtime.js';
6
+ import { component_invalid } from './errors.js';
6
7
 
7
8
  export { render_component } from './runtime.js';
8
9
 
@@ -21,9 +22,5 @@ export function render_tsrx_element(value, anchor, block = active_block) {
21
22
  * @returns {never}
22
23
  */
23
24
  export function throw_invalid_component_type(value) {
24
- if (is_tsrx_element(value)) {
25
- throw new TypeError('Invalid component type: received a TSRXElement value.');
26
- }
27
-
28
- throw new TypeError('Invalid component type: expected a component function.');
25
+ component_invalid(is_tsrx_element(value));
29
26
  }
@@ -1,13 +1,17 @@
1
1
  /** @import { Block } from '#client' */
2
+ import { component_invalid } from './errors.js';
2
3
 
3
- import { branch, destroy_block, render, render_spread } from './blocks.js';
4
+ import { branch, destroy_block, render } from './blocks.js';
5
+ import { render_spread } from './attributes.js';
4
6
  import { COMPOSITE_BLOCK, DEFAULT_NAMESPACE, NAMESPACE_URI } from './constants.js';
5
7
  import { hydrate_node, hydrate_next, hydrating, set_hydrate_node } from './hydration.js';
6
8
  import { first_child } from './operations.js';
7
9
  import { active_block, active_namespace, get, untrack, with_ns } from './runtime.js';
8
10
  import { top_element_to_ns } from './utils.js';
11
+ import { install_ns_templates } from './template-ns.js';
9
12
  import { is_tsrx_element } from '../../element.js';
10
13
  import { render_component } from './component.js';
14
+ import { HYDRATION } from 'ripple/internal/client/hydration-enabled';
11
15
 
12
16
  /**
13
17
  * @typedef {Function | string | null | undefined | false} CompositeTarget
@@ -19,7 +23,7 @@ import { render_component } from './component.js';
19
23
  * @returns {void}
20
24
  */
21
25
  export function composite(get_component, node, get_props) {
22
- if (hydrating) {
26
+ if (HYDRATION && hydrating) {
23
27
  // During hydration, `node` may already point at the first real SSR node
24
28
  // (e.g. layout children). Only skip forward when we are on an empty
25
29
  // comment anchor from a client template placeholder.
@@ -49,16 +53,20 @@ export function composite(get_component, node, get_props) {
49
53
  render_component(component, anchor, props);
50
54
  });
51
55
  } else if (is_tsrx_element(component)) {
52
- throw new TypeError('Invalid component type: received a TSRXElement value.');
56
+ component_invalid(true);
53
57
  } else if (component != null) {
54
58
  // Custom element - only create if component is not null/undefined
55
59
  const ns = top_element_to_ns(component, active_namespace);
60
+ if (ns !== DEFAULT_NAMESPACE) {
61
+ // Templates cloned inside the element parse in its namespace.
62
+ install_ns_templates();
63
+ }
56
64
  var run = () => {
57
65
  var block = /** @type {Block} */ (active_block);
58
66
 
59
67
  /** @type {Element} */
60
68
  var element;
61
- if (hydrating) {
69
+ if (HYDRATION && hydrating) {
62
70
  // Claim the SSR-rendered element instead of creating a new one.
63
71
  element = /** @type {Element} */ (hydrate_node);
64
72
  } else {
@@ -86,7 +94,7 @@ export function composite(get_component, node, get_props) {
86
94
  if (is_tsrx_element(props.children)) {
87
95
  /** @type {Node} */
88
96
  var child_anchor;
89
- if (hydrating) {
97
+ if (HYDRATION && hydrating) {
90
98
  // The server renders children directly inside the element with no
91
99
  // extra markers; descend the cursor so they claim those nodes.
92
100
  child_anchor = /** @type {Node} */ (first_child(element));
@@ -101,7 +109,7 @@ export function composite(get_component, node, get_props) {
101
109
  props.children.render(child_anchor, block, props.children.p);
102
110
  }
103
111
 
104
- if (hydrating) {
112
+ if (HYDRATION && hydrating) {
105
113
  // Reset the cursor to the claimed element so sibling traversal
106
114
  // continues after it.
107
115
  set_hydrate_node(element);
@@ -39,7 +39,7 @@ export var ITEM_BLOCK = 1 << 26;
39
39
  export var CONTROL_FLOW_BLOCK = FOR_BLOCK | IF_BLOCK | SWITCH_BLOCK | TRY_BLOCK | COMPOSITE_BLOCK;
40
40
 
41
41
  /** @type {unique symbol} */
42
- export const UNINITIALIZED = Symbol('uninitialized');
42
+ export const UNINITIALIZED = Symbol();
43
43
  /** @type {unique symbol} */
44
44
  export const TRACKED_ARRAY = Symbol();
45
45
  /** @type {unique symbol} */
@@ -58,13 +58,13 @@ export const NAMESPACE_URI = {
58
58
  mathml: 'http://www.w3.org/1998/Math/MathML',
59
59
  };
60
60
  /** @type {unique symbol} */
61
- export const TRACKED_UPDATED = Symbol('TRACKED_UPDATED');
61
+ export const TRACKED_UPDATED = Symbol();
62
62
  /** @type {unique symbol} */
63
- export const SUSPENSE_PENDING = Symbol('suspense_pending');
63
+ export const SUSPENSE_PENDING = Symbol();
64
64
  /** @type {unique symbol} */
65
- export const SUSPENSE_REJECTED = Symbol('suspense_rejected');
65
+ export const SUSPENSE_REJECTED = Symbol();
66
66
  /** @type {unique symbol} */
67
- export const ASYNC_DERIVED_READ_THROWN = Symbol('async_derived_read_thrown');
67
+ export const ASYNC_DERIVED_READ_THROWN = Symbol();
68
68
 
69
69
  /** The hoisted render function a module-level component carries (see `render_component`). */
70
- export const RENDER_ENTRY = Symbol('render');
70
+ export const RENDER_ENTRY = Symbol();
@@ -1,4 +1,5 @@
1
1
  /** @import { Component, ContextEntry } from '#client' */
2
+ import { context_orphan } from './errors.js';
2
3
 
3
4
  import { active_component } from './runtime.js';
4
5
 
@@ -18,7 +19,7 @@ export class Context {
18
19
  const component = active_component;
19
20
 
20
21
  if (component === null) {
21
- throw new Error('No active component found, cannot get context');
22
+ context_orphan(false);
22
23
  }
23
24
 
24
25
  // A component inherits its parent's chain of set entries when it is
@@ -45,7 +46,7 @@ export class Context {
45
46
  const component = active_component;
46
47
 
47
48
  if (component === null) {
48
- throw new Error('No active component found, cannot set context');
49
+ context_orphan(true);
49
50
  }
50
51
 
51
52
  component.c = { k: this, v: value, n: component.c };
@@ -24,49 +24,34 @@ function remove() {
24
24
  }
25
25
 
26
26
  /**
27
- * @param {function} callback
27
+ * Calls back once every stylesheet link has loaded (or failed).
28
+ * @param {() => void} callback
28
29
  * @returns {void}
29
30
  */
30
31
  function remove_when_css_loaded(callback) {
31
- /** @type {HTMLLinkElement[]} */
32
- const links = Array.from(
33
- /** @type {NodeListOf<HTMLLinkElement>} */ (
34
- document.querySelectorAll('link[rel="stylesheet"]')
35
- ),
32
+ var links = /** @type {NodeListOf<HTMLLinkElement>} */ (
33
+ document.querySelectorAll('link[rel="stylesheet"]')
36
34
  );
37
- let remaining = links.length;
35
+ var remaining = links.length;
38
36
 
39
37
  if (remaining === 0) {
40
38
  callback();
41
39
  return;
42
40
  }
43
41
 
44
- const done = () => {
45
- remaining--;
46
- if (remaining === 0) {
47
- // clean up all listeners
48
- links.forEach((link) => {
49
- link.removeEventListener('load', onLoad);
50
- link.removeEventListener('error', onError);
51
- });
42
+ var done = () => {
43
+ if (--remaining === 0) {
52
44
  callback();
53
45
  }
54
46
  };
55
47
 
56
- function onLoad() {
57
- done();
58
- }
59
- function onError() {
60
- done();
61
- }
62
-
63
- links.forEach((link) => {
48
+ for (var link of links) {
64
49
  if (link.sheet) {
65
50
  // already loaded (possibly cached)
66
51
  done();
67
52
  } else {
68
- link.addEventListener('load', onLoad);
69
- link.addEventListener('error', onError);
53
+ link.addEventListener('load', done, { once: true });
54
+ link.addEventListener('error', done, { once: true });
70
55
  }
71
- });
56
+ }
72
57
  }
@@ -0,0 +1,188 @@
1
+ import { DEV } from 'esm-env';
2
+
3
+ /**
4
+ * The runtime's errors, one function per error as in Svelte: in development
5
+ * the message is thrown with a link to its reference page; in production only
6
+ * the link is, so the message text never ships. Each page lives at
7
+ * `https://ripple-ts.com/e/<code>`.
8
+ */
9
+
10
+ /**
11
+ * @param {string} code
12
+ * @param {string} message
13
+ * @returns {string}
14
+ */
15
+ function text(code, message) {
16
+ var link = `https://ripple-ts.com/e/${code}`;
17
+ return DEV ? `${message}\n${link}` : link;
18
+ }
19
+
20
+ /** @returns {never} */
21
+ export function effect_orphan() {
22
+ throw new Error(
23
+ DEV
24
+ ? text(
25
+ 'effect_orphan',
26
+ 'effect() must be called within an active context, such as a component or effect',
27
+ )
28
+ : text('effect_orphan', ''),
29
+ );
30
+ }
31
+
32
+ /** @returns {never} */
33
+ export function track_orphan() {
34
+ throw new TypeError(
35
+ DEV
36
+ ? text('track_orphan', 'track() requires a valid component context')
37
+ : text('track_orphan', ''),
38
+ );
39
+ }
40
+
41
+ /** @returns {never} */
42
+ export function track_async_orphan() {
43
+ throw new TypeError(
44
+ DEV
45
+ ? text('track_async_orphan', 'trackAsync() requires a valid component context')
46
+ : text('track_async_orphan', ''),
47
+ );
48
+ }
49
+
50
+ /** @returns {never} */
51
+ export function track_async_argument() {
52
+ throw new TypeError(
53
+ DEV
54
+ ? text(
55
+ 'track_async_argument',
56
+ 'trackAsync() only accepts function arguments that return a promise or an object with a promise property',
57
+ )
58
+ : text('track_async_argument', ''),
59
+ );
60
+ }
61
+
62
+ /** @returns {never} */
63
+ export function track_async_boundary() {
64
+ throw new Error(
65
+ DEV
66
+ ? text('track_async_boundary', 'Missing parent `try { ... } pending { ... }` statement')
67
+ : text('track_async_boundary', ''),
68
+ );
69
+ }
70
+
71
+ /**
72
+ * @param {boolean} in_component the read sits directly in a component body,
73
+ * otherwise in a try/pending/catch body
74
+ * @returns {never}
75
+ */
76
+ export function pending_read_direct(in_component) {
77
+ throw new Error(
78
+ DEV
79
+ ? text(
80
+ 'pending_read_direct',
81
+ `Reads on pending tracked values directly inside ${in_component ? 'component' : 'try/pending/catch'} body are prohibited. Use trackPending() test or peek() for safe access or create another derived instead.`,
82
+ )
83
+ : text('pending_read_direct', ''),
84
+ );
85
+ }
86
+
87
+ /** @returns {never} */
88
+ export function update_depth_exceeded() {
89
+ throw new Error(
90
+ DEV
91
+ ? text(
92
+ 'update_depth_exceeded',
93
+ 'Maximum update depth exceeded. This typically indicates that an effect reads and writes the same piece of state.',
94
+ )
95
+ : text('update_depth_exceeded', ''),
96
+ );
97
+ }
98
+
99
+ /** @returns {never} */
100
+ export function set_in_derived() {
101
+ throw new Error(
102
+ DEV
103
+ ? text(
104
+ 'set_in_derived',
105
+ 'Assignments or updates to tracked values are not allowed during computed "track(() => ...)" evaluation',
106
+ )
107
+ : text('set_in_derived', ''),
108
+ );
109
+ }
110
+
111
+ /**
112
+ * @param {string} [message] a caller's own wording
113
+ * @returns {never}
114
+ */
115
+ export function scope_orphan(message) {
116
+ throw new Error(
117
+ DEV
118
+ ? text('scope_orphan', message ?? 'Cannot access outside of a component context')
119
+ : text('scope_orphan', ''),
120
+ );
121
+ }
122
+
123
+ /**
124
+ * @param {boolean} is_element the value is a TSRX element rather than a
125
+ * component function
126
+ * @returns {never}
127
+ */
128
+ export function component_invalid(is_element) {
129
+ throw new TypeError(
130
+ DEV
131
+ ? text(
132
+ 'component_invalid',
133
+ `Invalid component type: ${is_element ? 'received a TSRXElement value.' : 'expected a component function.'}`,
134
+ )
135
+ : text('component_invalid', ''),
136
+ );
137
+ }
138
+
139
+ /**
140
+ * @param {boolean} is_set `Context#set` rather than `Context#get`
141
+ * @returns {never}
142
+ */
143
+ export function context_orphan(is_set) {
144
+ throw new Error(
145
+ DEV
146
+ ? text(
147
+ 'context_orphan',
148
+ `No active component found, cannot ${is_set ? 'set' : 'get'} context`,
149
+ )
150
+ : text('context_orphan', ''),
151
+ );
152
+ }
153
+
154
+ /**
155
+ * @param {string} kind the block whose end marker is missing
156
+ * @returns {never}
157
+ */
158
+ export function hydration_mismatch(kind) {
159
+ throw new Error(
160
+ DEV
161
+ ? text('hydration_mismatch', `Hydration mismatch: expected end marker for ${kind} block`)
162
+ : text('hydration_mismatch', ''),
163
+ );
164
+ }
165
+
166
+ /** @returns {never} */
167
+ export function hydration_disabled() {
168
+ throw new Error(
169
+ DEV
170
+ ? text(
171
+ 'hydration_disabled',
172
+ 'hydrate() is not available in a client-only build: this build was compiled with `ssr: false`, which leaves the hydration runtime out',
173
+ )
174
+ : text('hydration_disabled', ''),
175
+ );
176
+ }
177
+
178
+ /** @returns {never} */
179
+ export function root_boundary_disabled() {
180
+ throw new Error(
181
+ DEV
182
+ ? text(
183
+ 'root_boundary_disabled',
184
+ 'A root boundary is not available in this build: it was compiled with `rootBoundary: false`, which leaves the boundary runtime out, so `mount()` and `hydrate()` cannot take `rootBoundary` options',
185
+ )
186
+ : text('root_boundary_disabled', ''),
187
+ );
188
+ }
@@ -23,7 +23,9 @@ import { render } from './blocks.js';
23
23
  var all_registered_events = new Set();
24
24
 
25
25
  /**
26
- * @typedef {{ count: number, registered_events: Set<string>, target: Element }} RootTargetRef
26
+ * A root delegation target: its acquire count, the event names it listens
27
+ * for, and the element.
28
+ * @typedef {{ n: number, e: Set<string>, t: Element }} RootTargetRef
27
29
  */
28
30
 
29
31
  /**
@@ -103,8 +105,6 @@ export function on(element, type, handler, options = {}) {
103
105
  };
104
106
  }
105
107
 
106
- var last_propagated_event = null;
107
-
108
108
  /**
109
109
  * @this {EventTarget}
110
110
  * @param {Event} event
@@ -114,19 +114,19 @@ export function handle_event_propagation(event) {
114
114
  var handler_element = this;
115
115
  var owner_document = /** @type {Node} */ (handler_element).ownerDocument;
116
116
  var event_name = event.type;
117
- var path = event.composedPath?.() || [];
118
- var current_target = /** @type {null | Element} */ (path[0] || event.target);
119
-
120
- last_propagated_event = event;
117
+ /** @type {null | Element} */
118
+ var current_target;
121
119
 
122
- // composedPath contains list of nodes the event has propagated through.
123
- // We check __root to skip all nodes below it in case this is a
124
- // parent of the __root node, which indicates that there's nested
125
- // mounted apps. In this case we don't want to trigger events multiple times.
126
- var path_idx = 0;
127
- var handled_at = last_propagated_event === event && event.__root;
120
+ // __root marks the root that already handled this event: this listener
121
+ // then belongs to a parent of a nested mounted app, and the walk resumes
122
+ // from that root so nothing below it handles the event twice. Only then
123
+ // is the composed path needed; a plain event starts at its target (the
124
+ // composed target when it was retargeted out of a shadow tree).
125
+ var handled_at = event.__root;
128
126
 
129
127
  if (handled_at) {
128
+ var path = event.composedPath?.() || [];
129
+ var path_idx = 0;
130
130
  var at_idx = path.indexOf(handled_at);
131
131
  if (at_idx !== -1 && (handler_element === document || handler_element === window)) {
132
132
  // This is the fallback document listener or a window listener, but the event was already handled
@@ -151,9 +151,14 @@ export function handle_event_propagation(event) {
151
151
  if (at_idx <= handler_idx) {
152
152
  path_idx = at_idx;
153
153
  }
154
+ current_target = /** @type {Element} */ (path[path_idx] || event.target);
155
+ } else {
156
+ current_target = /** @type {Element} */ (event.target);
157
+ if (current_target !== null && /** @type {any} */ (current_target).shadowRoot) {
158
+ current_target = /** @type {Element} */ (event.composedPath?.()[0] || current_target);
159
+ }
154
160
  }
155
161
 
156
- current_target = /** @type {Element} */ (path[path_idx] || event.target);
157
162
  // there can only be one delegated event per element, and we either already handled the current target,
158
163
  // or this is the very first target in the chain which has a non-delegated listener, in which case it's safe
159
164
  // to handle a possible delegated event on it later (through the root delegation listener for example).
@@ -181,9 +186,10 @@ export function handle_event_propagation(event) {
181
186
  */
182
187
  var throw_error;
183
188
  /**
184
- * @type {unknown[]}
189
+ * @type {unknown[] | null}
185
190
  */
186
- var other_errors = [];
191
+ var other_errors = null;
192
+ var prop = '__' + event_name;
187
193
 
188
194
  while (current_target !== null) {
189
195
  /** @type {null | Element} */
@@ -194,7 +200,7 @@ export function handle_event_propagation(event) {
194
200
  null;
195
201
 
196
202
  try {
197
- var delegated = /** @type {Record<string, any>} */ (current_target)['__' + event_name];
203
+ var delegated = /** @type {Record<string, any>} */ (current_target)[prop];
198
204
 
199
205
  if (delegated !== undefined && !(/** @type {any} */ (current_target).disabled)) {
200
206
  if (is_array(delegated)) {
@@ -207,7 +213,7 @@ export function handle_event_propagation(event) {
207
213
  }
208
214
  } catch (error) {
209
215
  if (throw_error) {
210
- other_errors.push(error);
216
+ (other_errors ??= []).push(error);
211
217
  } else {
212
218
  throw_error = error;
213
219
  }
@@ -219,16 +225,17 @@ export function handle_event_propagation(event) {
219
225
  }
220
226
 
221
227
  if (throw_error) {
222
- for (let error of other_errors) {
223
- // Throw the rest of the errors, one-by-one on a microtask
224
- queueMicrotask(() => {
225
- throw error;
226
- });
228
+ if (other_errors !== null) {
229
+ for (let error of other_errors) {
230
+ // Throw the rest of the errors, one-by-one on a microtask
231
+ queueMicrotask(() => {
232
+ throw error;
233
+ });
234
+ }
227
235
  }
228
236
  throw throw_error;
229
237
  }
230
238
  } finally {
231
- set_active_block(previous_block);
232
239
  event.__root = handler_element;
233
240
  // @ts-ignore remove proxy on currentTarget
234
241
  delete event.currentTarget;
@@ -412,6 +419,44 @@ export function event(event_name, dom, handler) {
412
419
  event_listener(event_name, dom, handler);
413
420
  }
414
421
 
422
+ /**
423
+ * Compiler-emitted listener for a static `onEvent={fn}` whose name can never
424
+ * be delegated (`onBlur`, `onScroll`, a `Capture` variant): a native listener
425
+ * on the element, with the handler run outside any reactive context as a
426
+ * delegated handler is. Nothing is returned, as for `event()`. The name is the
427
+ * DOM event name the compiler resolved, so no name table is consulted here.
428
+ * @param {string} name
429
+ * @param {EventTarget} dom
430
+ * @param {EventListener} handler
431
+ * @param {boolean} [capture]
432
+ * @returns {void}
433
+ */
434
+ export function listen(name, dom, handler, capture) {
435
+ dom.addEventListener(
436
+ name,
437
+ /**
438
+ * @this {EventTarget}
439
+ * @param {Event} event
440
+ */
441
+ function (event) {
442
+ var previous_block = active_block;
443
+ var previous_reaction = active_reaction;
444
+ var previous_tracking = tracking;
445
+ set_active_block(null);
446
+ set_active_reaction(null);
447
+ set_tracking(false);
448
+ try {
449
+ return handler.call(this, event);
450
+ } finally {
451
+ set_active_block(previous_block);
452
+ set_active_reaction(previous_reaction);
453
+ set_tracking(previous_tracking);
454
+ }
455
+ },
456
+ capture,
457
+ );
458
+ }
459
+
415
460
  /**
416
461
  * Attaches a listener and returns its remover; `event()` is the one-shot
417
462
  * compiled form of this.
@@ -496,8 +541,8 @@ export function delegate(events) {
496
541
  * @param {Iterable<string>} events
497
542
  */
498
543
  function register_root_events(ref, events) {
499
- var registered_events = ref.registered_events;
500
- var target = ref.target;
544
+ var registered_events = ref.e;
545
+ var target = ref.t;
501
546
 
502
547
  for (var event_name of events) {
503
548
  if (registered_events.has(event_name)) continue;
@@ -519,17 +564,17 @@ export function handle_root_events(target) {
519
564
  // Sibling portals mostly share one target, so check the last ref first.
520
565
  /** @type {RootTargetRef | undefined} */
521
566
  var ref =
522
- last_root_ref !== null && last_root_ref.target === target
567
+ last_root_ref !== null && last_root_ref.t === target
523
568
  ? last_root_ref
524
569
  : root_target_refs.get(target);
525
570
 
526
571
  if (ref === undefined) {
527
- ref = { count: 0, registered_events: new Set(), target };
572
+ ref = { n: 0, e: new Set(), t: target };
528
573
  root_target_refs.set(target, ref);
529
574
  register_root_events(ref, all_registered_events);
530
575
  }
531
576
 
532
- ref.count += 1;
577
+ ref.n += 1;
533
578
  last_root_ref = ref;
534
579
  return ref;
535
580
  }
@@ -539,18 +584,18 @@ export function handle_root_events(target) {
539
584
  * @returns {void}
540
585
  */
541
586
  export function release_root_events(ref) {
542
- // The map entry for `ref.target` is always this `ref`: it is only deleted
587
+ // The map entry for `ref.t` is always this `ref`: it is only deleted
543
588
  // when the count hits 0, which requires every acquirer to have released.
544
- ref.count -= 1;
545
- if (ref.count > 0) return;
589
+ ref.n -= 1;
590
+ if (ref.n > 0) return;
546
591
 
547
592
  // Last caller for this target: actually tear down the shared listeners.
548
- var target = ref.target;
593
+ var target = ref.t;
549
594
  root_target_refs.delete(target);
550
595
  if (last_root_ref === ref) {
551
596
  last_root_ref = null;
552
597
  }
553
- for (var event_name of ref.registered_events) {
598
+ for (var event_name of ref.e) {
554
599
  target.removeEventListener(event_name, /** @type {EventListener} */ (handle_event_propagation));
555
600
  }
556
601
  }