svelte 5.45.3 → 5.45.5

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.
@@ -1,4 +1,4 @@
1
- /** @import { EachItem, EachState, Effect, MaybeSource, Source, TemplateNode, TransitionManager, Value } from '#client' */
1
+ /** @import { EachItem, EachOutroGroup, EachState, Effect, EffectNodes, MaybeSource, Source, TemplateNode, TransitionManager, Value } from '#client' */
2
2
  /** @import { Batch } from '../../reactivity/batch.js'; */
3
3
  import {
4
4
  EACH_INDEX_REACTIVE,
@@ -29,31 +29,21 @@ import {
29
29
  block,
30
30
  branch,
31
31
  destroy_effect,
32
- run_out_transitions,
33
- pause_children,
34
32
  pause_effect,
35
33
  resume_effect
36
34
  } from '../../reactivity/effects.js';
37
35
  import { source, mutable_source, internal_set } from '../../reactivity/sources.js';
38
36
  import { array_from, is_array } from '../../../shared/utils.js';
39
- import { COMMENT_NODE, INERT } from '#client/constants';
37
+ import { COMMENT_NODE, EFFECT_OFFSCREEN, INERT } from '#client/constants';
40
38
  import { queue_micro_task } from '../task.js';
41
39
  import { get } from '../../runtime.js';
42
40
  import { DEV } from 'esm-env';
43
41
  import { derived_safe_equal } from '../../reactivity/deriveds.js';
44
42
  import { current_batch } from '../../reactivity/batch.js';
45
43
 
46
- /**
47
- * The row of a keyed each block that is currently updating. We track this
48
- * so that `animate:` directives have something to attach themselves to
49
- * @type {EachItem | null}
50
- */
51
- export let current_each_item = null;
52
-
53
- /** @param {EachItem | null} item */
54
- export function set_current_each_item(item) {
55
- current_each_item = item;
56
- }
44
+ // When making substantive changes to this file, validate them with the each block stress test:
45
+ // https://svelte.dev/playground/1972b2cf46564476ad8c8c6405b23b7b
46
+ // This test also exists in this repo, as `packages/svelte/tests/manual/each-stress-test`
57
47
 
58
48
  /**
59
49
  * @param {any} _
@@ -67,7 +57,7 @@ export function index(_, i) {
67
57
  * Pause multiple effects simultaneously, and coordinate their
68
58
  * subsequent destruction. Used in each blocks
69
59
  * @param {EachState} state
70
- * @param {EachItem[]} to_destroy
60
+ * @param {Effect[]} to_destroy
71
61
  * @param {null | Node} controlled_anchor
72
62
  */
73
63
  function pause_effects(state, to_destroy, controlled_anchor) {
@@ -75,19 +65,44 @@ function pause_effects(state, to_destroy, controlled_anchor) {
75
65
  var transitions = [];
76
66
  var length = to_destroy.length;
77
67
 
68
+ /** @type {EachOutroGroup} */
69
+ var group;
70
+ var remaining = to_destroy.length;
71
+
78
72
  for (var i = 0; i < length; i++) {
79
- pause_children(to_destroy[i].e, transitions, true);
73
+ let effect = to_destroy[i];
74
+
75
+ pause_effect(
76
+ effect,
77
+ () => {
78
+ if (group) {
79
+ group.pending.delete(effect);
80
+ group.done.add(effect);
81
+
82
+ if (group.pending.size === 0) {
83
+ var groups = /** @type {Set<EachOutroGroup>} */ (state.outrogroups);
84
+
85
+ destroy_effects(array_from(group.done));
86
+ groups.delete(group);
87
+
88
+ if (groups.size === 0) {
89
+ state.outrogroups = null;
90
+ }
91
+ }
92
+ } else {
93
+ remaining -= 1;
94
+ }
95
+ },
96
+ false
97
+ );
80
98
  }
81
99
 
82
- run_out_transitions(transitions, () => {
100
+ if (remaining === 0) {
83
101
  // If we're in a controlled each block (i.e. the block is the only child of an
84
102
  // element), and we are removing all items, _and_ there are no out transitions,
85
103
  // we can use the fast path — emptying the element and replacing the anchor
86
104
  var fast_path = transitions.length === 0 && controlled_anchor !== null;
87
105
 
88
- // TODO only destroy effects if no pending batch needs them. otherwise,
89
- // just set `item.o` back to `false`
90
-
91
106
  if (fast_path) {
92
107
  var anchor = /** @type {Element} */ (controlled_anchor);
93
108
  var parent_node = /** @type {Element} */ (anchor.parentNode);
@@ -96,26 +111,34 @@ function pause_effects(state, to_destroy, controlled_anchor) {
96
111
  parent_node.append(anchor);
97
112
 
98
113
  state.items.clear();
99
- link(state, to_destroy[0].prev, to_destroy[length - 1].next);
100
114
  }
101
115
 
102
- for (var i = 0; i < length; i++) {
103
- var item = to_destroy[i];
104
-
105
- if (!fast_path) {
106
- state.items.delete(item.k);
107
- link(state, item.prev, item.next);
108
- }
116
+ destroy_effects(to_destroy, !fast_path);
117
+ } else {
118
+ group = {
119
+ pending: new Set(to_destroy),
120
+ done: new Set()
121
+ };
109
122
 
110
- destroy_effect(item.e, !fast_path);
111
- }
123
+ (state.outrogroups ??= new Set()).add(group);
124
+ }
125
+ }
112
126
 
113
- if (state.first === to_destroy[0]) {
114
- state.first = to_destroy[0].prev;
115
- }
116
- });
127
+ /**
128
+ * @param {Effect[]} to_destroy
129
+ * @param {boolean} remove_dom
130
+ */
131
+ function destroy_effects(to_destroy, remove_dom = true) {
132
+ // TODO only destroy effects if no pending batch needs them. otherwise,
133
+ // just re-add the `EFFECT_OFFSCREEN` flag
134
+ for (var i = 0; i < to_destroy.length; i++) {
135
+ destroy_effect(to_destroy[i], remove_dom);
136
+ }
117
137
  }
118
138
 
139
+ /** @type {TemplateNode} */
140
+ var offscreen_anchor;
141
+
119
142
  /**
120
143
  * @template V
121
144
  * @param {Element | Comment} node The next sibling node, or the parent node if this is a 'controlled' block
@@ -132,18 +155,13 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
132
155
  /** @type {Map<any, EachItem>} */
133
156
  var items = new Map();
134
157
 
135
- /** @type {EachItem | null} */
136
- var first = null;
137
-
138
158
  var is_controlled = (flags & EACH_IS_CONTROLLED) !== 0;
139
- var is_reactive_value = (flags & EACH_ITEM_REACTIVE) !== 0;
140
- var is_reactive_index = (flags & EACH_INDEX_REACTIVE) !== 0;
141
159
 
142
160
  if (is_controlled) {
143
161
  var parent_node = /** @type {Element} */ (node);
144
162
 
145
163
  anchor = hydrating
146
- ? set_hydrate_node(/** @type {Comment | Text} */ (get_first_child(parent_node)))
164
+ ? set_hydrate_node(get_first_child(parent_node))
147
165
  : parent_node.appendChild(create_text());
148
166
  }
149
167
 
@@ -151,7 +169,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
151
169
  hydrate_next();
152
170
  }
153
171
 
154
- /** @type {{ fragment: DocumentFragment | null, effect: Effect } | null} */
172
+ /** @type {Effect | null} */
155
173
  var fallback = null;
156
174
 
157
175
  // TODO: ideally we could use derived for runes mode but because of the ability
@@ -169,20 +187,19 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
169
187
  var first_run = true;
170
188
 
171
189
  function commit() {
190
+ state.fallback = fallback;
172
191
  reconcile(state, array, anchor, flags, get_key);
173
192
 
174
193
  if (fallback !== null) {
175
194
  if (array.length === 0) {
176
- if (fallback.fragment) {
177
- anchor.before(fallback.fragment);
178
- fallback.fragment = null;
195
+ if ((fallback.f & EFFECT_OFFSCREEN) === 0) {
196
+ resume_effect(fallback);
179
197
  } else {
180
- resume_effect(fallback.effect);
198
+ fallback.f ^= EFFECT_OFFSCREEN;
199
+ move(fallback, null, anchor);
181
200
  }
182
-
183
- effect.first = fallback.effect;
184
201
  } else {
185
- pause_effect(fallback.effect, () => {
202
+ pause_effect(fallback, () => {
186
203
  // TODO only null out if no pending batch needs it,
187
204
  // otherwise re-add `fallback.fragment` and move the
188
205
  // effect into it
@@ -214,10 +231,9 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
214
231
 
215
232
  var keys = new Set();
216
233
  var batch = /** @type {Batch} */ (current_batch);
217
- var prev = null;
218
234
  var defer = should_defer_append();
219
235
 
220
- for (var i = 0; i < length; i += 1) {
236
+ for (var index = 0; index < length; index += 1) {
221
237
  if (
222
238
  hydrating &&
223
239
  hydrate_node.nodeType === COMMENT_NODE &&
@@ -230,48 +246,33 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
230
246
  set_hydrating(false);
231
247
  }
232
248
 
233
- var value = array[i];
234
- var key = get_key(value, i);
249
+ var value = array[index];
250
+ var key = get_key(value, index);
235
251
 
236
252
  var item = first_run ? null : items.get(key);
237
253
 
238
254
  if (item) {
239
255
  // update before reconciliation, to trigger any async updates
240
- if (is_reactive_value) {
241
- internal_set(item.v, value);
242
- }
243
-
244
- if (is_reactive_index) {
245
- internal_set(/** @type {Value<number>} */ (item.i), i);
246
- } else {
247
- item.i = i;
248
- }
256
+ if (item.v) internal_set(item.v, value);
257
+ if (item.i) internal_set(item.i, index);
249
258
 
250
259
  if (defer) {
251
260
  batch.skipped_effects.delete(item.e);
252
261
  }
253
262
  } else {
254
263
  item = create_item(
255
- first_run ? anchor : null,
256
- prev,
264
+ items,
265
+ first_run ? anchor : (offscreen_anchor ??= create_text()),
257
266
  value,
258
267
  key,
259
- i,
268
+ index,
260
269
  render_fn,
261
270
  flags,
262
271
  get_collection
263
272
  );
264
273
 
265
- if (first_run) {
266
- item.o = true;
267
-
268
- if (prev === null) {
269
- first = item;
270
- } else {
271
- prev.next = item;
272
- }
273
-
274
- prev = item;
274
+ if (!first_run) {
275
+ item.e.f |= EFFECT_OFFSCREEN;
275
276
  }
276
277
 
277
278
  items.set(key, item);
@@ -282,19 +283,10 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
282
283
 
283
284
  if (length === 0 && fallback_fn && !fallback) {
284
285
  if (first_run) {
285
- fallback = {
286
- fragment: null,
287
- effect: branch(() => fallback_fn(anchor))
288
- };
286
+ fallback = branch(() => fallback_fn(anchor));
289
287
  } else {
290
- var fragment = document.createDocumentFragment();
291
- var target = create_text();
292
- fragment.append(target);
293
-
294
- fallback = {
295
- fragment,
296
- effect: branch(() => fallback_fn(target))
297
- };
288
+ fallback = branch(() => fallback_fn((offscreen_anchor ??= create_text())));
289
+ fallback.f |= EFFECT_OFFSCREEN;
298
290
  }
299
291
  }
300
292
 
@@ -335,7 +327,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
335
327
  });
336
328
 
337
329
  /** @type {EachState} */
338
- var state = { effect, flags, items, first };
330
+ var state = { effect, flags, items, outrogroups: null, fallback };
339
331
 
340
332
  first_run = false;
341
333
 
@@ -359,21 +351,21 @@ function reconcile(state, array, anchor, flags, get_key) {
359
351
 
360
352
  var length = array.length;
361
353
  var items = state.items;
362
- var current = state.first;
354
+ var current = state.effect.first;
363
355
 
364
- /** @type {undefined | Set<EachItem>} */
356
+ /** @type {undefined | Set<Effect>} */
365
357
  var seen;
366
358
 
367
- /** @type {EachItem | null} */
359
+ /** @type {Effect | null} */
368
360
  var prev = null;
369
361
 
370
- /** @type {undefined | Set<EachItem>} */
362
+ /** @type {undefined | Set<Effect>} */
371
363
  var to_animate;
372
364
 
373
- /** @type {EachItem[]} */
365
+ /** @type {Effect[]} */
374
366
  var matched = [];
375
367
 
376
- /** @type {EachItem[]} */
368
+ /** @type {Effect[]} */
377
369
  var stashed = [];
378
370
 
379
371
  /** @type {V} */
@@ -382,8 +374,8 @@ function reconcile(state, array, anchor, flags, get_key) {
382
374
  /** @type {any} */
383
375
  var key;
384
376
 
385
- /** @type {EachItem | undefined} */
386
- var item;
377
+ /** @type {Effect | undefined} */
378
+ var effect;
387
379
 
388
380
  /** @type {number} */
389
381
  var i;
@@ -392,13 +384,13 @@ function reconcile(state, array, anchor, flags, get_key) {
392
384
  for (i = 0; i < length; i += 1) {
393
385
  value = array[i];
394
386
  key = get_key(value, i);
395
- item = /** @type {EachItem} */ (items.get(key));
387
+ effect = /** @type {EachItem} */ (items.get(key)).e;
396
388
 
397
389
  // offscreen == coming in now, no animation in that case,
398
390
  // else this would happen https://github.com/sveltejs/svelte/issues/17181
399
- if (item.o) {
400
- item.a?.measure();
401
- (to_animate ??= new Set()).add(item);
391
+ if ((effect.f & EFFECT_OFFSCREEN) === 0) {
392
+ effect.nodes?.a?.measure();
393
+ (to_animate ??= new Set()).add(effect);
402
394
  }
403
395
  }
404
396
  }
@@ -407,38 +399,53 @@ function reconcile(state, array, anchor, flags, get_key) {
407
399
  value = array[i];
408
400
  key = get_key(value, i);
409
401
 
410
- item = /** @type {EachItem} */ (items.get(key));
402
+ effect = /** @type {EachItem} */ (items.get(key)).e;
411
403
 
412
- state.first ??= item;
404
+ if (state.outrogroups !== null) {
405
+ for (const group of state.outrogroups) {
406
+ group.pending.delete(effect);
407
+ group.done.delete(effect);
408
+ }
409
+ }
413
410
 
414
- if (!item.o) {
415
- item.o = true;
411
+ if ((effect.f & EFFECT_OFFSCREEN) !== 0) {
412
+ effect.f ^= EFFECT_OFFSCREEN;
416
413
 
417
- var next = prev ? prev.next : current;
414
+ if (effect === current) {
415
+ move(effect, null, anchor);
416
+ } else {
417
+ var next = prev ? prev.next : current;
418
418
 
419
- link(state, prev, item);
420
- link(state, item, next);
419
+ if (effect === state.effect.last) {
420
+ state.effect.last = effect.prev;
421
+ }
421
422
 
422
- move(item, next, anchor);
423
- prev = item;
423
+ if (effect.prev) effect.prev.next = effect.next;
424
+ if (effect.next) effect.next.prev = effect.prev;
425
+ link(state, prev, effect);
426
+ link(state, effect, next);
424
427
 
425
- matched = [];
426
- stashed = [];
428
+ move(effect, next, anchor);
429
+ prev = effect;
427
430
 
428
- current = prev.next;
429
- continue;
431
+ matched = [];
432
+ stashed = [];
433
+
434
+ current = prev.next;
435
+ continue;
436
+ }
430
437
  }
431
438
 
432
- if ((item.e.f & INERT) !== 0) {
433
- resume_effect(item.e);
439
+ if ((effect.f & INERT) !== 0) {
440
+ resume_effect(effect);
434
441
  if (is_animated) {
435
- item.a?.unfix();
436
- (to_animate ??= new Set()).delete(item);
442
+ effect.nodes?.a?.unfix();
443
+ (to_animate ??= new Set()).delete(effect);
437
444
  }
438
445
  }
439
446
 
440
- if (item !== current) {
441
- if (seen !== undefined && seen.has(item)) {
447
+ if (effect !== current) {
448
+ if (seen !== undefined && seen.has(effect)) {
442
449
  if (matched.length < stashed.length) {
443
450
  // more efficient to move later items to the front
444
451
  var start = stashed[0];
@@ -469,14 +476,14 @@ function reconcile(state, array, anchor, flags, get_key) {
469
476
  stashed = [];
470
477
  } else {
471
478
  // more efficient to move earlier items to the back
472
- seen.delete(item);
473
- move(item, current, anchor);
479
+ seen.delete(effect);
480
+ move(effect, current, anchor);
474
481
 
475
- link(state, item.prev, item.next);
476
- link(state, item, prev === null ? state.first : prev.next);
477
- link(state, prev, item);
482
+ link(state, effect.prev, effect.next);
483
+ link(state, effect, prev === null ? state.effect.first : prev.next);
484
+ link(state, prev, effect);
478
485
 
479
- prev = item;
486
+ prev = effect;
480
487
  }
481
488
 
482
489
  continue;
@@ -485,12 +492,8 @@ function reconcile(state, array, anchor, flags, get_key) {
485
492
  matched = [];
486
493
  stashed = [];
487
494
 
488
- while (current !== null && current.k !== key) {
489
- // If the each block isn't inert and an item has an effect that is already inert,
490
- // skip over adding it to our seen Set as the item is already being handled
491
- if ((current.e.f & INERT) === 0) {
492
- (seen ??= new Set()).add(current);
493
- }
495
+ while (current !== null && current !== effect) {
496
+ (seen ??= new Set()).add(current);
494
497
  stashed.push(current);
495
498
  current = current.next;
496
499
  }
@@ -498,42 +501,62 @@ function reconcile(state, array, anchor, flags, get_key) {
498
501
  if (current === null) {
499
502
  continue;
500
503
  }
504
+ }
501
505
 
502
- item = current;
506
+ if ((effect.f & EFFECT_OFFSCREEN) === 0) {
507
+ matched.push(effect);
503
508
  }
504
509
 
505
- matched.push(item);
506
- prev = item;
507
- current = item.next;
510
+ prev = effect;
511
+ current = effect.next;
508
512
  }
509
513
 
510
- let has_offscreen_items = items.size > length;
514
+ if (state.outrogroups !== null) {
515
+ for (const group of state.outrogroups) {
516
+ if (group.pending.size === 0) {
517
+ destroy_effects(array_from(group.done));
518
+ state.outrogroups?.delete(group);
519
+ }
520
+ }
521
+
522
+ if (state.outrogroups.size === 0) {
523
+ state.outrogroups = null;
524
+ }
525
+ }
511
526
 
512
527
  if (current !== null || seen !== undefined) {
513
- var to_destroy = seen === undefined ? [] : array_from(seen);
528
+ /** @type {Effect[]} */
529
+ var to_destroy = [];
530
+
531
+ if (seen !== undefined) {
532
+ for (effect of seen) {
533
+ if ((effect.f & INERT) === 0) {
534
+ to_destroy.push(effect);
535
+ }
536
+ }
537
+ }
514
538
 
515
539
  while (current !== null) {
516
540
  // If the each block isn't inert, then inert effects are currently outroing and will be removed once the transition is finished
517
- if ((current.e.f & INERT) === 0) {
541
+ if ((current.f & INERT) === 0 && current !== state.fallback) {
518
542
  to_destroy.push(current);
519
543
  }
544
+
520
545
  current = current.next;
521
546
  }
522
547
 
523
548
  var destroy_length = to_destroy.length;
524
549
 
525
- has_offscreen_items = items.size - destroy_length > length;
526
-
527
550
  if (destroy_length > 0) {
528
551
  var controlled_anchor = (flags & EACH_IS_CONTROLLED) !== 0 && length === 0 ? anchor : null;
529
552
 
530
553
  if (is_animated) {
531
554
  for (i = 0; i < destroy_length; i += 1) {
532
- to_destroy[i].a?.measure();
555
+ to_destroy[i].nodes?.a?.measure();
533
556
  }
534
557
 
535
558
  for (i = 0; i < destroy_length; i += 1) {
536
- to_destroy[i].a?.fix();
559
+ to_destroy[i].nodes?.a?.fix();
537
560
  }
538
561
  }
539
562
 
@@ -541,23 +564,11 @@ function reconcile(state, array, anchor, flags, get_key) {
541
564
  }
542
565
  }
543
566
 
544
- // Append offscreen items at the end
545
- if (has_offscreen_items) {
546
- for (const item of items.values()) {
547
- if (!item.o) {
548
- link(state, prev, item);
549
- prev = item;
550
- }
551
- }
552
- }
553
-
554
- state.effect.last = prev && prev.e;
555
-
556
567
  if (is_animated) {
557
568
  queue_micro_task(() => {
558
569
  if (to_animate === undefined) return;
559
- for (item of to_animate) {
560
- item.a?.apply();
570
+ for (effect of to_animate) {
571
+ effect.nodes?.a?.apply();
561
572
  }
562
573
  });
563
574
  }
@@ -565,8 +576,8 @@ function reconcile(state, array, anchor, flags, get_key) {
565
576
 
566
577
  /**
567
578
  * @template V
568
- * @param {Node | null} anchor
569
- * @param {EachItem | null} prev
579
+ * @param {Map<any, EachItem>} items
580
+ * @param {Node} anchor
570
581
  * @param {V} value
571
582
  * @param {unknown} key
572
583
  * @param {number} index
@@ -575,102 +586,81 @@ function reconcile(state, array, anchor, flags, get_key) {
575
586
  * @param {() => V[]} get_collection
576
587
  * @returns {EachItem}
577
588
  */
578
- function create_item(anchor, prev, value, key, index, render_fn, flags, get_collection) {
579
- var previous_each_item = current_each_item;
580
- var reactive = (flags & EACH_ITEM_REACTIVE) !== 0;
581
- var mutable = (flags & EACH_ITEM_IMMUTABLE) === 0;
589
+ function create_item(items, anchor, value, key, index, render_fn, flags, get_collection) {
590
+ var v =
591
+ (flags & EACH_ITEM_REACTIVE) !== 0
592
+ ? (flags & EACH_ITEM_IMMUTABLE) === 0
593
+ ? mutable_source(value, false, false)
594
+ : source(value)
595
+ : null;
582
596
 
583
- var v = reactive ? (mutable ? mutable_source(value, false, false) : source(value)) : value;
584
- var i = (flags & EACH_INDEX_REACTIVE) === 0 ? index : source(index);
597
+ var i = (flags & EACH_INDEX_REACTIVE) !== 0 ? source(index) : null;
585
598
 
586
- if (DEV && reactive) {
599
+ if (DEV && v) {
587
600
  // For tracing purposes, we need to link the source signal we create with the
588
601
  // collection + index so that tracing works as intended
589
- /** @type {Value} */ (v).trace = () => {
590
- var collection_index = typeof i === 'number' ? index : i.v;
602
+ v.trace = () => {
591
603
  // eslint-disable-next-line @typescript-eslint/no-unused-expressions
592
- get_collection()[collection_index];
604
+ get_collection()[i?.v ?? index];
593
605
  };
594
606
  }
595
607
 
596
- /** @type {EachItem} */
597
- var item = {
598
- i,
608
+ return {
599
609
  v,
600
- k: key,
601
- a: null,
602
- // @ts-expect-error
603
- e: null,
604
- o: false,
605
- prev,
606
- next: null
607
- };
608
-
609
- current_each_item = item;
610
-
611
- try {
612
- if (anchor === null) {
613
- var fragment = document.createDocumentFragment();
614
- fragment.append((anchor = create_text()));
615
- }
616
-
617
- item.e = branch(() => render_fn(/** @type {Node} */ (anchor), v, i, get_collection));
618
-
619
- if (prev !== null) {
620
- // we only need to set `prev.next = item`, because
621
- // `item.prev = prev` was set on initialization.
622
- // the effects themselves are already linked
623
- prev.next = item;
624
- }
610
+ i,
611
+ e: branch(() => {
612
+ render_fn(anchor, v ?? value, i ?? index, get_collection);
625
613
 
626
- return item;
627
- } finally {
628
- current_each_item = previous_each_item;
629
- }
614
+ return () => {
615
+ items.delete(key);
616
+ };
617
+ })
618
+ };
630
619
  }
631
620
 
632
621
  /**
633
- * @param {EachItem} item
634
- * @param {EachItem | null} next
622
+ * @param {Effect} effect
623
+ * @param {Effect | null} next
635
624
  * @param {Text | Element | Comment} anchor
636
625
  */
637
- function move(item, next, anchor) {
638
- var end = item.next ? /** @type {TemplateNode} */ (item.next.e.nodes_start) : anchor;
626
+ function move(effect, next, anchor) {
627
+ if (!effect.nodes) return;
628
+
629
+ var node = effect.nodes.start;
630
+ var end = effect.nodes.end;
639
631
 
640
- var dest = next ? /** @type {TemplateNode} */ (next.e.nodes_start) : anchor;
641
- var node = /** @type {TemplateNode} */ (item.e.nodes_start);
632
+ var dest =
633
+ next && (next.f & EFFECT_OFFSCREEN) === 0
634
+ ? /** @type {EffectNodes} */ (next.nodes).start
635
+ : anchor;
642
636
 
643
- while (node !== null && node !== end) {
637
+ while (node !== null) {
644
638
  var next_node = /** @type {TemplateNode} */ (get_next_sibling(node));
645
639
  dest.before(node);
640
+
641
+ if (node === end) {
642
+ return;
643
+ }
644
+
646
645
  node = next_node;
647
646
  }
648
647
  }
649
648
 
650
649
  /**
651
650
  * @param {EachState} state
652
- * @param {EachItem | null} prev
653
- * @param {EachItem | null} next
651
+ * @param {Effect | null} prev
652
+ * @param {Effect | null} next
654
653
  */
655
654
  function link(state, prev, next) {
656
655
  if (prev === null) {
657
- state.first = next;
658
- state.effect.first = next && next.e;
656
+ state.effect.first = next;
659
657
  } else {
660
- if (prev.e.next) {
661
- prev.e.next.prev = null;
662
- }
663
-
664
658
  prev.next = next;
665
- prev.e.next = next && next.e;
666
659
  }
667
660
 
668
- if (next !== null) {
669
- if (next.e.prev) {
670
- next.e.prev.next = null;
671
- }
672
-
661
+ if (next === null) {
662
+ state.effect.last = prev;
663
+ } else {
673
664
  next.prev = prev;
674
- next.e.prev = prev && prev.e;
675
665
  }
676
666
  }