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
@@ -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
 
@@ -37,8 +49,6 @@ export function init_operations() {
37
49
 
38
50
  // the following assignments improve perf of lookups on DOM nodes
39
51
  element_prototype.__click = undefined;
40
- element_prototype.__className = undefined;
41
- Text.prototype.__t = undefined;
42
52
  event_target_prototype.__root = undefined;
43
53
  }
44
54
 
@@ -73,28 +83,6 @@ export function first_child(node, is_text) {
73
83
  return hydrate_first_child(is_text);
74
84
  }
75
85
 
76
- /**
77
- * @param {boolean} [is_text]
78
- * @returns {Node | null}
79
- */
80
- export function hydrate_first_child(is_text) {
81
- var child = get_first_child(/** @type {Node} */ (hydrate_node));
82
-
83
- // Handles the case where we have `<p>{text}</p>`, where `text` is empty
84
- if (child === null) {
85
- child = /** @type {Node} */ (hydrate_node).appendChild(create_text());
86
- } else if (is_text && child.nodeType !== TEXT_NODE) {
87
- var text = create_text();
88
- /** @type {Element | Text | Comment} */ (child)?.before(text);
89
- set_hydrate_node(text);
90
- return text;
91
- }
92
-
93
- set_hydrate_node(child);
94
-
95
- return child;
96
- }
97
-
98
86
  /**
99
87
  * Anchor sentinel for all-component children: components render directly into
100
88
  * `parent` instead of each inserting before a placeholder comment. `append()`
@@ -115,7 +103,29 @@ export function append_into(parent) {
115
103
  set_hydrate_node(child);
116
104
  }
117
105
 
118
- 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());
119
129
  }
120
130
 
121
131
  /**
@@ -160,37 +170,6 @@ export function next_sibling(node, is_text) {
160
170
  return hydrate_next_sibling(is_text);
161
171
  }
162
172
 
163
- /**
164
- * @param {boolean} [is_text]
165
- * @returns {Node | null}
166
- */
167
- export function hydrate_next_sibling(is_text) {
168
- var next_sibling = /** @type {ChildNode | null} */ (
169
- get_next_sibling(/** @type {ChildNode} */ (hydrate_node))
170
- );
171
- var last_sibling = next_sibling;
172
-
173
- // if a sibling {expression} is empty during SSR, there might be no
174
- // text node to hydrate — we must therefore create one
175
- if (is_text && next_sibling?.nodeType !== TEXT_NODE) {
176
- var text = create_text();
177
- // If the next sibling is `null` and we're handling text then it's because
178
- // the SSR content was empty for the text, so we need to generate a new text
179
- // node and insert it after the last sibling
180
- if (next_sibling === null) {
181
- /** @type {ChildNode} */ (last_sibling).after(text);
182
- } else {
183
- /** @type {ChildNode} */ (next_sibling).before(text);
184
- }
185
- set_hydrate_node(text);
186
- return text;
187
- }
188
-
189
- set_hydrate_node(next_sibling);
190
-
191
- return next_sibling;
192
- }
193
-
194
173
  export function create_text(value = '') {
195
174
  return document.createTextNode(value);
196
175
  }
@@ -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,7 +9,7 @@ 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
15
  import { hydrating } from './hydration.js';
@@ -22,18 +22,14 @@ import { normalize_css_property_name } from '@tsrx/core/runtime/html';
22
22
  * @returns {void}
23
23
  */
24
24
  export function set_text(text, value) {
25
- // For objects, we apply string coercion
26
- var str = value == null ? '' : typeof value === 'object' ? value + '' : value;
27
- var previous = text.__t;
28
- // Only server-rendered text can already hold the value; a fresh template
29
- // text node never does, so skip reading it back.
30
- if (previous === undefined && hydrating) {
31
- previous = text.__t = text.nodeValue;
32
- }
33
- if (str !== previous) {
34
- text.__t = str;
35
- 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;
36
31
  }
32
+ text.nodeValue = str;
37
33
  }
38
34
 
39
35
  /** @type {Map<string, string[]>} */
