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,13 +1,25 @@
1
1
  /** @import { AppendIntoAnchor } from '#client' */
2
2
 
3
- import { TEXT_NODE } from '../../../constants.js';
4
- import { hydrate_node, hydrating, set_hydrate_node } from './hydration.js';
3
+ import {
4
+ hydrate_first_child,
5
+ hydrate_next_sibling,
6
+ hydrate_node,
7
+ hydrate_text_child,
8
+ hydrating,
9
+ set_hydrate_node,
10
+ } from './hydration.js';
5
11
  import { get_descriptor } from '@tsrx/core/runtime/language-helpers';
6
12
 
13
+ export { hydrate_first_child, hydrate_next_sibling, hydrate_text_child };
14
+
15
+ /**
16
+ * The `firstChild` / `nextSibling` getters, called directly so a traversal
17
+ * site never becomes a megamorphic property read across node types.
18
+ * @type {(() => Node | null)}
19
+ */
20
+ export var first_child_getter;
7
21
  /** @type {(() => Node | null)} */
8
- var first_child_getter;
9
- /** @type {(() => Node | null)} */
10
- var next_sibling_getter;
22
+ export var next_sibling_getter;
11
23
  /** @type {(() => Node | null)} */
12
24
  var last_child_getter;
13
25
 
@@ -66,23 +78,9 @@ export function get_last_child(node) {
66
78
  */
67
79
  export function first_child(node, is_text) {
68
80
  if (!hydrating) {
69
- return get_first_child(node);
70
- }
71
- var child = get_first_child(/** @type {Node} */ (hydrate_node));
72
-
73
- // Handles the case where we have `<p>{text}</p>`, where `text` is empty
74
- if (child === null) {
75
- child = /** @type {Node} */ (hydrate_node).appendChild(create_text());
76
- } else if (is_text && child.nodeType !== TEXT_NODE) {
77
- var text = create_text();
78
- /** @type {Element | Text | Comment} */ (child)?.before(text);
79
- set_hydrate_node(text);
80
- return text;
81
+ return node.firstChild;
81
82
  }
82
-
83
- set_hydrate_node(child);
84
-
85
- return child;
83
+ return hydrate_first_child(is_text);
86
84
  }
87
85
 
88
86
  /**
@@ -105,7 +103,29 @@ export function append_into(parent) {
105
103
  set_hydrate_node(child);
106
104
  }
107
105
 
108
- return { parent };
106
+ return { parent, into: true };
107
+ }
108
+
109
+ /**
110
+ * Resolves the anchor for a block that keeps inserting relative to it for its
111
+ * whole life (root-controlled control flow). An append-into sentinel is only
112
+ * valid for a one-shot append: a later branch swap or list move must land at
113
+ * the block's own position, not after whatever siblings were appended since,
114
+ * so the sentinel is materialized into a text anchor at the current end of
115
+ * the parent. During hydration the cursor sits on the block's SSR boundary
116
+ * marker, which is exactly the anchor a non-sentinel root-controlled block
117
+ * receives, so it serves as the anchor there.
118
+ * @param {Node | AppendIntoAnchor} node
119
+ * @returns {Node}
120
+ */
121
+ export function resolve_anchor(node) {
122
+ if (/** @type {AppendIntoAnchor} */ (node).into !== true) {
123
+ return /** @type {Node} */ (node);
124
+ }
125
+ if (hydrating) {
126
+ return /** @type {Node} */ (hydrate_node);
127
+ }
128
+ return /** @type {AppendIntoAnchor} */ (node).parent.appendChild(create_text());
109
129
  }
110
130
 
111
131
  /**
@@ -144,37 +164,10 @@ export function get_next_sibling(node) {
144
164
  * @returns {Node | null}
145
165
  */
146
166
  export function next_sibling(node, is_text) {
147
- let next_sibling = hydrating ? hydrate_node : node;
148
- var last_sibling;
149
-
150
- next_sibling = /** @type {ChildNode | null} */ (
151
- get_next_sibling(/** @type {ChildNode} */ (next_sibling))
152
- );
153
- last_sibling = next_sibling;
154
-
155
167
  if (!hydrating) {
156
- return next_sibling;
168
+ return node.nextSibling;
157
169
  }
158
-
159
- // if a sibling {expression} is empty during SSR, there might be no
160
- // text node to hydrate — we must therefore create one
161
- if (is_text && next_sibling?.nodeType !== TEXT_NODE) {
162
- var text = create_text();
163
- // If the next sibling is `null` and we're handling text then it's because
164
- // the SSR content was empty for the text, so we need to generate a new text
165
- // node and insert it after the last sibling
166
- if (next_sibling === null) {
167
- /** @type {ChildNode} */ (last_sibling).after(text);
168
- } else {
169
- /** @type {ChildNode} */ (next_sibling).before(text);
170
- }
171
- set_hydrate_node(text);
172
- return text;
173
- }
174
-
175
- set_hydrate_node(next_sibling);
176
-
177
- return next_sibling;
170
+ return hydrate_next_sibling(is_text);
178
171
  }
179
172
 
180
173
  export function create_text(value = '') {
@@ -1,97 +1,175 @@
1
- /** @import { Block } from '#client' */
1
+ /** @import { AppendIntoAnchor, Block } from '#client' */
2
2
 
3
- import { branch, destroy_block, remove_block_dom, render } from './blocks.js';
4
- import { UNINITIALIZED } from './constants.js';
5
- import { handle_root_events } from './events.js';
6
- import { create_text } from './operations.js';
3
+ import { branch, destroy_block, render } from './blocks.js';
4
+ import { DESTROYED, DETACHED_BLOCK, UNINITIALIZED } from './constants.js';
5
+ import { handle_root_events, release_root_events } from './events.js';
7
6
  import { active_block } from './runtime.js';
8
- import { hydrating, hydrate_node, set_hydrating, set_hydrate_node } from './hydration.js';
7
+ import { hydrating, hydrate_node, set_hydration, set_hydrating } from './hydration.js';
9
8
  import { is_tsrx_element, tsrx_element } from '../../element.js';
10
9
 
10
+ /**
11
+ * @typedef {(anchor: AppendIntoAnchor, block: Block) => void} PortalRender
12
+ * @typedef {{
13
+ * g: () => Element;
14
+ * r: PortalRender | null;
15
+ * c: () => any;
16
+ * t: Element | typeof UNINITIALIZED;
17
+ * k: any;
18
+ * b: Block | null;
19
+ * e: import('./events.js').RootTargetRef | null;
20
+ * p: Block | null;
21
+ * }} PortalState
22
+ * @typedef {{
23
+ * start: Node | null;
24
+ * end: Node | null;
25
+ * r: PortalRender | null;
26
+ * c: any;
27
+ * a: AppendIntoAnchor;
28
+ * b: Block;
29
+ * }} PortalBranchState
30
+ */
31
+
32
+ /**
33
+ * @param {PortalState} s
34
+ */
35
+ function run_portal(s) {
36
+ // The render block itself; lets the shared teardown tell destroy from re-run.
37
+ s.p = /** @type {Block} */ (active_block);
38
+
39
+ var next_target = s.g();
40
+ var next_children = s.c();
41
+
42
+ if (s.t === next_target && s.k === next_children) return;
43
+
44
+ if (s.t !== next_target) {
45
+ // The target's delegated root listeners are only released and
46
+ // re-acquired when the target changes — a children-only update must not
47
+ // touch them.
48
+ if (s.e !== null) {
49
+ release_root_events(s.e);
50
+ }
51
+ s.e = handle_root_events(next_target);
52
+ }
53
+
54
+ s.t = next_target;
55
+ s.k = next_children;
56
+
57
+ if (s.b !== null) {
58
+ destroy_block(s.b);
59
+ }
60
+
61
+ // Portal content always appends at the end of its target, so an
62
+ // append-into sentinel replaces a placeholder text anchor: no extra node to
63
+ // create, insert before, or remove on teardown. The branch is detached: its
64
+ // DOM is removed even when a destroyed ancestor already removed its own.
65
+ s.b = branch(run_portal_children, DETACHED_BLOCK, {
66
+ start: null,
67
+ end: null,
68
+ r: s.r,
69
+ c: next_children,
70
+ a: { parent: next_target, into: true },
71
+ b: /** @type {Block} */ (s.p),
72
+ });
73
+
74
+ return portal_teardown;
75
+ }
76
+
77
+ /**
78
+ * @param {PortalBranchState} state
79
+ */
80
+ function run_portal_children(state) {
81
+ var render_children = state.r;
82
+ if (render_children !== null) {
83
+ render_children(state.a, state.b);
84
+ return;
85
+ }
86
+ var children = state.c;
87
+ if (is_tsrx_element(children)) {
88
+ children.render(state.a, state.b, children.p);
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Only the destroy path releases the target's listeners; a re-run diffs the
94
+ * target itself in `run_portal`.
95
+ * @this {null}
96
+ * @param {PortalState} s
97
+ */
98
+ function portal_teardown(s) {
99
+ var block = /** @type {Block} */ (s.p);
100
+ if ((block.f & DESTROYED) !== 0 && s.e !== null) {
101
+ release_root_events(s.e);
102
+ s.e = null;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * @param {() => Element} get_target
108
+ * @param {PortalRender | null} render_children
109
+ * @param {() => any} get_children
110
+ */
111
+ function create_portal(get_target, render_children, get_children) {
112
+ /** @type {PortalState} */
113
+ var state = {
114
+ g: get_target,
115
+ r: render_children,
116
+ c: get_children,
117
+ // current target
118
+ t: UNINITIALIZED,
119
+ // current children value (component form only)
120
+ k: UNINITIALIZED,
121
+ // branch rendering the content into the target
122
+ b: null,
123
+ // delegated root listener ref for the current target
124
+ e: null,
125
+ // the render block, assigned on its first run
126
+ p: null,
127
+ };
128
+
129
+ if (!hydrating) {
130
+ render(run_portal, state);
131
+ return;
132
+ }
133
+
134
+ // Portals are client-only and don't participate in hydration; the
135
+ // compiler-generated code already advanced past the SSR marker. Disable
136
+ // hydration for the portal content and restore the cursor afterwards.
137
+ var previous_hydrate_node = hydrate_node;
138
+ set_hydrating(false);
139
+ try {
140
+ render(run_portal, state);
141
+ } finally {
142
+ set_hydration(true, previous_hydrate_node);
143
+ }
144
+ }
145
+
146
+ function no_children() {
147
+ return null;
148
+ }
149
+
150
+ /**
151
+ * Compiler fast path for `<Portal target={...}>...</Portal>`: no props object,
152
+ * component context, or element wrapper — the children render function is
153
+ * fixed for the life of the portal.
154
+ * @param {Node} _anchor
155
+ * @param {() => Element} get_target
156
+ * @param {PortalRender} render_children
157
+ * @returns {void}
158
+ */
159
+ export function portal(_anchor, get_target, render_children) {
160
+ create_portal(get_target, render_children, no_children);
161
+ }
162
+
11
163
  /**
12
164
  * @param {{ target: Element, children: import('../../element.js').TSRXElement }} props
13
165
  * @returns {import('../../element.js').TSRXElement}
14
166
  */
15
167
  export function Portal(props) {
16
168
  return tsrx_element(function render_portal() {
17
- // Portals are client-only and don't participate in hydration
18
- // The compiler-generated code already handles getting the node via sibling()
19
- var was_hydrating = hydrating;
20
- var previous_hydrate_node = hydrate_node;
21
-
22
- /** @type {Element | symbol} */
23
- let target = UNINITIALIZED;
24
- /** @type {import('../../element.js').TSRXElement | symbol} */
25
- let children = UNINITIALIZED;
26
- /** @type {Block | null} */
27
- var b = null;
28
- /** @type {Text | null} */
29
- var anchor = null;
30
- /** @type {Node | null} */
31
- var dom_start = null;
32
- /** @type {Node | null} */
33
- var dom_end = null;
34
-
35
- // Temporarily disable hydration for portal content
36
- if (was_hydrating) {
37
- set_hydrating(false);
38
- }
39
-
40
- try {
41
- // Root event delegation lives in its own render block so it only
42
- // re-runs when `props.target` changes — a children-only update must
43
- // not release and re-acquire the target's delegated listeners.
44
- render(() => {
45
- const cleanup_events = handle_root_events(/** @type {Element} */ (props.target));
46
- return cleanup_events;
47
- });
48
-
49
- render(() => {
50
- const next_target = props.target;
51
- const next_children = props.children;
52
-
53
- if (target === next_target && children === next_children) return;
54
-
55
- target = next_target;
56
- children = next_children;
57
-
58
- if (b !== null) {
59
- destroy_block(b);
60
- }
61
-
62
- if (anchor !== null) {
63
- anchor.remove();
64
- }
65
-
66
- dom_start = dom_end = null;
67
-
68
- anchor = create_text();
69
- /** @type {Element} */ (target).append(anchor);
70
-
71
- var block = /** @type {Block} */ (active_block);
72
-
73
- b = branch(() => {
74
- if (is_tsrx_element(children)) {
75
- children.render(/** @type {Text} */ (anchor), block);
76
- }
77
- });
78
-
79
- dom_start = b?.s?.start;
80
- dom_end = b?.s?.end;
81
-
82
- return () => {
83
- /** @type {Text} */ (anchor).remove();
84
- if (dom_start && dom_end) {
85
- remove_block_dom(dom_start, dom_end);
86
- }
87
- };
88
- });
89
- } finally {
90
- // Restore hydration state
91
- if (was_hydrating) {
92
- set_hydrating(true);
93
- set_hydrate_node(/** @type {any} */ (previous_hydrate_node));
94
- }
95
- }
169
+ create_portal(
170
+ () => props.target,
171
+ null,
172
+ () => props.children,
173
+ );
96
174
  });
97
175
  }
@@ -9,9 +9,10 @@ import {
9
9
  get_own_property_symbols,
10
10
  get_prototype_of,
11
11
  } from '@tsrx/core/runtime/language-helpers';
12
- import { event } from './events.js';
12
+ import { event_listener } from './events.js';
13
13
  import { get_attribute_event_name, is_event_attribute } from '@tsrx/core/runtime/events';
14
14
  import { get } from './runtime.js';
15
+ import { hydrating } from './hydration.js';
15
16
  import { clsx } from 'clsx';
16
17
  import { normalize_css_property_name } from '@tsrx/core/runtime/html';
17
18
 
@@ -21,12 +22,14 @@ import { normalize_css_property_name } from '@tsrx/core/runtime/html';
21
22
  * @returns {void}
22
23
  */
23
24
  export function set_text(text, value) {
24
- // For objects, we apply string coercion
25
- var str = value == null ? '' : typeof value === 'object' ? value + '' : value;
26
- if (str !== (text.__t ??= text.nodeValue)) {
27
- text.__t = str;
28
- text.nodeValue = str + '';
25
+ var str = value == null ? '' : value + '';
26
+ // The compiled render block compares against the value it last wrote, so
27
+ // nothing is cached on the node. Only server-rendered text can already
28
+ // hold the value; a fresh template text node never does.
29
+ if (hydrating && text.nodeValue === str) {
30
+ return;
29
31
  }
32
+ text.nodeValue = str;
30
33
  }
31
34
 
32
35
  /** @type {Map<string, string[]>} */
@@ -151,7 +154,7 @@ function set_attribute_helper(element, key, value, remove_listeners, prev) {
151
154
  }
152
155
  if (value != null) {
153
156
  const event_name = get_attribute_event_name(key, value);
154
- remove_listeners[key] = event(event_name, element, value);
157
+ remove_listeners[key] = event_listener(event_name, element, value);
155
158
  }
156
159
  } else {
157
160
  set_attribute(element, key, value);
@@ -168,23 +171,28 @@ function set_attribute_helper(element, key, value, remove_listeners, prev) {
168
171
  export function set_class(dom, value, hash, is_html = true) {
169
172
  var class_value =
170
173
  value == null
171
- ? (hash ?? '')
174
+ ? hash === undefined
175
+ ? null
176
+ : hash
172
177
  : // Fast-path for string values
173
178
  typeof value === 'string'
174
179
  ? value + (hash ? ' ' + hash : '')
175
180
  : clsx([value, hash]);
176
181
 
177
- // Removing the attribute when the value is only an empty string causes
178
- // performance issues vs simply making the className an empty string. So
179
- // we should only remove the class if the value is nullish.
180
- if (value == null && hash === undefined) {
182
+ // The compiled render block compares against the class it last applied,
183
+ // so nothing is cached on the element. Removing the attribute when the
184
+ // value is only an empty string costs more than an empty className, so the
185
+ // class is removed only for a nullish value; an element that has no class
186
+ // does not receive an empty one either (the server omits an empty class
187
+ // attribute as well).
188
+ if (class_value === null) {
181
189
  dom.removeAttribute('class');
182
- } else {
183
- if (is_html) {
190
+ } else if (is_html) {
191
+ if (class_value !== '' || dom.className !== '') {
184
192
  dom.className = class_value;
185
- } else {
186
- dom.setAttribute('class', class_value);
187
193
  }
194
+ } else if (class_value !== '' || dom.getAttribute('class')) {
195
+ dom.setAttribute('class', class_value);
188
196
  }
189
197
  }
190
198
 
@@ -254,10 +262,9 @@ export function set_selected(element, selected) {
254
262
  /**
255
263
  * @param {Element} element
256
264
  * @param {() => Record<string | symbol, any>} fn
257
- * @param {string} [exclude_prop]
258
265
  * @returns {() => void}
259
266
  */
260
- export function apply_element_spread(element, fn, exclude_prop) {
267
+ export function apply_element_spread(element, fn) {
261
268
  /** @type {Record<string | symbol, any>} */
262
269
  var prev = {};
263
270
  /** @type {Record<string | symbol, Block | undefined>} */
@@ -304,10 +311,9 @@ export function apply_element_spread(element, fn, exclude_prop) {
304
311
 
305
312
  /** @type {Record<string, any>} */
306
313
  var current_ref_props = {};
314
+ var keys = spread_keys(next);
307
315
 
308
- for (const key in next) {
309
- if (key === exclude_prop) continue;
310
-
316
+ for (const key of keys) {
311
317
  const ref_fn = next[key];
312
318
  if (!is_ref_prop(ref_fn)) {
313
319
  continue;
@@ -338,14 +344,14 @@ export function apply_element_spread(element, fn, exclude_prop) {
338
344
 
339
345
  for (let key in remove_listeners) {
340
346
  // Remove event listeners that are no longer present
341
- if ((!(key in next) || is_ref_prop(next[key])) && remove_listeners[key]) {
347
+ if ((!keys.includes(key) || is_ref_prop(next[key])) && remove_listeners[key]) {
342
348
  remove_listeners[key]();
343
349
  remove_listeners[key] = undefined;
344
350
  }
345
351
  }
346
352
 
347
353
  for (const key in prev) {
348
- if (!(key in next) || is_ref_prop(next[key])) {
354
+ if (!keys.includes(key) || is_ref_prop(next[key])) {
349
355
  if (key === '#class') {
350
356
  continue;
351
357
  }
@@ -355,8 +361,8 @@ export function apply_element_spread(element, fn, exclude_prop) {
355
361
 
356
362
  /** @type {typeof prev} */
357
363
  const current = {};
358
- for (const key in next) {
359
- if (key === 'children' || key === exclude_prop) continue;
364
+ for (const key of keys) {
365
+ if (key === 'children') continue;
360
366
 
361
367
  let value = next[key];
362
368
  if (is_ref_prop(value)) {
@@ -377,6 +383,19 @@ export function apply_element_spread(element, fn, exclude_prop) {
377
383
  };
378
384
  }
379
385
 
386
+ /**
387
+ * The keys an element spread applies: every prop of a props instance, or the
388
+ * enumerable keys of a plain object.
389
+ * @param {Record<string | symbol, any>} next
390
+ * @returns {string[]}
391
+ */
392
+ function spread_keys(next) {
393
+ /** @type {string[]} */
394
+ var keys = [];
395
+ for (var key in next) keys.push(key);
396
+ return keys;
397
+ }
398
+
380
399
  /**
381
400
  * Keep spread refs in a branch block so ordinary spread updates do not destroy
382
401
  * and recreate the ref block before `apply_element_spread` can compare the
@@ -1,11 +1,11 @@
1
- import * as devalue from 'devalue';
1
+ import { encode, revive } from './transport.js';
2
2
 
3
3
  /**
4
4
  * @param {string} hash
5
5
  * @param {any[]} args
6
6
  */
7
7
  export async function rpc(hash, args) {
8
- const body = devalue.stringify(args);
8
+ const body = encode(args);
9
9
  /** @type {Response} */
10
10
  let response;
11
11
 
@@ -55,5 +55,5 @@ export async function rpc(hash, args) {
55
55
  );
56
56
  }
57
57
 
58
- return devalue.parse(data).value;
58
+ return revive(data).value;
59
59
  }