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,47 +1,54 @@
1
- /** @import { Block, Tracked } from '#client' */
1
+ /** @import { AppendIntoAnchor, Block, Tracked } from '#client' */
2
2
 
3
3
  import { IS_CONTROLLED, IS_INDEXED, ROOT_CONTROLLED } from '../../../constants.js';
4
4
  import {
5
+ block as create_block,
5
6
  branch,
6
7
  destroy_block,
7
8
  destroy_block_children,
8
9
  get_first_node,
9
10
  get_last_node,
11
+ own_anchor,
10
12
  render,
11
13
  } from './blocks.js';
12
- import { FOR_BLOCK, TRACKED_ARRAY } from './constants.js';
13
- import { hydrate_next, hydrate_node, hydrating, set_hydrate_node } from './hydration.js';
14
- import { create_text, get_first_child, get_last_child, next_sibling } from './operations.js';
14
+ import { BRANCH_BLOCK, FOR_BLOCK, TRACKED_ARRAY } from './constants.js';
15
+ import { hydrate_first_child, hydrate_next, hydrate_node, hydrating } from './hydration.js';
16
+ import { get_last_child, next_sibling, resolve_anchor } from './operations.js';
15
17
  import { append } from './template.js';
16
- import { active_block, set, tracked, untrack } from './runtime.js';
18
+ import { active_block, set, set_tracking, tracked } from './runtime.js';
17
19
  import { array_from, is_array } from '@tsrx/core/runtime/language-helpers';
18
20
 
19
21
  /**
20
22
  * @template V
21
- * @param {Node} anchor
23
+ * @param {Node | AppendIntoAnchor} anchor
22
24
  * @param {V} value
23
25
  * @param {number} index
24
26
  * @param {(anchor: Node, value: V | Tracked, index?: any) => Block} render_fn
25
27
  * @param {boolean} is_indexed
26
28
  * @param {boolean} is_keyed
29
+ * @param {((item: V) => any) | undefined} [map_item] a keyed loop's destructuring of the item (see `for_block_keyed`)
27
30
  * @returns {Block}
28
31
  */