@@ -72,7 +68,7 @@ function get_setters(element) {
72
68
  /**
73
69
  * @param {Element} element
74
70
  * @param {any} value
75
- * @param {Record<string, string> | undefined} prev
71
+ * @param {Record<string, string | number | null | undefined> | undefined} prev
76
72
  * @returns {void}
77
73
  */
78
74
  export function set_style(element, value, prev = {}) {
@@ -104,8 +100,8 @@ export function set_attribute(element, attribute, value) {
104
100
 
105
101
  /**
106
102
  * @param {HTMLElement} element
107
- * @param {Record<string, string | number>} new_styles
108
- * @param {Record<string, string>} prev
103
+ * @param {Record<string, string | number | null | undefined>} new_styles
104
+ * @param {Record<string, string | number | null | undefined>} prev
109
105
  */
110
106
  function apply_styles(element, new_styles, prev) {
111
107
  const style = element.style;
@@ -113,7 +109,8 @@ function apply_styles(element, new_styles, prev) {
113
109
  // Apply new styles
114
110
  for (const key in new_styles) {
115
111
  const css_prop = normalize_css_property_name(key);
116
- const value = String(new_styles[key]);
112
+ const raw_value = new_styles[key];
113
+ const value = raw_value == null ? null : String(raw_value);
117
114
 
118
115
  if (!(key in prev) || prev[key] !== value) {
119
116
  style.setProperty(css_prop, value);
@@ -158,7 +155,7 @@ function set_attribute_helper(element, key, value, remove_listeners, prev) {
158
155
  }
159
156
  if (value != null) {
160
157
  const event_name = get_attribute_event_name(key, value);
161
- remove_listeners[key] = event(event_name, element, value);
158
+ remove_listeners[key] = event_listener(event_name, element, value);
162
159
  }
163
160
  } else {
164
161
  set_attribute(element, key, value);
@@ -183,27 +180,20 @@ export function set_class(dom, value, hash, is_html = true) {
183
180
  ? value + (hash ? ' ' + hash : '')
184
181
  : clsx([value, hash]);
185
182
 
186
- // Skip the DOM write when the class we last applied is unchanged, or when
187
- // an element that never had a class would only receive an empty one (the
188
- // server omits an empty class attribute as well).
189
- var previous = dom.__className;
190
- if (previous === class_value || (previous === undefined && class_value === '')) {
191
- dom.__className = class_value;
192
- return;
193
- }
194
- dom.__className = class_value;
195
-
196
- // Removing the attribute when the value is only an empty string causes
197
- // performance issues vs simply making the className an empty string. So
198
- // we should only remove the class if the value is nullish.
183
+ // The compiled render block compares against the class it last applied,
184
+ // so nothing is cached on the element. Removing the attribute when the
185
+ // value is only an empty string costs more than an empty className, so the
186
+ // class is removed only for a nullish value; an element that has no class
187
+ // does not receive an empty one either (the server omits an empty class
188
+ // attribute as well).
199
189
  if (class_value === null) {
200
190
  dom.removeAttribute('class');
201
- } else {
202
- if (is_html) {
191
+ } else if (is_html) {
192
+ if (class_value !== '' || dom.className !== '') {
203
193
  dom.className = class_value;
204
- } else {
205
- dom.setAttribute('class', class_value);
206
194
  }
195
+ } else if (class_value !== '' || dom.getAttribute('class')) {
196
+ dom.setAttribute('class', class_value);
207
197
  }
208
198
  }
209
199
 
@@ -273,10 +263,9 @@ export function set_selected(element, selected) {
273
263
  /**
274
264
  * @param {Element} element
275
265
  * @param {() => Record<string | symbol, any>} fn
276
- * @param {string} [exclude_prop]
277
266
  * @returns {() => void}
278
267
  */
279
- export function apply_element_spread(element, fn, exclude_prop) {
268
+ export function apply_element_spread(element, fn) {
280
269
  /** @type {Record<string | symbol, any>} */
281
270
  var prev = {};
282
271
  /** @type {Record<string | symbol, Block | undefined>} */
@@ -323,10 +312,9 @@ export function apply_element_spread(element, fn, exclude_prop) {
323
312
 
324
313
  /** @type {Record<string, any>} */
325
314
  var current_ref_props = {};
315
+ var keys = spread_keys(next);
326
316
 
327
- for (const key in next) {
328
- if (key === exclude_prop) continue;
329
-
317
+ for (const key of keys) {
330
318
  const ref_fn = next[key];
331
319
  if (!is_ref_prop(ref_fn)) {
332
320
  continue;
@@ -357,14 +345,14 @@ export function apply_element_spread(element, fn, exclude_prop) {
357
345
 
358
346
  for (let key in remove_listeners) {
359
347
  // Remove event listeners that are no longer present
360
- if ((!(key in next) || is_ref_prop(next[key])) && remove_listeners[key]) {
348
+ if ((!keys.includes(key) || is_ref_prop(next[key])) && remove_listeners[key]) {
361
349
  remove_listeners[key]();
362
350
  remove_listeners[key] = undefined;
363
351
  }
364
352
  }
365
353
 
366
354
  for (const key in prev) {
367
- if (!(key in next) || is_ref_prop(next[key])) {
355
+ if (!keys.includes(key) || is_ref_prop(next[key])) {
368
356
  if (key === '#class') {
369
357
  continue;
370
358
  }
@@ -374,8 +362,8 @@ export function apply_element_spread(element, fn, exclude_prop) {
374
362
 
375
363
  /** @type {typeof prev} */
376
364
  const current = {};
377
- for (const key in next) {
378
- if (key === 'children' || key === exclude_prop) continue;
365
+ for (const key of keys) {
366
+ if (key === 'children') continue;
379
367
 
380
368
  let value = next[key];
381
369
  if (is_ref_prop(value)) {
@@ -396,6 +384,19 @@ export function apply_element_spread(element, fn, exclude_prop) {
396
384
  };
397
385
  }
398
386
 
387
+ /**
388
+ * The keys an element spread applies: every prop of a props instance, or the
389
+ * enumerable keys of a plain object.
390
+ * @param {Record<string | symbol, any>} next
391
+ * @returns {string[]}
392
+ */
393
+ function spread_keys(next) {
394
+ /** @type {string[]} */
395
+ var keys = [];
396
+ for (var key in next) keys.push(key);
397
+ return keys;
398
+ }
399
+
399
400
  /**
400
401
  * Keep spread refs in a branch block so ordinary spread updates do not destroy
401
402
  * 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
  }