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,10 +1,9 @@
1
- /** @import { Block, Derived, Component } from '#client' */
1
+ /** @import { AppendIntoAnchor, Block, Derived, Component } from '#client' */
2
2
 
3
3
  import {
4
4
  BLOCK_HAS_RUN,
5
5
  BRANCH_BLOCK,
6
6
  DERIVED,
7
- CONTAINS_TEARDOWN,
8
7
  DESTROYED,
9
8
  EFFECT_BLOCK,
10
9
  PAUSED,
@@ -12,10 +11,12 @@ import {
12
11
  RENDER_BLOCK,
13
12
  ROOT_BLOCK,
14
13
  TRY_BLOCK,
14
+ DETACHED_BLOCK,
15
15
  HEAD_BLOCK,
16
- DIRECT_CHILD_BLOCK,
17
- UNINITIALIZED,
16
+ IF_BLOCK,
17
+ CREATES_DERIVEDS,
18
18
  } from './constants.js';
19
+ import { hydrating } from './hydration.js';
19
20
  import { next_sibling } from './operations.js';
20
21
  import { apply_element_spread } from './render.js';
21
22
  import { is_array } from '@tsrx/core/runtime/language-helpers';
@@ -23,8 +24,10 @@ import {
23
24
  active_block,
24
25
  active_component,
25
26
  active_reaction,
26
- create_component_ctx,
27
27
  is_block_dirty,
28
+ release_deriveds,
29
+ take_created_deriveds,
30
+ remove_dependencies,
28
31
  run_block,
29
32
  run_teardown,
30
33
  schedule_update,
@@ -44,12 +47,9 @@ export function user_effect(fn) {
44
47
 
45
48
  var component = active_component;
46
49
  if (component !== null && !component.m) {
50
+ // Flat triples, created once the component has rendered (`pop_component`).
47
51
  var e = (component.e ??= []);
48
- e.push({
49
- b: active_block,
50
- fn,
51
- r: active_reaction,
52
- });
52
+ e.push(fn, active_block, active_reaction);
53
53
 
54
54
  return;
55
55
  }
@@ -78,17 +78,20 @@ export function pre_effect(fn) {
78
78
  * @param {number} [flags]
79
79
  */
80
80
  export function render(fn, state, flags = 0) {
81
- return block(RENDER_BLOCK | flags, fn, state);
81
+ // block(), inline: a render block runs at once.
82
+ var block = create_block(RENDER_BLOCK | flags, fn, state);
83
+ run_block(block, true);
84
+ block.f ^= BLOCK_HAS_RUN;
85
+ return block;
82
86
  }
83
87
 
84
88
  /**
85
89
  * @param {any} element
86
90
  * @param {any} fn
87
91
  * @param {number} [flags]
88
- * @param {string} [exclude_prop]
89
92
  */
90
- export function render_spread(element, fn, flags = 0, exclude_prop) {
91
- return block(RENDER_BLOCK | flags, apply_element_spread(element, fn, exclude_prop));
93
+ export function render_spread(element, fn, flags = 0) {
94
+ return block(RENDER_BLOCK | flags, apply_element_spread(element, fn));
92
95
  }
93
96
 
94
97
  /**
@@ -100,6 +103,24 @@ export function branch(fn, flags = 0, state = null) {
100
103
  return block(BRANCH_BLOCK | flags, fn, state);
101
104
  }
102
105
 
106
+ function noop() {}
107
+
108
+ /**
109
+ * Gives a text anchor materialized by `resolve_anchor` a branch block of its
110
+ * own, so destroying the enclosing block removes the anchor along with the
111
+ * content: a portal target outlives its content, and nothing else owns a node
112
+ * appended there. Call it after creating the anchored block so block order
113
+ * matches DOM order (the content is inserted before the anchor). A no-op when
114
+ * `anchor` is the node as received (an ordinary anchor belongs to a template)
115
+ * or the SSR marker the sentinel resolves to during hydration.
116
+ * @param {Node | AppendIntoAnchor} node
117
+ * @param {Node} anchor
118
+ */
119
+ export function own_anchor(node, anchor) {
120
+ if (anchor === node || hydrating) return;
121
+ branch(noop, 0, { start: anchor, end: anchor });
122
+ }
123
+
103
124
  /**
104
125
  * Wire up a `ref={expr}` attribute. `expr` may be:
105
126
  * - a callback function — invoked with the element on mount; if it returns
@@ -114,89 +135,95 @@ export function branch(fn, flags = 0, state = null) {
114
135
  * doesn't subscribe to whatever the thunk happens to read. The supported
115
136
  * shape is to pass the ref slot itself (`ref={tracker}`); a foot-gun like
116
137
  * `ref={tracker.value}` would otherwise read the cell reactively and cause
117
- * spurious re-runs.
138
+ * spurious re-runs. Read untracked, the value is fixed for the life of the
139
+ * enclosing block, so each ref is a single effect block keyed on its state
140
+ * rather than a render block re-evaluating the thunk.
118
141
  *
119
142
  * @param {Element} element
120
143
  * @param {() => any} get_fn
121
144
  * @param {(value: any) => void} [set_fn]
122
- * @returns {Block}
145
+ * @returns {void}
123
146
  */
124
147
  export function ref(element, get_fn, set_fn) {
125
- // make sure the first run always enters the dispatch branch,
126
- /** @type {any} */
127
- var ref_value = UNINITIALIZED;
128
- /** @type {Block | null} */
129
- var e;
130
-
131
- return block(RENDER_BLOCK, () => {
132
- // avoid any reactive reads
133
- var next = untrack(get_fn);
134
- if (ref_value !== (ref_value = next)) {
135
- if (e) {
136
- destroy_block(e);
137
- e = null;
138
- }
148
+ apply_ref(element, untrack(get_fn), set_fn);
149
+ }
139
150
 
140
- if (is_array(ref_value)) {
141
- e = branch(() => {
142
- for (var i = 0; i < ref_value.length; i++) {
143
- let current = ref_value[i];
144
- ref(element, () => current);
145
- }
146
- });
147
- } else if (typeof ref_value === 'function') {
148
- e = branch(() => {
149
- effect(() => ref_value(element));
150
- });
151
- } else if (is_ripple_object(ref_value)) {
152
- e = branch(() => {
153
- effect(() => {
154
- ref_value.value = element;
155
- return () => {
156
- ref_value.value = null;
157
- };
158
- });
159
- });
160
- } else if (set_fn !== undefined) {
161
- e = branch(() => {
162
- effect(() => {
163
- set_fn(element);
164
- return () => {
165
- set_fn(null);
166
- };
167
- });
168
- });
169
- }
151
+ /**
152
+ * @param {Element} element
153
+ * @param {any} ref_value
154
+ * @param {((value: any) => void) | undefined} set_fn
155
+ * @returns {void}
156
+ */
157
+ function apply_ref(element, ref_value, set_fn) {
158
+ if (is_array(ref_value)) {
159
+ for (var i = 0; i < ref_value.length; i++) {
160
+ apply_ref(element, ref_value[i], undefined);
170
161
  }
171
- });
162
+ } else if (typeof ref_value === 'function') {
163
+ block(EFFECT_BLOCK, run_function_ref, { e: element, f: ref_value });
164
+ } else if (is_ripple_object(ref_value)) {
165
+ block(EFFECT_BLOCK, run_tracked_ref, { e: element, t: ref_value });
166
+ } else if (set_fn !== undefined) {
167
+ block(EFFECT_BLOCK, run_set_ref, { e: element, f: set_fn });
168
+ }
172
169
  }
173
170
 
174
171
  /**
175
- * @param {() => (void | (() => void))} fn
176
- * @returns {Block}
172
+ * The callback's return value is the effect's teardown.
173
+ * @param {{ e: Element, f: (element: Element) => any }} s
177
174
  */
178
- export function root(fn) {
179
- return block(ROOT_BLOCK, fn, { start: null, end: null }, create_component_ctx());
175
+ function run_function_ref(s) {
176
+ return s.f(s.e);
180
177
  }
181
178
 
182
179
  /**
183
- * @param {() => void} fn
184
- * @param {any} state
185
- * @returns {Block}
180
+ * @param {{ e: Element, t: { value: any } }} s
186
181
  */
187
- export function create_try_block(fn, state) {
188
- return block(TRY_BLOCK, fn, state);
182
+ function run_tracked_ref(s) {
183
+ s.t.value = s.e;
184
+ return clear_tracked_ref;
189
185
  }
190
186
 
191
187
  /**
192
- * @param {() => void} fn
193
- * @param {number} [flags]
194
- * @param {any} [state]
188
+ * Teardowns keyed on block state receive it (see `run_teardown`).
189
+ * @param {{ e: Element, t: { value: any } }} s
190
+ */
191
+ function clear_tracked_ref(s) {
192
+ s.t.value = null;
193
+ }
194
+
195
+ /**
196
+ * @param {{ e: Element, f: (value: any) => void }} s
197
+ */
198
+ function run_set_ref(s) {
199
+ s.f(s.e);
200
+ return clear_set_ref;
201
+ }
202
+
203
+ /**
204
+ * @param {{ e: Element, f: (value: any) => void }} s
205
+ */
206
+ function clear_set_ref(s) {
207
+ s.f(null);
208
+ }
209
+
210
+ /**
211
+ * @param {() => (void | (() => void))} fn
212
+ * @returns {Block}
195
213
  */
196
- export function boundary_fn_running_block(fn, flags = 0, state = null) {
197
- return branch(fn, DIRECT_CHILD_BLOCK | flags, state);
214
+ export function root(fn) {
215
+ // create_component_ctx, inline: the root is the first component context.
216
+ return block(
217
+ ROOT_BLOCK,
218
+ fn,
219
+ { start: null, end: null },
220
+ { b: active_block, c: null, e: null, m: false, p: active_component },
221
+ );
198
222
  }
199
223
 
224
+ /** Creation counter: a parent always has a lower id than its descendants. */
225
+ var block_id = 0;
226
+
200
227
  /**
201
228
  * @param {Block} block
202
229
  * @param {Block} parent_block
@@ -220,6 +247,27 @@ function push_block(block, parent_block) {
220
247
  * @returns {Block}
221
248
  */
222
249
  export function block(flags, fn, state = null, co) {
250
+ var block = create_block(flags, fn, state, co);
251
+
252
+ if ((flags & EFFECT_BLOCK) !== 0) {
253
+ schedule_update(block);
254
+ } else {
255
+ run_block(block, true);
256
+ block.f ^= BLOCK_HAS_RUN;
257
+ }
258
+
259
+ return block;
260
+ }
261
+
262
+ /**
263
+ * Allocates a block and links it under the active block without running it.
264
+ * @param {number} flags
265
+ * @param {Function} fn
266
+ * @param {any} [state]
267
+ * @param {Component} [co]
268
+ * @returns {Block}
269
+ */
270
+ export function create_block(flags, fn, state = null, co) {
223
271
  /** @type {Block} */
224
272
  var block = {
225
273
  co: co || active_component,
@@ -227,6 +275,7 @@ export function block(flags, fn, state = null, co) {
227
275
  first: null,
228
276
  f: flags,
229
277
  fn,
278
+ i: ++block_id,
230
279
  last: null,
231
280
  next: null,
232
281
  p: active_block,
@@ -240,15 +289,17 @@ export function block(flags, fn, state = null, co) {
240
289
  (/** @type {Derived} */ (active_reaction).blocks ??= []).push(block);
241
290
  }
242
291
 
243
- if (active_block !== null) {
244
- push_block(block, active_block);
245
- }
246
-
247
- if ((flags & EFFECT_BLOCK) !== 0) {
248
- schedule_update(block);
249
- } else {
250
- run_block(block);
251
- block.f ^= BLOCK_HAS_RUN;
292
+ var parent_block = active_block;
293
+ if (parent_block !== null) {
294
+ // push_block, inline: block creation is the hot path.
295
+ var parent_last = parent_block.last;
296
+ if (parent_last === null) {
297
+ parent_block.last = parent_block.first = block;
298
+ } else {
299
+ parent_last.next = block;
300
+ block.prev = parent_last;
301
+ parent_block.last = block;
302
+ }
252
303
  }
253
304
 
254
305
  return block;
@@ -262,12 +313,10 @@ export function destroy_block_children(parent, remove_dom = false) {
262
313
  var block = parent.first;
263
314
  parent.first = parent.last = null;
264
315
 
265
- if (remove_dom || (parent.f & CONTAINS_TEARDOWN) !== 0) {
266
- while (block !== null) {
267
- var next = block.next;
268
- destroy_block(block, remove_dom);
269
- block = next;
270
- }
316
+ while (block !== null) {
317
+ var next = block.next;
318
+ destroy_block(block, remove_dom);
319
+ block = next;
271
320
  }
272
321
  }
273
322
 
@@ -278,20 +327,12 @@ export function destroy_block_children(parent, remove_dom = false) {
278
327
  export function destroy_non_branch_children(parent, remove_dom = false) {
279
328
  var block = parent.first;
280
329
 
281
- if (
282
- (parent.f & CONTAINS_TEARDOWN) === 0 &&
283
- parent.first !== null &&
284
- (parent.first.f & BRANCH_BLOCK) === 0
285
- ) {
286
- parent.first = parent.last = null;
287
- } else {
288
- while (block !== null) {
289
- var next = block.next;
290
- if ((block.f & BRANCH_BLOCK) === 0) {
291
- destroy_block(block, remove_dom);
292
- }
293
- block = next;
330
+ while (block !== null) {
331
+ var next = block.next;
332
+ if ((block.f & BRANCH_BLOCK) === 0) {
333
+ destroy_block(block, remove_dom);
294
334
  }
335
+ block = next;
295
336
  }
296
337
  }
297
338
 
@@ -312,6 +353,23 @@ export function unlink_block(block) {
312
353
  }
313
354
  }
314
355
 
356
+ /**
357
+ * Moves `block` to the end of its parent's child list. A block whose content
358
+ * is inserted before an anchor the parent already owns is created after the
359
+ * anchor's block; relinking keeps block order equal to DOM order, which the
360
+ * first/last node descent in `get_first_node`/`get_last_node` relies on.
361
+ * @param {Block} block
362
+ */
363
+ export function move_block_last(block) {
364
+ var parent = /** @type {Block} */ (block.p);
365
+ if (parent.last === block) {
366
+ return;
367
+ }
368
+ unlink_block(block);
369
+ block.prev = block.next = null;
370
+ push_block(block, parent);
371
+ }
372
+
315
373
  /**
316
374
  * @param {Block} block
317
375
  */
@@ -401,8 +459,8 @@ export function remove_block_dom(node, end) {
401
459
  export function move_block(block, target) {
402
460
  var f = block.f;
403
461
 
404
- // Only BRANCH_BLOCKs (excluding TRY_BLOCK) can have DOM state to move
405
- if ((f & BRANCH_BLOCK) !== 0 && (f & TRY_BLOCK) === 0) {
462
+ // Only branch and if blocks (excluding TRY_BLOCK) can have DOM state to move
463
+ if ((f & (BRANCH_BLOCK | IF_BLOCK)) !== 0 && (f & TRY_BLOCK) === 0) {
406
464
  var s = block.s;
407
465
  if (s !== null && s.start !== null) {
408
466
  var node = s.start;
@@ -413,6 +471,10 @@ export function move_block(block, target) {
413
471
  target.append(node);
414
472
  node = next;
415
473
  }
474
+ // An if's materialized anchor follows its range.
475
+ if ((f & IF_BLOCK) !== 0 && s.o !== null) {
476
+ move_block(s.o, target);
477
+ }
416
478
  return true;
417
479
  }
418
480
  }
@@ -430,8 +492,8 @@ export function move_block(block, target) {
430
492
  }
431
493
 
432
494
  /**
433
- * Resolve the first DOM node owned by a block. A block normally records its
434
- * range in `s.start`/`s.end`, but an optimized single control-flow / component
495
+ * Resolve the first DOM node owned by a block. A branch or if block normally
496
+ * records its range in `s.start`/`s.end`, but an optimized single control-flow / component
435
497
  * root scope renders its content through a descendant block instead of a
436
498
  * synthesized `<!>` wrapper, so its own `s.start` is null. In that case we
437
499
  * descend into child blocks to find the real first node. Returns null when the
@@ -441,7 +503,7 @@ export function move_block(block, target) {
441
503
  */
442
504
  export function get_first_node(block) {
443
505
  var f = block.f;
444
- if ((f & BRANCH_BLOCK) !== 0 && (f & TRY_BLOCK) === 0) {
506
+ if ((f & (BRANCH_BLOCK | IF_BLOCK)) !== 0 && (f & TRY_BLOCK) === 0) {
445
507
  var s = block.s;
446
508
  if (s !== null && s.start !== null) {
447
509
  return s.start;
@@ -465,10 +527,11 @@ export function get_first_node(block) {
465
527
  */
466
528
  export function get_last_node(block) {
467
529
  var f = block.f;
468
- if ((f & BRANCH_BLOCK) !== 0 && (f & TRY_BLOCK) === 0) {
530
+ if ((f & (BRANCH_BLOCK | IF_BLOCK)) !== 0 && (f & TRY_BLOCK) === 0) {
469
531
  var s = block.s;
470
532
  if (s !== null && s.start !== null) {
471
- return s.end;
533
+ // An if's materialized anchor is its last node.
534
+ return (f & IF_BLOCK) !== 0 && s.o !== null ? s.o.s.end : s.end;
472
535
  }
473
536
  }
474
537
  var child = block.last;
@@ -487,13 +550,18 @@ export function get_last_node(block) {
487
550
  * @param {boolean} [remove_dom]
488
551
  */
489
552
  export function destroy_block(block, remove_dom = true) {
490
- block.f ^= DESTROYED;
491
-
553
+ var f = (block.f |= DESTROYED);
492
554
  var removed = false;
493
- var f = block.f;
555
+
556
+ // An ancestor that removed its own range tells its subtree not to bother,
557
+ // but detached content (a portal target, `<head>`) is not inside that
558
+ // range and must still be removed, by this block or by its children.
559
+ if ((f & DETACHED_BLOCK) !== 0) {
560
+ remove_dom = true;
561
+ }
494
562
 
495
563
  if (
496
- (remove_dom && (f & (BRANCH_BLOCK | ROOT_BLOCK)) !== 0 && (f & TRY_BLOCK) === 0) ||
564
+ (remove_dom && (f & (BRANCH_BLOCK | ROOT_BLOCK | IF_BLOCK)) !== 0 && (f & TRY_BLOCK) === 0) ||
497
565
  (f & HEAD_BLOCK) !== 0
498
566
  ) {
499
567
  var s = block.s;
@@ -507,6 +575,16 @@ export function destroy_block(block, remove_dom = true) {
507
575
 
508
576
  run_teardown(block);
509
577
 
578
+ if (block.d !== null) {
579
+ remove_dependencies(block);
580
+ }
581
+ if ((f & CREATES_DERIVEDS) !== 0) {
582
+ var deriveds = take_created_deriveds(block);
583
+ if (deriveds !== null) {
584
+ release_deriveds(deriveds);
585
+ }
586
+ }
587
+
510
588
  var parent = block.p;
511
589
 
512
590
  // If the parent doesn't have any children, then skip this work altogether
@@ -1,36 +1,10 @@
1
- /** @import { AppendIntoAnchor, Block } from '#client' */
1
+ /** @import { Block } from '#client' */
2
2
 
3
3
  import { is_tsrx_element } from '../../element.js';
4
4
  import { render_value } from './expression.js';
5
- import { active_block, pop_component, push_component } from './runtime.js';
5
+ import { active_block } from './runtime.js';
6
6
 
7
- /**
8
- * @param {Function} fn
9
- * @param {Node | AppendIntoAnchor} anchor
10
- * @param {Record<string, any>} props
11
- * @param {Block | null} [block=active_block]
12
- * @returns {void}
13
- */
14
- export function render_component(fn, anchor, props, block = active_block) {
15
- if (typeof fn !== 'function' || is_tsrx_element(fn)) {
16
- throw_invalid_component_type(fn);
17
- }
18
-
19
- run_component(fn, anchor, props, block);
20
- }
21
-
22
- /**
23
- * @param {Function} fn
24
- * @param {Node | AppendIntoAnchor} anchor
25
- * @param {Record<string, any>} props
26
- * @param {Block | null} [block=active_block]
27
- * @returns {void}
28
- */
29
- function run_component(fn, anchor, props, block = active_block) {
30
- push_component();
31
- render_value(fn(props), /** @type {ChildNode} */ (anchor), block);
32
- pop_component();
33
- }
7
+ export { render_component } from './runtime.js';
34
8
 
35
9
  /**
36
10
  * @param {import('../../element.js').TSRXElement} value
@@ -46,7 +20,7 @@ export function render_tsrx_element(value, anchor, block = active_block) {
46
20
  * @param {any} value
47
21
  * @returns {never}
48
22
  */
49
- function throw_invalid_component_type(value) {
23
+ export function throw_invalid_component_type(value) {
50
24
  if (is_tsrx_element(value)) {
51
25
  throw new TypeError('Invalid component type: received a TSRXElement value.');
52
26
  }
@@ -1,11 +1,10 @@
1
1
  /** @import { Block } from '#client' */
2
2
 
3
- import { exclude_prop_from_object } from '@tsrx/core/runtime/language-helpers';
4
3
  import { branch, destroy_block, render, render_spread } from './blocks.js';
5
4
  import { COMPOSITE_BLOCK, DEFAULT_NAMESPACE, NAMESPACE_URI } from './constants.js';
6
5
  import { hydrate_node, hydrate_next, hydrating, set_hydrate_node } from './hydration.js';
7
6
  import { first_child } from './operations.js';
8
- import { active_block, active_namespace, get, with_ns } from './runtime.js';
7
+ import { active_block, active_namespace, get, untrack, with_ns } from './runtime.js';
9
8
  import { top_element_to_ns } from './utils.js';
10
9
  import { is_tsrx_element } from '../../element.js';
11
10
  import { render_component } from './component.js';
@@ -14,11 +13,12 @@ import { render_component } from './component.js';
14
13
  * @typedef {Function | string | null | undefined | false} CompositeTarget
15
14
  * @param {() => CompositeTarget} get_component
16
15
  * @param {Node} node
17
- * @param {Record<string, any>} props
18
- * @param {string} [exclude_prop]
16
+ * @param {() => Record<string, any>} get_props the props literal of the call
17
+ * site: read once per rendered component (props are plain values), and
18
+ * re-read as element attributes when the target is a tag
19
19
  * @returns {void}
20
20
  */
21
- export function composite(get_component, node, props, exclude_prop) {
21
+ export function composite(get_component, node, get_props) {
22
22
  if (hydrating) {
23
23
  // During hydration, `node` may already point at the first real SSR node
24
24
  // (e.g. layout children). Only skip forward when we are on an empty
@@ -45,10 +45,8 @@ export function composite(get_component, node, props, exclude_prop) {
45
45
  if (typeof component === 'function') {
46
46
  // Handle as regular component
47
47
  b = branch(() => {
48
- const component_props = exclude_prop
49
- ? exclude_prop_from_object(props, exclude_prop)
50
- : props;
51
- render_component(component, anchor, component_props);
48
+ var props = untrack(get_props);
49
+ render_component(component, anchor, props);
52
50
  });
53
51
  } else if (is_tsrx_element(component)) {
54
52
  throw new TypeError('Invalid component type: received a TSRXElement value.');
@@ -82,9 +80,10 @@ export function composite(get_component, node, props, exclude_prop) {
82
80
  };
83
81
  }
84
82
 
85
- render_spread(element, () => props || {}, 0, exclude_prop);
83
+ render_spread(element, get_props, 0);
86
84
 
87
- if (is_tsrx_element(props?.children)) {
85
+ var props = untrack(get_props);
86
+ if (is_tsrx_element(props.children)) {
88
87
  /** @type {Node} */
89
88
  var child_anchor;
90
89
  if (hydrating) {
@@ -97,9 +96,9 @@ export function composite(get_component, node, props, exclude_prop) {
97
96
  }
98
97
 
99
98
  if (ns !== DEFAULT_NAMESPACE) {
100
- with_ns(ns, () => props.children.render(child_anchor, block));
99
+ with_ns(ns, () => props.children.render(child_anchor, block, props.children.p));
101
100
  } else {
102
- props.children.render(child_anchor, block);
101
+ props.children.render(child_anchor, block, props.children.p);
103
102
  }
104
103
 
105
104
  if (hydrating) {
@@ -11,7 +11,8 @@ export var ASYNC_BLOCK = 1 << 10;
11
11
  export var HEAD_BLOCK = 1 << 11;
12
12
  export var PRE_EFFECT_BLOCK = 1 << 12;
13
13
  export var DIRECT_CHILD_BLOCK = 1 << 13;
14
- export var CONTAINS_UPDATE = 1 << 14;
14
+ // Content rendered outside the parent's DOM range (`<head>`, a portal target).
15
+ export var DETACHED_BLOCK = 1 << 14;
15
16
  export var CONTAINS_TEARDOWN = 1 << 15;
16
17
  export var BLOCK_HAS_RUN = 1 << 16;
17
18
  export var TRACKED = 1 << 17;
@@ -19,10 +20,15 @@ export var DERIVED = 1 << 18;
19
20
  export var DEFERRED = 1 << 19;
20
21
  export var PAUSED = 1 << 20;
21
22
  export var DESTROYED = 1 << 21;
22
- // Marks a block that was directly scheduled (the owner of a mutated tracked).
23
- // Its whole subtree is scanned for dirty subscribers during flush; blocks above
24
- // it are only traversed along the CONTAINS_UPDATE routing path.
25
- export var UPDATE_SOURCE = 1 << 22;
23
+ // Marks a block that is queued for the next flush.
24
+ export var SCHEDULED = 1 << 22;
25
+ // A per-key match value created by a selector; its map entry is dropped once
26
+ // the last subscriber unlinks.
27
+ export var SELECTOR = 1 << 23;
28
+ // A selector key whose last subscriber left; kept for reuse until a sweep.
29
+ export var RELEASED = 1 << 24;
30
+ /** A block whose latest run created deriveds that are recorded for release (see `release_deriveds`). */
31
+ export var CREATES_DERIVEDS = 1 << 25;
26
32
 
27
33
  export var CONTROL_FLOW_BLOCK = FOR_BLOCK | IF_BLOCK | SWITCH_BLOCK | TRY_BLOCK | COMPOSITE_BLOCK;
28
34
 
@@ -53,3 +59,6 @@ export const SUSPENSE_PENDING = Symbol('suspense_pending');
53
59
  export const SUSPENSE_REJECTED = Symbol('suspense_rejected');
54
60
  /** @type {unique symbol} */
55
61
  export const ASYNC_DERIVED_READ_THROWN = Symbol('async_derived_read_thrown');
62
+
63
+ /** The hoisted render function a module-level component carries (see `render_component`). */
64
+ export const RENDER_ENTRY = Symbol('render');