29
- function create_item(anchor, value, index, render_fn, is_indexed, is_keyed) {
32
+ function create_item(anchor, value, index, render_fn, is_indexed, is_keyed, map_item) {
30
33
  var block = /** @type {Block} */ (active_block);
31
34
  var tracked_index = is_indexed ? tracked(index, block) : undefined;
32
- var tracked_value = is_keyed ? tracked(value, block) : value;
35
+ var tracked_value = is_keyed
36
+ ? tracked(map_item === undefined ? value : map_item(value), block)
37
+ : value;
33
38
  var state = {
34
39
  start: null,
35
40
  end: null,
36
41
  i: tracked_index,
37
42
  v: tracked_value,
43
+ // The item as the collection holds it; a keyed diff compares against it.
44
+ r: value,
38
45
  };
39
46
 
40
47
  // Passed through module state rather than a per-item closure; run_item
41
48
  // reads them before rendering, so nested loops cannot observe a stale pair.
42
49
  item_anchor = anchor;
43
50
  item_render_fn = render_fn;
44
- var b = branch(run_item, 0, state);
51
+ var b = create_block(BRANCH_BLOCK, run_item, state);
45
52
 
46
53
  // The item's tracked value and index are owned by the item block itself.
47
54
  if (is_keyed) {
@@ -53,7 +60,22 @@ function create_item(anchor, value, index, render_fn, is_indexed, is_keyed) {
53
60
  return b;
54
61
  }
55
62
 
56
- /** @type {Node | null} */
63
+ /**
64
+ * The insertion point of a list: a node the items go before, or, for a
65
+ * controlled list (the sole content of its parent), an append-into sentinel so
66
+ * items append to the parent with no anchor node in the DOM.
67
+ * @typedef {Element | Text | AppendIntoAnchor} ListAnchor
68
+ */
69
+
70
+ /**
71
+ * @param {Element} parent
72
+ * @returns {AppendIntoAnchor}
73
+ */
74
+ function controlled_anchor(parent) {
75
+ return { parent, into: true };
76
+ }
77
+
78
+ /** @type {Node | AppendIntoAnchor | null} */
57
79
  var item_anchor = null;
58
80
  /** @type {((anchor: Node, value: any, index?: any) => Block) | null} */
59
81
  var item_render_fn = null;
@@ -70,19 +92,19 @@ function run_item(state) {
70
92
  }
71
93
 
72
94
  /**
73
- * @param {Node} anchor
95
+ * @param {ListAnchor} anchor
74
96
  * @param {(anchor: Node) => void} render_empty
75
97
  * @returns {Block}
76
98
  */
77
99
  function create_empty(anchor, render_empty) {
78
100
  return branch(() => {
79
- render_empty(anchor);
101
+ render_empty(/** @type {Node} */ (anchor));
80
102
  });
81
103
  }
82
104
 
83
105
  /**
84
106
  * @param {Block} block
85
- * @param {ChildNode} anchor
107
+ * @param {ChildNode | AppendIntoAnchor} anchor
86
108
  * @returns {void}
87
109
  */
88
110
  function move(block, anchor) {
@@ -103,21 +125,46 @@ function move(block, anchor) {
103
125
  end = s.end;
104
126
  }
105
127
 
128
+ // A controlled list appends into its parent (no anchor node); anything
129
+ // else inserts before its anchor.
130
+ /** @type {Node | null} */
131
+ var parent = null;
132
+ /** @type {ChildNode | null} */
133
+ var before = null;
134
+ if (/** @type {AppendIntoAnchor} */ (anchor).into === true) {
135
+ parent = /** @type {AppendIntoAnchor} */ (anchor).parent;
136
+ } else {
137
+ before = /** @type {ChildNode} */ (anchor);
138
+ }
139
+
106
140
  if (node === end) {
107
- anchor.before(node);
141
+ insert_node(parent, before, node);
108
142
  return;
109
143
  }
110
144
  while (node !== null) {
111
145
  var next_node = /** @type {Node} */ (next_sibling(node));
112
- anchor.before(node);
146
+ insert_node(parent, before, node);
113
147
  node = next_node;
114
148
  if (node === end) {
115
- anchor.before(/** @type {Node} */ (end));
149
+ insert_node(parent, before, /** @type {Node} */ (end));
116
150
  break;
117
151
  }
118
152
  }
119
153
  }
120
154
 
155
+ /**
156
+ * @param {Node | null} parent
157
+ * @param {ChildNode | null} before
158
+ * @param {Node} node
159
+ */
160
+ function insert_node(parent, before, node) {
161
+ if (parent !== null) {
162
+ parent.appendChild(node);
163
+ } else {
164
+ /** @type {ChildNode} */ (before).before(node);
165
+ }
166
+ }
167
+
121
168
  /**
122
169
  * Resolve the insertion anchor for a block at `index`: the first real DOM node
123
170
  * at or after `index`, or `fallback` when every remaining block renders nothing.
@@ -127,8 +174,8 @@ function move(block, anchor) {
127
174
  * @param {Block[]} blocks
128
175
  * @param {number} index
129
176
  * @param {number} length
130
- * @param {Element | Text} fallback
131
- * @returns {ChildNode}
177
+ * @param {ListAnchor} fallback
178
+ * @returns {ChildNode | AppendIntoAnchor}
132
179
  */
133
180
  function block_start(blocks, index, length, fallback) {
134
181
  if (index >= length) {
@@ -166,9 +213,91 @@ function collection_to_array(collection) {
166
213
  return array;
167
214
  }
168
215
 
216
+ /**
217
+ * The state of a list block. The reconciled list (`array`, `blocks`, `keys`,
218
+ * `empty`) and the inputs the list is re-run with both live here, so a list
219
+ * allocates no closures.
220
+ */
221
+ class ListState {
222
+ /** @type {any[]} */
223
+ array = [];
224
+ /** @type {Block[]} */
225
+ blocks = [];
226
+ /** @type {any[] | null} null until the first reconcile */
227
+ keys = null;
228
+ /** @type {Block | null} */
229
+ empty = null;
230
+
231
+ /**
232
+ * @param {ListAnchor} anchor
233
+ * @param {() => any} get_collection
234
+ * @param {(anchor: Node, value: any, index?: any) => Block} render_fn
235
+ * @param {boolean} is_controlled
236
+ * @param {boolean} is_indexed
237
+ * @param {((item: any) => any) | undefined} get_key
238
+ * @param {((anchor: Node) => void) | undefined} render_empty
239
+ * @param {((item: any) => any) | undefined} [map_item]
240
+ */
241
+ constructor(
242
+ anchor,
243
+ get_collection,
244
+ render_fn,
245
+ is_controlled,
246
+ is_indexed,
247
+ get_key,
248
+ render_empty,
249
+ map_item,
250
+ ) {
251
+ this.a = anchor;
252
+ this.g = get_collection;
253
+ this.r = render_fn;
254
+ this.c = is_controlled;
255
+ this.x = is_indexed;
256
+ this.k = get_key;
257
+ this.e = render_empty;
258
+ this.m = map_item;
259
+ }
260
+ }
261
+
262
+ /**
263
+ * @param {ListState} state
264
+ */
265
+ function run_for(state) {
266
+ var block = /** @type {Block} */ (active_block);
267
+ var array = collection_to_array(state.g());
268
+
269
+ // Items render untracked, as in a branch; the list tracks only its collection.
270
+ set_tracking(false);
271
+ reconcile_by_ref(state.a, block, array, state.r, state.c, state.x, state.e);
272
+ set_tracking(true);
273
+
274
+ // Re-anchor a hydrated list: the hydrated anchor is the block's start
275
+ // marker; later inserts and end moves must go before the cursor, which now
276
+ // sits after the hydrated items.
277
+ if (hydrating) {
278
+ state.a = /** @type {Element | Text} */ (hydrate_node);
279
+ }
280
+ }
281
+
282
+ /**
283
+ * @param {ListState} state
284
+ */
285
+ function run_for_keyed(state) {
286
+ var block = /** @type {Block} */ (active_block);
287
+ var array = collection_to_array(state.g());
288
+
289
+ set_tracking(false);
290
+ reconcile_by_key(state.a, block, array, state.r, state.c, state.x, state.k, state.e, state.m);
291
+ set_tracking(true);
292
+
293
+ if (hydrating) {
294
+ state.a = /** @type {Element | Text} */ (hydrate_node);
295
+ }
296
+ }
297
+
169
298
  /**
170
299
  * @template V
171
- * @param {Element} node
300
+ * @param {Element | AppendIntoAnchor} node
172
301
  * @param {() => V[] | Iterable<V>} get_collection
173
302
  * @param {(anchor: Node, value: V | Tracked, index?: any) => Block} render_fn
174
303
  * @param {number} flags
@@ -179,16 +308,18 @@ export function for_block(node, get_collection, render_fn, flags, render_empty)
179
308
  var is_controlled = (flags & IS_CONTROLLED) !== 0;
180
309
  var is_indexed = (flags & IS_INDEXED) !== 0;
181
310
  var root_controlled = (flags & ROOT_CONTROLLED) !== 0;
182
- var anchor = /** @type {Element | Text} */ (node);
311
+ // A root-controlled list receives the component's `__anchor`, which may be
312
+ // an append-into sentinel; moves and end insertions need a real node.
313
+ var anchor = /** @type {ListAnchor} */ (root_controlled ? resolve_anchor(node) : node);
183
314
  /** @type {Node | undefined} */
184
315
  var boundary;
185
316
 
186
317
  if (is_controlled) {
187
318
  if (hydrating) {
188
- var parent_node = /** @type {Element} */ (node);
189
- /** @type {Element | Text} */ (set_hydrate_node(get_first_child(parent_node)));
319
+ // The cursor sits on the list's element; the items are its children.
320
+ hydrate_first_child();
190
321
  } else {
191
- anchor = node.appendChild(create_text());
322
+ anchor = controlled_anchor(/** @type {Element} */ (node));
192
323
  }
193
324
  }
194
325
 
@@ -200,24 +331,24 @@ export function for_block(node, get_collection, render_fn, flags, render_empty)
200
331
  }
201
332
 
202
333
  render(
203
- () => {
204
- var block = /** @type {Block} */ (active_block);
205
- var collection = get_collection();
206
- var array = collection_to_array(collection);
207
-
208
- untrack(() => {
209
- reconcile_by_ref(anchor, block, array, render_fn, is_controlled, is_indexed, render_empty);
210
- });
211
-
212
- if (hydrating) {
213
- anchor = /** @type {Element | Text} */ (hydrate_node);
214
- }
215
- },
216
- null,
334
+ run_for,
335
+ new ListState(
336
+ anchor,
337
+ get_collection,
338
+ render_fn,
339
+ is_controlled,
340
+ is_indexed,
341
+ undefined,
342
+ render_empty,
343
+ ),
217
344
  FOR_BLOCK,
218
345
  );
219
346
 
347
+ if (!is_controlled) own_anchor(node, /** @type {Node} */ (anchor));
348
+
220
349
  if (hydrating && root_controlled) {
350
+ // The original `node`: for a sentinel, `hydrate_append` performs the
351
+ // cursor advance that stands in for the eliminated sibling navigation.
221
352
  append(/** @type {ChildNode} */ (node), /** @type {Node} */ (boundary));
222
353
  }
223
354
  }
@@ -225,19 +356,30 @@ export function for_block(node, get_collection, render_fn, flags, render_empty)
225
356
  /**
226
357
  * @template V
227
358
  * @template K
228
- * @param {Element} node
359
+ * @param {Element | AppendIntoAnchor} node
229
360
  * @param {() => V[] | Iterable<V>} get_collection
230
361
  * @param {(anchor: Node, value: V | Tracked, index?: any) => Block} render_fn
231
362
  * @param {number} flags
232
- * @param {(item: V) => K} [get_key]
363
+ * @param {(item: V) => K} [get_key] omitted for identity keys (`key item`)
233
364
  * @param {(anchor: Node) => void} [render_empty]
365
+ * @param {(item: V) => any} [map_item] for a destructuring pattern with a rest
366
+ * element or a default: destructures the item once, natively, into an object
367
+ * of the pattern's names, which is what the item's tracked then holds
234
368
  * @returns {void}
235
369
  */
236
- export function for_block_keyed(node, get_collection, render_fn, flags, get_key, render_empty) {
370
+ export function for_block_keyed(
371
+ node,
372
+ get_collection,
373
+ render_fn,
374
+ flags,
375
+ get_key,
376
+ render_empty,
377
+ map_item,
378
+ ) {
237
379
  var is_controlled = (flags & IS_CONTROLLED) !== 0;
238
380
  var is_indexed = (flags & IS_INDEXED) !== 0;
239
381
  var root_controlled = (flags & ROOT_CONTROLLED) !== 0;
240
- var anchor = /** @type {Element | Text} */ (node);
382
+ var anchor = /** @type {ListAnchor} */ (root_controlled ? resolve_anchor(node) : node);
241
383
  /** @type {Node | undefined} */
242
384
  var boundary;
243
385
 
@@ -245,10 +387,10 @@ export function for_block_keyed(node, get_collection, render_fn, flags, get_key,
245
387
  var parent_node = /** @type {Element} */ (node);
246
388
 
247
389
  if (hydrating) {
248
- /** @type {Element | Text} */ (set_hydrate_node(get_first_child(parent_node)));
390
+ hydrate_first_child();
249
391
  anchor = /** @type {Element | Text} */ (get_last_child(parent_node));
250
392
  } else {
251
- anchor = node.appendChild(create_text());
393
+ anchor = controlled_anchor(parent_node);
252
394
  }
253
395
  }
254
396
 
@@ -260,46 +402,45 @@ export function for_block_keyed(node, get_collection, render_fn, flags, get_key,
260
402
  }
261
403
 
262
404
  render(
263
- () => {
264
- var block = /** @type {Block} */ (active_block);
265
- var collection = get_collection();
266
- var array = collection_to_array(collection);
267
-
268
- untrack(() => {
269
- reconcile_by_key(
270
- anchor,
271
- block,
272
- array,
273
- render_fn,
274
- is_controlled,
275
- is_indexed,
276
- /** @type {(item: V) => K} */ (get_key),
277
- render_empty,
278
- );
279
- });
280
- },
281
- null,
405
+ run_for_keyed,
406
+ new ListState(
407
+ anchor,
408
+ get_collection,
409
+ render_fn,
410
+ is_controlled,
411
+ is_indexed,
412
+ get_key,
413
+ render_empty,
414
+ map_item,
415
+ ),
282
416
  FOR_BLOCK,
283
417
  );
284
418
 
419
+ if (!is_controlled) own_anchor(node, /** @type {Node} */ (anchor));
420
+
285
421
  if (hydrating && root_controlled) {
422
+ // The original `node`: for a sentinel, `hydrate_append` performs the
423
+ // cursor advance that stands in for the eliminated sibling navigation.
286
424
  append(/** @type {ChildNode} */ (node), /** @type {Node} */ (boundary));
287
425
  }
288
426
  }
289
427
 
290
428
  /**
291
429
  * @template V
292
- * @param {Element | Text} anchor
430
+ * @param {ListAnchor} anchor
293
431
  * @param {Block} block
294
432
  * @param {V[]} array
295
433
  * @returns {void}
296
434
  */
297
435
  function reconcile_fast_clear(anchor, block, array) {
298
436
  var state = block.s;
299
- var parent_node = /** @type {Element} */ (anchor.parentNode);
437
+ var into = /** @type {AppendIntoAnchor} */ (anchor).into === true;
438
+ var parent_node = /** @type {Element} */ (
439
+ into ? /** @type {AppendIntoAnchor} */ (anchor).parent : /** @type {Node} */ (anchor).parentNode
440
+ );
300
441
  parent_node.textContent = '';
301
442
  destroy_block_children(block);
302
- parent_node.append(anchor);
443
+ if (!into) parent_node.append(/** @type {Node} */ (anchor));
303
444
  state.array = array;
304
445
  state.blocks = [];
305
446
  state.empty = null;
@@ -317,27 +458,33 @@ function update_index(block, index) {
317
458
  /**
318
459
  * @param {Block} block
319
460
  * @param {any} value
461
+ * @param {((item: any) => any) | undefined} map_item
320
462
  * @returns {void}
321
463
  */
322
- function update_value(block, value) {
323
- var tracked_value = block.s.v;
324
- if (tracked_value.__v !== value) {
325
- set(tracked_value, value);
464
+ function update_value(block, value, map_item) {
465
+ var state = block.s;
466
+ if (state.r !== value) {
467
+ state.r = value;
468
+ set(state.v, map_item === undefined ? value : map_item(value));
326
469
  }
327
470
  }
328
471
 
329
472
  /**
330
473
  * @template V
331
474
  * @template K
332
- * @param {Element | Text} anchor
475
+ * @param {ListAnchor} anchor
333
476
  * @param {Block} block
334
477
  * @param {V[]} b
335
478
  * @param {(anchor: Node, value: V | Tracked, index?: any) => Block} render_fn
336
479
  * @param {boolean} is_controlled
337
480
  * @param {boolean} is_indexed
338
- * @param {(item: V) => K} get_key
339
- * @param {(anchor: Node) => void} [render_empty]
481
+ * @param {((item: V) => K) | undefined} get_key identity keys when omitted
482
+ * @param {((anchor: Node) => void) | undefined} render_empty
483
+ * @param {((item: V) => any) | undefined} map_item
340
484
  * @returns {void}
485
+ *
486
+ * The first run only creates items, so it lives in this small function and
487
+ * the diff below is compiled only once a list actually changes.
341
488
  */
342
489
  function reconcile_by_key(
343
490
  anchor,
@@ -348,8 +495,72 @@ function reconcile_by_key(
348
495
  is_indexed,
349
496
  get_key,
350
497
  render_empty,
498
+ map_item,
351
499
  ) {
352
- var state = block.s;
500
+ var b_length = b.length;
501
+ var state = /** @type {ListState} */ (block.s);
502
+
503
+ if (state.keys === null && b_length > 0) {
504
+ var b_blocks = Array(b_length);
505
+ // Identity keys (`key item`) are the items themselves; the list keeps a
506
+ // copy, as the array it rendered may later be mutated in place.
507
+ var b_keys = get_key === undefined ? b.slice() : Array(b_length);
508
+
509
+ // One loop, no `map` callback machinery: most lists are short.
510
+ for (var j = 0; j < b_length; j++) {
511
+ var value = b[j];
512
+ if (get_key !== undefined) {
513
+ b_keys[j] = get_key(value);
514
+ }
515
+ b_blocks[j] = create_item(anchor, value, j, render_fn, is_indexed, true, map_item);
516
+ }
517
+
518
+ state.array = b;
519
+ state.blocks = b_blocks;
520
+ state.keys = b_keys;
521
+ return;
522
+ }
523
+
524
+ reconcile_by_key_diff(
525
+ anchor,
526
+ block,
527
+ b,
528
+ render_fn,
529
+ is_controlled,
530
+ is_indexed,
531
+ get_key,
532
+ render_empty,
533
+ map_item,
534
+ );
535
+ }
536
+
537
+ /**
538
+ * Keyed diff for every run after the first (or a first run with an empty
539
+ * list). See {@link reconcile_by_key}.
540
+ * @template V, K
541
+ * @param {ListAnchor} anchor
542
+ * @param {Block} block
543
+ * @param {V[]} b
544
+ * @param {(anchor: Node, value: V | Tracked, index?: any) => Block} render_fn
545
+ * @param {boolean} is_controlled
546
+ * @param {boolean} is_indexed
547
+ * @param {((item: V) => K) | undefined} get_key
548
+ * @param {((anchor: Node) => void) | undefined} render_empty
549
+ * @param {((item: V) => any) | undefined} map_item
550
+ * @returns {void}
551
+ */
552
+ function reconcile_by_key_diff(
553
+ anchor,
554
+ block,
555
+ b,
556
+ render_fn,
557
+ is_controlled,
558
+ is_indexed,
559
+ get_key,
560
+ render_empty,
561
+ map_item,
562
+ ) {
563
+ var state = /** @type {ListState} */ (block.s);
353
564
 
354
565
  // Variables used in conditional branches - declare with initial values
355
566
  /** @type {number} */
@@ -367,17 +578,8 @@ function reconcile_by_key(
367
578
  /** @type {number} */
368
579
  var i = 0;
369
580
 
370
- if (state === null) {
371
- state = block.s = {
372
- array: [],
373
- blocks: [],
374
- keys: null,
375
- empty: null,
376
- };
377
- }
378
-
379
- var a = state.array;
380
- var a_length = a.length;
581
+ var a_blocks = state.blocks;
582
+ var a_length = a_blocks.length;
381
583
  var b_length = b.length;
382
584
  var j = 0;
383
585
 
@@ -412,13 +614,20 @@ function reconcile_by_key(
412
614
  }
413
615
  return;
414
616
  }
415
- var b_blocks = Array(b_length);
416
- var b_keys = b.map(get_key);
617
+ // Identity keys are the items themselves (copied, as the rendered array
618
+ // may later be mutated in place), so a matched key is also the item's
619
+ // current tracked value: only computed keys need `update_value`.
620
+ var b_keys = get_key === undefined ? b.slice() : b.map(get_key);
621
+ // A same-length run rewrites the block array in place: matched ends are
622
+ // already at their index, and the middle reads the old blocks from a copy
623
+ // taken before its first write.
624
+ var in_place = a_length === b_length;
625
+ var b_blocks = in_place ? a_blocks : Array(b_length);
417
626
 
418
627
  // Fast-path for create
419
628
  if (a_length === 0) {
420
629
  for (; j < b_length; j++) {
421
- b_blocks[j] = create_item(anchor, b[j], j, render_fn, is_indexed, true);
630
+ b_blocks[j] = create_item(anchor, b[j], j, render_fn, is_indexed, true, map_item);
422
631
  }
423
632
  state.array = b;
424
633
  state.blocks = b_blocks;
@@ -426,8 +635,8 @@ function reconcile_by_key(
426
635
  return;
427
636
  }
428
637
 
429
- var a_blocks = state.blocks;
430
- var a_keys = state.keys;
638
+ // Set by every run that leaves items behind.
639
+ var a_keys = /** @type {any[]} */ (state.keys);
431
640
  var a_start = 0;
432
641
  var b_start = 0;
433
642
  var a_end = a_length - 1;
@@ -435,6 +644,25 @@ function reconcile_by_key(
435
644
  var b_val;
436
645
  var b_block;
437
646
 
647
+ if (get_key === undefined && !is_indexed) {
648
+ // Identity keys without an index: a matched item needs no update, so
649
+ // the unchanged prefix and suffix are skipped with plain compares.
650
+ while (a_start <= a_end && b_start <= b_end && a_keys[a_start] === b_keys[b_start]) {
651
+ if (!in_place) {
652
+ b_blocks[b_start] = a_blocks[a_start];
653
+ }
654
+ a_start++;
655
+ b_start++;
656
+ }
657
+ while (a_start <= a_end && b_start <= b_end && a_keys[a_end] === b_keys[b_end]) {
658
+ if (!in_place) {
659
+ b_blocks[b_end] = a_blocks[a_end];
660
+ }
661
+ a_end--;
662
+ b_end--;
663
+ }
664
+ }
665
+
438
666
  // Match from both ends first, including the two end-crossing cases: an old
439
667
  // item that moved to the far end of the new list, and a run whose ends were
440
668
  // exchanged (a reversal, or a swap of two items). Those complete with plain
@@ -446,7 +674,9 @@ function reconcile_by_key(
446
674
  if (is_indexed) {
447
675
  update_index(b_block, b_start);
448
676
  }
449
- update_value(b_block, b_val);
677
+ if (get_key !== undefined) {
678
+ update_value(b_block, b_val, map_item);
679
+ }
450
680
  a_start++;
451
681
  b_start++;
452
682
  continue;
@@ -457,7 +687,9 @@ function reconcile_by_key(
457
687
  if (is_indexed) {
458
688
  update_index(b_block, b_end);
459
689
  }
460
- update_value(b_block, b_val);
690
+ if (get_key !== undefined) {
691
+ update_value(b_block, b_val, map_item);
692
+ }
461
693
  a_end--;
462
694
  b_end--;
463
695
  continue;
@@ -465,6 +697,11 @@ function reconcile_by_key(
465
697
  if (a_start === a_end || a_blocks[a_start].s.start === null) {
466
698
  break;
467
699
  }
700
+ if (in_place) {
701
+ // An end-crossing move overwrites an old block before it is read.
702
+ a_blocks = a_blocks.slice();
703
+ in_place = false;
704
+ }
468
705
  if (a_keys[a_end] === b_keys[b_start]) {
469
706
  // Last old item is the next new one: move it in front of the old run.
470
707
  b_val = b[b_start];
@@ -472,7 +709,9 @@ function reconcile_by_key(
472
709
  if (is_indexed) {
473
710
  update_index(b_block, b_start);
474
711
  }
475
- update_value(b_block, b_val);
712
+ if (get_key !== undefined) {
713
+ update_value(b_block, b_val, map_item);
714
+ }
476
715
  move(b_block, /** @type {ChildNode} */ (a_blocks[a_start].s.start));
477
716
  a_end--;
478
717
  b_start++;
@@ -485,7 +724,9 @@ function reconcile_by_key(
485
724
  if (is_indexed) {
486
725
  update_index(b_block, b_end);
487
726
  }
488
- update_value(b_block, b_val);
727
+ if (get_key !== undefined) {
728
+ update_value(b_block, b_val, map_item);
729
+ }
489
730
  move(b_block, block_start(b_blocks, b_end + 1, b_length, anchor));
490
731
  a_start++;
491
732
  b_end--;
@@ -507,6 +748,7 @@ function reconcile_by_key(
507
748
  render_fn,
508
749
  is_indexed,
509
750
  true,
751
+ map_item,
510
752
  );
511
753
  b_start++;
512
754
  }
@@ -516,6 +758,10 @@ function reconcile_by_key(
516
758
  destroy_block(a_blocks[a_start++]);
517
759
  }
518
760
  } else {
761
+ if (in_place) {
762
+ a_blocks = a_blocks.slice();
763
+ in_place = false;
764
+ }
519
765
  a_left = a_end - a_start + 1;
520
766
  b_left = b_end - b_start + 1;
521
767
  sources = new Int32Array(b_left + 1);
@@ -549,7 +795,9 @@ function reconcile_by_key(
549
795
  if (is_indexed) {
550
796
  update_index(b_block, j);
551
797
  }
552
- update_value(b_block, b_val);
798
+ if (get_key !== undefined) {
799
+ update_value(b_block, b_val, map_item);
800
+ }
553
801
  ++patched;
554
802
  break;
555
803
  }
@@ -590,7 +838,9 @@ function reconcile_by_key(
590
838
  if (is_indexed) {
591
839
  update_index(b_block, j);
592
840
  }
593
- update_value(b_block, b_val);
841
+ if (get_key !== undefined) {
842
+ update_value(b_block, b_val, map_item);
843
+ }
594
844
  ++patched;
595
845
  } else if (!fast_path_removal) {
596
846
  destroy_block(a_blocks[i]);
@@ -604,7 +854,17 @@ function reconcile_by_key(
604
854
 
605
855
  if (fast_path_removal) {
606
856
  reconcile_fast_clear(anchor, block, []);
607
- reconcile_by_key(anchor, block, b, render_fn, is_controlled, is_indexed, get_key);
857
+ reconcile_by_key(
858
+ anchor,
859
+ block,
860
+ b,
861
+ render_fn,
862
+ is_controlled,
863
+ is_indexed,
864
+ get_key,
865
+ render_empty,
866
+ map_item,
867
+ );
608
868
  return;
609
869
  } else if (moved) {
610
870
  var next_pos = 0;
@@ -619,7 +879,15 @@ function reconcile_by_key(
619
879
  for (i = 0; i < b_left; i++) {
620
880
  pos = i + b_start;
621
881
  if (sources[i] === 0) {
622
- b_blocks[pos] = create_item(relay_target, b[pos], pos, render_fn, is_indexed, true);
882
+ b_blocks[pos] = create_item(
883
+ relay_target,
884
+ b[pos],
885
+ pos,
886
+ render_fn,
887
+ is_indexed,
888
+ true,
889
+ map_item,
890
+ );
623
891
  } else {
624
892
  move(b_blocks[pos], relay_target);
625
893
  }
@@ -637,7 +905,7 @@ function reconcile_by_key(
637
905
  next_pos = pos + 1;
638
906
 
639
907
  var target = block_start(b_blocks, next_pos, b_length, anchor);
640
- b_blocks[pos] = create_item(target, b_val, pos, render_fn, is_indexed, true);
908
+ b_blocks[pos] = create_item(target, b_val, pos, render_fn, is_indexed, true, map_item);
641
909
  } else if (j < 0 || i !== seq[j]) {
642
910
  pos = i + b_start;
643
911
  next_pos = pos + 1;
@@ -656,7 +924,7 @@ function reconcile_by_key(
656
924
  next_pos = pos + 1;
657
925
 
658
926
  var target = block_start(b_blocks, next_pos, b_length, anchor);
659
- b_blocks[pos] = create_item(target, b_val, pos, render_fn, is_indexed, true);
927
+ b_blocks[pos] = create_item(target, b_val, pos, render_fn, is_indexed, true, map_item);
660
928
  }
661
929
  }
662
930
  }
@@ -668,7 +936,7 @@ function reconcile_by_key(
668
936
 
669
937
  /**
670
938
  * @template V
671
- * @param {Element | Text} anchor
939
+ * @param {ListAnchor} anchor
672
940
  * @param {Block} block
673
941
  * @param {V[]} b
674
942
  * @param {(anchor: Node, value: V | Tracked, index?: any) => Block} render_fn
@@ -678,35 +946,8 @@ function reconcile_by_key(
678
946
  * @returns {void}
679
947
  */
680
948
  function reconcile_by_ref(anchor, block, b, render_fn, is_controlled, is_indexed, render_empty) {
681
- var state = block.s;
682
-
683
- // Variables used in conditional branches - declare with initial values
684
- /** @type {number} */
685
- var a_left = 0;
686
- /** @type {number} */
687
- var b_left = 0;
688
- /** @type {Int32Array} */
689
- var sources = new Int32Array(0);
690
- /** @type {boolean} */
691
- var moved = false;
692
- /** @type {number} */
693
- var pos = 0;
694
- /** @type {number} */
695
- var patched = 0;
696
- /** @type {number} */
697
- var i = 0;
698
-
699
- if (state === null) {
700
- state = block.s = {
701
- array: [],
702
- blocks: [],
703
- keys: null,
704
- empty: null,
705
- };
706
- }
707
-
708
- var a = state.array;
709
- var a_length = a.length;
949
+ var state = /** @type {ListState} */ (block.s);
950
+ var a_length = state.array.length;
710
951
  var b_length = b.length;
711
952
  var j = 0;
712
953
 
@@ -752,6 +993,45 @@ function reconcile_by_ref(anchor, block, b, render_fn, is_controlled, is_indexed
752
993
  return;
753
994
  }
754
995
 
996
+ reconcile_by_ref_diff(anchor, block, b, b_blocks, render_fn, is_controlled, is_indexed);
997
+ }
998
+
999
+ /**
1000
+ * Diffs a non-empty list against its previous run. Separate from the empty and
1001
+ * create paths so a first render (a mount, a hydration) never compiles it.
1002
+ * @template V
1003
+ * @param {ListAnchor} anchor
1004
+ * @param {Block} block
1005
+ * @param {V[]} b
1006
+ * @param {Block[]} b_blocks
1007
+ * @param {(anchor: Node, value: V | Tracked, index?: any) => Block} render_fn
1008
+ * @param {boolean} is_controlled
1009
+ * @param {boolean} is_indexed
1010
+ * @returns {void}
1011
+ */
1012
+ function reconcile_by_ref_diff(anchor, block, b, b_blocks, render_fn, is_controlled, is_indexed) {
1013
+ var state = /** @type {ListState} */ (block.s);
1014
+
1015
+ // Variables used in conditional branches - declare with initial values
1016
+ /** @type {number} */
1017
+ var a_left = 0;
1018
+ /** @type {number} */
1019
+ var b_left = 0;
1020
+ /** @type {Int32Array} */
1021
+ var sources = new Int32Array(0);
1022
+ /** @type {boolean} */
1023
+ var moved = false;
1024
+ /** @type {number} */
1025
+ var pos = 0;
1026
+ /** @type {number} */
1027
+ var patched = 0;
1028
+ /** @type {number} */
1029
+ var i = 0;
1030
+
1031
+ var a = state.array;
1032
+ var a_length = a.length;
1033
+ var b_length = b.length;
1034
+ var j = 0;
755
1035
  var a_blocks = state.blocks;
756
1036
  var a_start = 0;
757
1037
  var b_start = 0;