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,6 +1,7 @@
1
1
  import { output_push, noop, tsrx_element } from './internal/server/index.js';
2
2
 
3
3
  export { Fragment } from '../jsx-runtime.js';
4
+ export { set_transport as setTransport } from './internal/server/transport.js';
4
5
  export { Context } from './internal/server/context.js';
5
6
  export {
6
7
  untrack,
@@ -1,4 +1,4 @@
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,
@@ -11,10 +11,12 @@ import {
11
11
  RENDER_BLOCK,
12
12
  ROOT_BLOCK,
13
13
  TRY_BLOCK,
14
+ DETACHED_BLOCK,
14
15
  HEAD_BLOCK,
15
- DIRECT_CHILD_BLOCK,
16
- UNINITIALIZED,
16
+ IF_BLOCK,
17
+ CREATES_DERIVEDS,
17
18
  } from './constants.js';
19
+ import { hydrating } from './hydration.js';
18
20
  import { next_sibling } from './operations.js';
19
21
  import { apply_element_spread } from './render.js';
20
22
  import { is_array } from '@tsrx/core/runtime/language-helpers';
@@ -22,8 +24,9 @@ import {
22
24
  active_block,
23
25
  active_component,
24
26
  active_reaction,
25
- create_component_ctx,
26
27
  is_block_dirty,
28
+ release_deriveds,
29
+ take_created_deriveds,
27
30
  remove_dependencies,
28
31
  run_block,
29
32
  run_teardown,
@@ -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,87 +135,90 @@ 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
195
205
  */
196
- export function boundary_fn_running_block(fn, flags = 0, state = null) {
197
- return branch(fn, DIRECT_CHILD_BLOCK | flags, state);
206
+ function clear_set_ref(s) {
207
+ s.f(null);
208
+ }
209
+
210
+ /**
211
+ * @param {() => (void | (() => void))} fn
212
+ * @returns {Block}
213
+ */
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
 
200
224
  /** Creation counter: a parent always has a lower id than its descendants. */
@@ -223,6 +247,27 @@ function push_block(block, parent_block) {
223
247
  * @returns {Block}
224
248
  */
225
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) {
226
271
  /** @type {Block} */
227
272
  var block = {
228
273
  co: co || active_component,
@@ -244,15 +289,17 @@ export function block(flags, fn, state = null, co) {
244
289
  (/** @type {Derived} */ (active_reaction).blocks ??= []).push(block);
245
290
  }
246
291
 
247
- if (active_block !== null) {
248
- push_block(block, active_block);
249
- }
250
-
251
- if ((flags & EFFECT_BLOCK) !== 0) {
252
- schedule_update(block);
253
- } else {
254
- run_block(block, true);
255
- 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
+ }
256
303
  }
257
304
 
258
305
  return block;
@@ -306,6 +353,23 @@ export function unlink_block(block) {
306
353
  }
307
354
  }
308
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
+
309
373
  /**
310
374
  * @param {Block} block
311
375
  */
@@ -395,8 +459,8 @@ export function remove_block_dom(node, end) {
395
459
  export function move_block(block, target) {
396
460
  var f = block.f;
397
461
 
398
- // Only BRANCH_BLOCKs (excluding TRY_BLOCK) can have DOM state to move
399
- 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) {
400
464
  var s = block.s;
401
465
  if (s !== null && s.start !== null) {
402
466
  var node = s.start;
@@ -407,6 +471,10 @@ export function move_block(block, target) {
407
471
  target.append(node);
408
472
  node = next;
409
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
+ }
410
478
  return true;
411
479
  }
412
480
  }
@@ -424,8 +492,8 @@ export function move_block(block, target) {
424
492
  }
425
493
 
426
494
  /**
427
- * Resolve the first DOM node owned by a block. A block normally records its
428
- * 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
429
497
  * root scope renders its content through a descendant block instead of a
430
498
  * synthesized `<!>` wrapper, so its own `s.start` is null. In that case we
431
499
  * descend into child blocks to find the real first node. Returns null when the
@@ -435,7 +503,7 @@ export function move_block(block, target) {
435
503
  */
436
504
  export function get_first_node(block) {
437
505
  var f = block.f;
438
- if ((f & BRANCH_BLOCK) !== 0 && (f & TRY_BLOCK) === 0) {
506
+ if ((f & (BRANCH_BLOCK | IF_BLOCK)) !== 0 && (f & TRY_BLOCK) === 0) {
439
507
  var s = block.s;
440
508
  if (s !== null && s.start !== null) {
441
509
  return s.start;
@@ -459,10 +527,11 @@ export function get_first_node(block) {
459
527
  */
460
528
  export function get_last_node(block) {
461
529
  var f = block.f;
462
- if ((f & BRANCH_BLOCK) !== 0 && (f & TRY_BLOCK) === 0) {
530
+ if ((f & (BRANCH_BLOCK | IF_BLOCK)) !== 0 && (f & TRY_BLOCK) === 0) {
463
531
  var s = block.s;
464
532
  if (s !== null && s.start !== null) {
465
- 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;
466
535
  }
467
536
  }
468
537
  var child = block.last;
@@ -484,8 +553,15 @@ export function destroy_block(block, remove_dom = true) {
484
553
  var f = (block.f |= DESTROYED);
485
554
  var removed = false;
486
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
+ }
562
+
487
563
  if (
488
- (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) ||
489
565
  (f & HEAD_BLOCK) !== 0
490
566
  ) {
491
567
  var s = block.s;
@@ -502,6 +578,12 @@ export function destroy_block(block, remove_dom = true) {
502
578
  if (block.d !== null) {
503
579
  remove_dependencies(block);
504
580
  }
581
+ if ((f & CREATES_DERIVEDS) !== 0) {
582
+ var deriveds = take_created_deriveds(block);
583
+ if (deriveds !== null) {
584
+ release_deriveds(deriveds);
585
+ }
586
+ }
505
587
 
506
588
  var parent = block.p;
507
589
 
@@ -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,6 +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
+ // Content rendered outside the parent's DOM range (`<head>`, a portal target).
15
+ export var DETACHED_BLOCK = 1 << 14;
14
16
  export var CONTAINS_TEARDOWN = 1 << 15;
15
17
  export var BLOCK_HAS_RUN = 1 << 16;
16
18
  export var TRACKED = 1 << 17;
@@ -23,6 +25,10 @@ export var SCHEDULED = 1 << 22;
23
25
  // A per-key match value created by a selector; its map entry is dropped once
24
26
  // the last subscriber unlinks.
25
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');
@@ -1,4 +1,4 @@
1
- /** @import { Component } from '#client' */
1
+ /** @import { Component, ContextEntry } from '#client' */
2
2
 
3
3
  import { active_component } from './runtime.js';
4
4
 
@@ -16,25 +16,25 @@ export class Context {
16
16
 
17
17
  get() {
18
18
  const component = active_component;
19
- const context = this;
20
19
 
21
20
  if (component === null) {
22
21
  throw new Error('No active component found, cannot get context');
23
22
  }
24
- /** @type {Component | null} */
25
- let current_component = component;
26
23
 
27
- while (current_component !== null) {
28
- const context_map = current_component.c;
24
+ // A component inherits its parent's chain of set entries when it is
25
+ // created, so only the values that were set are visited, not every
26
+ // ancestor; the most recent set of a context is nearest the head.
27
+ /** @type {ContextEntry | null} */
28
+ let entry = component.c;
29
29
 
30
- if (context_map?.has(context)) {
31
- return context_map.get(context);
30
+ while (entry !== null) {
31
+ if (entry.k === this) {
32
+ return entry.v;
32
33
  }
33
-
34
- current_component = current_component.p;
34
+ entry = entry.n;
35
35
  }
36
36
 
37
- return context._v;
37
+ return this._v;
38
38
  }
39
39
 
40
40
  /**
@@ -43,19 +43,12 @@ export class Context {
43
43
  */
44
44
  set(value) {
45
45
  const component = active_component;
46
- const context = this;
47
46
 
48
47
  if (component === null) {
49
48
  throw new Error('No active component found, cannot set context');
50
49
  }
51
50
 
52
- let current_context = component.c;
53
-
54
- if (current_context === null) {
55
- current_context = component.c = new Map();
56
- }
57
-
58
- current_context.set(context, value);
51
+ component.c = { k: this, v: value, n: component.c };
59
52
  }
60
53
  }
61
54
 
@@ -1,14 +1,12 @@
1
1
  import { DEV } from 'esm-env';
2
2
 
3
- export function remove_ssr_css() {
4
- if (!document || typeof requestAnimationFrame !== 'function') {
5
- return;
6
- }
7
-
8
- remove_styles();
9
- }
10
-
11
- function remove_styles() {
3
+ /**
4
+ * Removes the server's inline styles once the client stylesheets are in.
5
+ * Schedule it with `requestAnimationFrame`: the removal already waits for a
6
+ * frame after the stylesheets load, and starting from a frame keeps mounting
7
+ * and hydrating free of stylesheet queries.
8
+ */
9
+ export function remove_styles() {
12
10
  if (DEV) {
13
11
  const styles = document.querySelector('style[data-vite-dev-id]');
14
12
  if (styles) {