svelte 5.43.6 → 5.43.8

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.
@@ -38,7 +38,7 @@ import { source, mutable_source, internal_set } from '../../reactivity/sources.j
38
38
  import { array_from, is_array } from '../../../shared/utils.js';
39
39
  import { COMMENT_NODE, INERT } from '#client/constants';
40
40
  import { queue_micro_task } from '../task.js';
41
- import { active_effect, get } from '../../runtime.js';
41
+ import { get } from '../../runtime.js';
42
42
  import { DEV } from 'esm-env';
43
43
  import { derived_safe_equal } from '../../reactivity/deriveds.js';
44
44
  import { current_batch } from '../../reactivity/batch.js';
@@ -67,41 +67,51 @@ export function index(_, i) {
67
67
  * Pause multiple effects simultaneously, and coordinate their
68
68
  * subsequent destruction. Used in each blocks
69
69
  * @param {EachState} state
70
- * @param {EachItem[]} items
70
+ * @param {EachItem[]} to_destroy
71
71
  * @param {null | Node} controlled_anchor
72
72
  */
73
- function pause_effects(state, items, controlled_anchor) {
74
- var items_map = state.items;
75
-
73
+ function pause_effects(state, to_destroy, controlled_anchor) {
76
74
  /** @type {TransitionManager[]} */
77
75
  var transitions = [];
78
- var length = items.length;
76
+ var length = to_destroy.length;
79
77
 
80
78
  for (var i = 0; i < length; i++) {
81
- pause_children(items[i].e, transitions, true);
82
- }
83
-
84
- var is_controlled = length > 0 && transitions.length === 0 && controlled_anchor !== null;
85
- // If we have a controlled anchor, it means that the each block is inside a single
86
- // DOM element, so we can apply a fast-path for clearing the contents of the element.
87
- if (is_controlled) {
88
- var parent_node = /** @type {Element} */ (
89
- /** @type {Element} */ (controlled_anchor).parentNode
90
- );
91
- clear_text_content(parent_node);
92
- parent_node.append(/** @type {Element} */ (controlled_anchor));
93
- items_map.clear();
94
- link(state, items[0].prev, items[length - 1].next);
79
+ pause_children(to_destroy[i].e, transitions, true);
95
80
  }
96
81
 
97
82
  run_out_transitions(transitions, () => {
83
+ // If we're in a controlled each block (i.e. the block is the only child of an
84
+ // element), and we are removing all items, _and_ there are no out transitions,
85
+ // we can use the fast path — emptying the element and replacing the anchor
86
+ var fast_path = transitions.length === 0 && controlled_anchor !== null;
87
+
88
+ // TODO only destroy effects if no pending batch needs them. otherwise,
89
+ // just set `item.o` back to `false`
90
+
91
+ if (fast_path) {
92
+ var anchor = /** @type {Element} */ (controlled_anchor);
93
+ var parent_node = /** @type {Element} */ (anchor.parentNode);
94
+
95
+ clear_text_content(parent_node);
96
+ parent_node.append(anchor);
97
+
98
+ state.items.clear();
99
+ link(state, to_destroy[0].prev, to_destroy[length - 1].next);
100
+ }
101
+
98
102
  for (var i = 0; i < length; i++) {
99
- var item = items[i];
100
- if (!is_controlled) {
101
- items_map.delete(item.k);
103
+ var item = to_destroy[i];
104
+
105
+ if (!fast_path) {
106
+ state.items.delete(item.k);
102
107
  link(state, item.prev, item.next);
103
108
  }
104
- destroy_effect(item.e, !is_controlled);
109
+
110
+ destroy_effect(item.e, !fast_path);
111
+ }
112
+
113
+ if (state.first === to_destroy[0]) {
114
+ state.first = to_destroy[0].prev;
105
115
  }
106
116
  });
107
117
  }
@@ -123,6 +133,8 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
123
133
  var state = { flags, items: new Map(), first: null };
124
134
 
125
135
  var is_controlled = (flags & EACH_IS_CONTROLLED) !== 0;
136
+ var is_reactive_value = (flags & EACH_ITEM_REACTIVE) !== 0;
137
+ var is_reactive_index = (flags & EACH_INDEX_REACTIVE) !== 0;
126
138
 
127
139
  if (is_controlled) {
128
140
  var parent_node = /** @type {Element} */ (node);
@@ -136,14 +148,9 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
136
148
  hydrate_next();
137
149
  }
138
150
 
139
- /** @type {Effect | null} */
151
+ /** @type {{ fragment: DocumentFragment | null, effect: Effect } | null} */
140
152
  var fallback = null;
141
153
 
142
- var was_empty = false;
143
-
144
- /** @type {Map<any, EachItem>} */
145
- var offscreen_items = new Map();
146
-
147
154
  // TODO: ideally we could use derived for runes mode but because of the ability
148
155
  // to use a store which can be mutated, we can't do that here as mutating a store
149
156
  // will still result in the collection array being the same from the store
@@ -156,51 +163,36 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
156
163
  /** @type {V[]} */
157
164
  var array;
158
165
 
159
- /** @type {Effect} */
160
- var each_effect;
166
+ var first_run = true;
161
167
 
162
168
  function commit() {
163
- reconcile(
164
- each_effect,
165
- array,
166
- state,
167
- offscreen_items,
168
- anchor,
169
- render_fn,
170
- flags,
171
- get_key,
172
- get_collection
173
- );
174
-
175
- if (fallback_fn !== null) {
169
+ reconcile(each_effect, array, state, anchor, flags, get_key);
170
+
171
+ if (fallback !== null) {
176
172
  if (array.length === 0) {
177
- if (fallback) {
178
- resume_effect(fallback);
173
+ if (fallback.fragment) {
174
+ anchor.before(fallback.fragment);
175
+ fallback.fragment = null;
179
176
  } else {
180
- fallback = branch(() => fallback_fn(anchor));
177
+ resume_effect(fallback.effect);
181
178
  }
182
- } else if (fallback !== null) {
183
- pause_effect(fallback, () => {
179
+
180
+ each_effect.first = fallback.effect;
181
+ } else {
182
+ pause_effect(fallback.effect, () => {
183
+ // TODO only null out if no pending batch needs it,
184
+ // otherwise re-add `fallback.fragment` and move the
185
+ // effect into it
184
186
  fallback = null;
185
187
  });
186
188
  }
187
189
  }
188
190
  }
189
191
 
190
- block(() => {
191
- // store a reference to the effect so that we can update the start/end nodes in reconciliation
192
- each_effect ??= /** @type {Effect} */ (active_effect);
193
-
192
+ var each_effect = block(() => {
194
193
  array = /** @type {V[]} */ (get(each_array));
195
194
  var length = array.length;
196
195
 
197
- if (was_empty && length === 0) {
198
- // ignore updates if the array is empty,
199
- // and it already was empty on previous run
200
- return;
201
- }
202
- was_empty = length === 0;
203
-
204
196
  /** `true` if there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
205
197
  let mismatch = false;
206
198
 
@@ -217,34 +209,46 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
217
209
  }
218
210
  }
219
211
 
220
- // this is separate to the previous block because `hydrating` might change
221
- if (hydrating) {
222
- /** @type {EachItem | null} */
223
- var prev = null;
224
-
225
- /** @type {EachItem} */
226
- var item;
227
-
228
- for (var i = 0; i < length; i++) {
229
- if (
230
- hydrate_node.nodeType === COMMENT_NODE &&
231
- /** @type {Comment} */ (hydrate_node).data === HYDRATION_END
232
- ) {
233
- // The server rendered fewer items than expected,
234
- // so break out and continue appending non-hydrated items
235
- anchor = /** @type {Comment} */ (hydrate_node);
236
- mismatch = true;
237
- set_hydrating(false);
238
- break;
212
+ var keys = new Set();
213
+ var batch = /** @type {Batch} */ (current_batch);
214
+ var prev = null;
215
+ var defer = should_defer_append();
216
+
217
+ for (var i = 0; i < length; i += 1) {
218
+ if (
219
+ hydrating &&
220
+ hydrate_node.nodeType === COMMENT_NODE &&
221
+ /** @type {Comment} */ (hydrate_node).data === HYDRATION_END
222
+ ) {
223
+ // The server rendered fewer items than expected,
224
+ // so break out and continue appending non-hydrated items
225
+ anchor = /** @type {Comment} */ (hydrate_node);
226
+ mismatch = true;
227
+ set_hydrating(false);
228
+ }
229
+
230
+ var value = array[i];
231
+ var key = get_key(value, i);
232
+
233
+ var item = first_run ? null : state.items.get(key);
234
+
235
+ if (item) {
236
+ // update before reconciliation, to trigger any async updates
237
+ if (is_reactive_value) {
238
+ internal_set(item.v, value);
239
239
  }
240
240
 
241
- var value = array[i];
242
- var key = get_key(value, i);
241
+ if (is_reactive_index) {
242
+ internal_set(/** @type {Value<number>} */ (item.i), i);
243
+ } else {
244
+ item.i = i;
245
+ }
246
+
247
+ batch.skipped_effects.delete(item.e);
248
+ } else {
243
249
  item = create_item(
244
- hydrate_node,
245
- state,
250
+ first_run ? anchor : null,
246
251
  prev,
247
- null,
248
252
  value,
249
253
  key,
250
254
  i,
@@ -252,65 +256,60 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
252
256
  flags,
253
257
  get_collection
254
258
  );
255
- state.items.set(key, item);
256
259
 
257
- prev = item;
258
- }
260
+ if (first_run) {
261
+ item.o = true;
262
+
263
+ if (prev === null) {
264
+ state.first = item;
265
+ } else {
266
+ prev.next = item;
267
+ }
259
268
 
260
- // remove excess nodes
261
- if (length > 0) {
262
- set_hydrate_node(skip_nodes());
269
+ prev = item;
270
+ }
271
+
272
+ state.items.set(key, item);
263
273
  }
274
+
275
+ keys.add(key);
264
276
  }
265
277
 
266
- if (hydrating) {
267
- if (length === 0 && fallback_fn) {
268
- fallback = branch(() => fallback_fn(anchor));
278
+ if (length === 0 && fallback_fn && !fallback) {
279
+ if (first_run) {
280
+ fallback = {
281
+ fragment: null,
282
+ effect: branch(() => fallback_fn(anchor))
283
+ };
284
+ } else {
285
+ var fragment = document.createDocumentFragment();
286
+ var target = create_text();
287
+ fragment.append(target);
288
+
289
+ fallback = {
290
+ fragment,
291
+ effect: branch(() => fallback_fn(target))
292
+ };
269
293
  }
270
- } else {
271
- if (should_defer_append()) {
272
- var keys = new Set();
273
- var batch = /** @type {Batch} */ (current_batch);
274
-
275
- for (i = 0; i < length; i += 1) {
276
- value = array[i];
277
- key = get_key(value, i);
278
-
279
- var existing = state.items.get(key) ?? offscreen_items.get(key);
280
-
281
- if (existing) {
282
- // update before reconciliation, to trigger any async updates
283
- if ((flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0) {
284
- update_item(existing, value, i, flags);
285
- }
286
- } else {
287
- item = create_item(
288
- null,
289
- state,
290
- null,
291
- null,
292
- value,
293
- key,
294
- i,
295
- render_fn,
296
- flags,
297
- get_collection,
298
- true
299
- );
300
-
301
- offscreen_items.set(key, item);
302
- }
294
+ }
303
295
 
304
- keys.add(key);
305
- }
296
+ // remove excess nodes
297
+ if (hydrating && length > 0) {
298
+ set_hydrate_node(skip_nodes());
299
+ }
306
300
 
307
- for (const [key, item] of state.items) {
308
- if (!keys.has(key)) {
309
- batch.skipped_effects.add(item.e);
310
- }
311
- }
301
+ for (const [key, item] of state.items) {
302
+ if (!keys.has(key)) {
303
+ batch.skipped_effects.add(item.e);
304
+ }
305
+ }
312
306
 
307
+ if (!first_run) {
308
+ if (defer) {
313
309
  batch.oncommit(commit);
310
+ batch.ondiscard(() => {
311
+ // TODO presumably we need to do something here?
312
+ });
314
313
  } else {
315
314
  commit();
316
315
  }
@@ -330,6 +329,8 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
330
329
  get(each_array);
331
330
  });
332
331
 
332
+ first_run = false;
333
+
333
334
  if (hydrating) {
334
335
  anchor = hydrate_node;
335
336
  }
@@ -341,32 +342,17 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
341
342
  * @param {Effect} each_effect
342
343
  * @param {Array<V>} array
343
344
  * @param {EachState} state
344
- * @param {Map<any, EachItem>} offscreen_items
345
345
  * @param {Element | Comment | Text} anchor
346
- * @param {(anchor: Node, item: MaybeSource<V>, index: number | Source<number>, collection: () => V[]) => void} render_fn
347
346
  * @param {number} flags
348
347
  * @param {(value: V, index: number) => any} get_key
349
- * @param {() => V[]} get_collection
350
348
  * @returns {void}
351
349
  */
352
- function reconcile(
353
- each_effect,
354
- array,
355
- state,
356
- offscreen_items,
357
- anchor,
358
- render_fn,
359
- flags,
360
- get_key,
361
- get_collection
362
- ) {
350
+ function reconcile(each_effect, array, state, anchor, flags, get_key) {
363
351
  var is_animated = (flags & EACH_IS_ANIMATED) !== 0;
364
- var should_update = (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0;
365
352
 
366
353
  var length = array.length;
367
354
  var items = state.items;
368
- var first = state.first;
369
- var current = first;
355
+ var current = state.first;
370
356
 
371
357
  /** @type {undefined | Set<EachItem>} */
372
358
  var seen;
@@ -399,12 +385,10 @@ function reconcile(
399
385
  for (i = 0; i < length; i += 1) {
400
386
  value = array[i];
401
387
  key = get_key(value, i);
402
- item = items.get(key);
388
+ item = /** @type {EachItem} */ (items.get(key));
403
389
 
404
- if (item !== undefined) {
405
- item.a?.measure();
406
- (to_animate ??= new Set()).add(item);
407
- }
390
+ item.a?.measure();
391
+ (to_animate ??= new Set()).add(item);
408
392
  }
409
393
  }
410
394
 
@@ -412,40 +396,20 @@ function reconcile(
412
396
  value = array[i];
413
397
  key = get_key(value, i);
414
398
 
415
- item = items.get(key);
416
-
417
- if (item === undefined) {
418
- var pending = offscreen_items.get(key);
399
+ item = /** @type {EachItem} */ (items.get(key));
419
400
 
420
- if (pending !== undefined) {
421
- offscreen_items.delete(key);
422
- items.set(key, pending);
401
+ state.first ??= item;
423
402
 
424
- var next = prev ? prev.next : current;
403
+ if (!item.o) {
404
+ item.o = true;
425
405
 
426
- link(state, prev, pending);
427
- link(state, pending, next);
406
+ var next = prev ? prev.next : current;
428
407
 
429
- move(pending, next, anchor);
430
- prev = pending;
431
- } else {
432
- var child_anchor = current ? /** @type {TemplateNode} */ (current.e.nodes_start) : anchor;
433
-
434
- prev = create_item(
435
- child_anchor,
436
- state,
437
- prev,
438
- prev === null ? state.first : prev.next,
439
- value,
440
- key,
441
- i,
442
- render_fn,
443
- flags,
444
- get_collection
445
- );
446
- }
408
+ link(state, prev, item);
409
+ link(state, item, next);
447
410
 
448
- items.set(key, prev);
411
+ move(item, next, anchor);
412
+ prev = item;
449
413
 
450
414
  matched = [];
451
415
  stashed = [];
@@ -454,10 +418,6 @@ function reconcile(
454
418
  continue;
455
419
  }
456
420
 
457
- if (should_update) {
458
- update_item(item, value, i, flags);
459
- }
460
-
461
421
  if ((item.e.f & INERT) !== 0) {
462
422
  resume_effect(item.e);
463
423
  if (is_animated) {
@@ -575,63 +535,30 @@ function reconcile(
575
535
  });
576
536
  }
577
537
 
538
+ // TODO i have an inkling that the rest of this function is wrong...
539
+ // the offscreen items need to be linked, so that they all update correctly.
540
+ // the last onscreen item should link to the first offscreen item, etc
578
541
  each_effect.first = state.first && state.first.e;
579
542
  each_effect.last = prev && prev.e;
580
543
 
581
- for (var unused of offscreen_items.values()) {
582
- destroy_effect(unused.e);
583
- }
584
-
585
- offscreen_items.clear();
586
- }
587
-
588
- /**
589
- * @param {EachItem} item
590
- * @param {any} value
591
- * @param {number} index
592
- * @param {number} type
593
- * @returns {void}
594
- */
595
- function update_item(item, value, index, type) {
596
- if ((type & EACH_ITEM_REACTIVE) !== 0) {
597
- internal_set(item.v, value);
598
- }
599
-
600
- if ((type & EACH_INDEX_REACTIVE) !== 0) {
601
- internal_set(/** @type {Value<number>} */ (item.i), index);
602
- } else {
603
- item.i = index;
544
+ if (prev) {
545
+ prev.e.next = null;
604
546
  }
605
547
  }
606
548
 
607
549
  /**
608
550
  * @template V
609
551
  * @param {Node | null} anchor
610
- * @param {EachState} state
611
552
  * @param {EachItem | null} prev
612
- * @param {EachItem | null} next
613
553
  * @param {V} value
614
554
  * @param {unknown} key
615
555
  * @param {number} index
616
556
  * @param {(anchor: Node, item: V | Source<V>, index: number | Value<number>, collection: () => V[]) => void} render_fn
617
557
  * @param {number} flags
618
558
  * @param {() => V[]} get_collection
619
- * @param {boolean} [deferred]
620
559
  * @returns {EachItem}
621
560
  */
622
- function create_item(
623
- anchor,
624
- state,
625
- prev,
626
- next,
627
- value,
628
- key,
629
- index,
630
- render_fn,
631
- flags,
632
- get_collection,
633
- deferred
634
- ) {
561
+ function create_item(anchor, prev, value, key, index, render_fn, flags, get_collection) {
635
562
  var previous_each_item = current_each_item;
636
563
  var reactive = (flags & EACH_ITEM_REACTIVE) !== 0;
637
564
  var mutable = (flags & EACH_ITEM_IMMUTABLE) === 0;
@@ -657,8 +584,9 @@ function create_item(
657
584
  a: null,
658
585
  // @ts-expect-error
659
586
  e: null,
587
+ o: false,
660
588
  prev,
661
- next
589
+ next: null
662
590
  };
663
591
 
664
592
  current_each_item = item;
@@ -669,25 +597,15 @@ function create_item(
669
597
  fragment.append((anchor = create_text()));
670
598
  }
671
599
 
672
- item.e = branch(() => render_fn(/** @type {Node} */ (anchor), v, i, get_collection), hydrating);
600
+ item.e = branch(() => render_fn(/** @type {Node} */ (anchor), v, i, get_collection));
673
601
 
674
602
  item.e.prev = prev && prev.e;
675
- item.e.next = next && next.e;
676
603
 
677
- if (prev === null) {
678
- if (!deferred) {
679
- state.first = item;
680
- }
681
- } else {
604
+ if (prev !== null) {
682
605
  prev.next = item;
683
606
  prev.e.next = item.e;
684
607
  }
685
608
 
686
- if (next !== null) {
687
- next.prev = item;
688
- next.e.prev = item.e;
689
- }
690
-
691
609
  return item;
692
610
  } finally {
693
611
  current_each_item = previous_each_item;
@@ -119,6 +119,7 @@ export {
119
119
  legacy_pre_effect_reset,
120
120
  render_effect,
121
121
  template_effect,
122
+ deferred_template_effect,
122
123
  effect,
123
124
  user_effect,
124
125
  user_pre_effect
@@ -16,7 +16,8 @@ import {
16
16
  BOUNDARY_EFFECT,
17
17
  EAGER_EFFECT,
18
18
  HEAD_EFFECT,
19
- ERROR_VALUE
19
+ ERROR_VALUE,
20
+ WAS_MARKED
20
21
  } from '#client/constants';
21
22
  import { async_mode_flag } from '../../flags/index.js';
22
23
  import { deferred, define_property } from '../../shared/utils.js';
@@ -144,6 +145,10 @@ export class Batch {
144
145
 
145
146
  is_fork = false;
146
147
 
148
+ is_deferred() {
149
+ return this.is_fork || this.#blocking_pending > 0;
150
+ }
151
+
147
152
  /**
148
153
  *
149
154
  * @param {Effect[]} root_effects
@@ -172,7 +177,7 @@ export class Batch {
172
177
  this.#resolve();
173
178
  }
174
179
 
175
- if (this.#blocking_pending > 0 || this.is_fork) {
180
+ if (this.is_deferred()) {
176
181
  this.#defer_effects(target.effects);
177
182
  this.#defer_effects(target.render_effects);
178
183
  this.#defer_effects(target.block_effects);
@@ -270,11 +275,32 @@ export class Batch {
270
275
  const target = (e.f & DIRTY) !== 0 ? this.#dirty_effects : this.#maybe_dirty_effects;
271
276
  target.push(e);
272
277
 
278
+ // Since we're not executing these effects now, we need to clear any WAS_MARKED flags
279
+ // so that other batches can correctly reach these effects during their own traversal
280
+ this.#clear_marked(e.deps);
281
+
273
282
  // mark as clean so they get scheduled if they depend on pending async state
274
283
  set_signal_status(e, CLEAN);
275
284
  }
276
285
  }
277
286
 
287
+ /**
288
+ * @param {Value[] | null} deps
289
+ */
290
+ #clear_marked(deps) {
291
+ if (deps === null) return;
292
+
293
+ for (const dep of deps) {
294
+ if ((dep.f & DERIVED) === 0 || (dep.f & WAS_MARKED) === 0) {
295
+ continue;
296
+ }
297
+
298
+ dep.f ^= WAS_MARKED;
299
+
300
+ this.#clear_marked(/** @type {Derived} */ (dep).deps);
301
+ }
302
+ }
303
+
278
304
  /**
279
305
  * Associate a change to a given source with the current
280
306
  * batch, noting its previous and current values