svelte 5.45.2 → 5.45.4

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 (46) hide show
  1. package/compiler/index.js +1 -1
  2. package/package.json +2 -2
  3. package/src/compiler/phases/1-parse/index.js +32 -16
  4. package/src/compiler/phases/1-parse/read/context.js +3 -12
  5. package/src/compiler/phases/1-parse/state/element.js +79 -55
  6. package/src/compiler/phases/1-parse/state/tag.js +5 -16
  7. package/src/compiler/phases/2-analyze/index.js +7 -2
  8. package/src/compiler/phases/2-analyze/visitors/Identifier.js +2 -1
  9. package/src/compiler/phases/2-analyze/visitors/RegularElement.js +3 -2
  10. package/src/compiler/phases/3-transform/client/transform-template/index.js +1 -2
  11. package/src/compiler/phases/3-transform/client/visitors/BindDirective.js +27 -11
  12. package/src/compiler/phases/3-transform/client/visitors/CallExpression.js +2 -1
  13. package/src/compiler/phases/3-transform/client/visitors/Component.js +1 -2
  14. package/src/compiler/phases/3-transform/client/visitors/Fragment.js +1 -1
  15. package/src/compiler/phases/3-transform/client/visitors/RegularElement.js +1 -0
  16. package/src/compiler/phases/3-transform/client/visitors/SvelteComponent.js +2 -1
  17. package/src/compiler/phases/3-transform/client/visitors/SvelteHead.js +1 -1
  18. package/src/compiler/phases/3-transform/client/visitors/SvelteSelf.js +2 -1
  19. package/src/compiler/phases/3-transform/client/visitors/TitleElement.js +1 -1
  20. package/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js +2 -1
  21. package/src/compiler/phases/3-transform/client/visitors/shared/component.js +27 -25
  22. package/src/compiler/phases/3-transform/client/visitors/shared/events.js +8 -2
  23. package/src/compiler/phases/3-transform/client/visitors/shared/fragment.js +10 -4
  24. package/src/compiler/phases/3-transform/client/visitors/shared/utils.js +2 -2
  25. package/src/compiler/phases/3-transform/server/visitors/RegularElement.js +1 -1
  26. package/src/compiler/phases/3-transform/server/visitors/SvelteElement.js +1 -1
  27. package/src/compiler/phases/3-transform/server/visitors/shared/element.js +2 -2
  28. package/src/compiler/phases/nodes.js +4 -2
  29. package/src/compiler/state.js +12 -4
  30. package/src/compiler/utils/builders.js +6 -2
  31. package/src/internal/client/dev/debug.js +365 -7
  32. package/src/internal/client/dom/blocks/each.js +24 -44
  33. package/src/internal/client/dom/blocks/html.js +3 -3
  34. package/src/internal/client/dom/blocks/svelte-element.js +8 -11
  35. package/src/internal/client/dom/elements/transitions.js +15 -7
  36. package/src/internal/client/dom/template.js +12 -10
  37. package/src/internal/client/reactivity/async.js +1 -6
  38. package/src/internal/client/reactivity/batch.js +4 -1
  39. package/src/internal/client/reactivity/effects.js +20 -16
  40. package/src/internal/client/reactivity/sources.js +2 -0
  41. package/src/internal/client/render.js +2 -2
  42. package/src/internal/client/runtime.js +1 -1
  43. package/src/internal/server/hydratable.js +11 -1
  44. package/src/version.js +1 -1
  45. package/types/index.d.ts +18 -11
  46. package/types/index.d.ts.map +1 -1
@@ -5,12 +5,18 @@ import {
5
5
  BOUNDARY_EFFECT,
6
6
  BRANCH_EFFECT,
7
7
  CLEAN,
8
+ CONNECTED,
8
9
  DERIVED,
10
+ DIRTY,
9
11
  EFFECT,
10
12
  ASYNC,
13
+ DESTROYED,
14
+ INERT,
11
15
  MAYBE_DIRTY,
12
16
  RENDER_EFFECT,
13
- ROOT_EFFECT
17
+ ROOT_EFFECT,
18
+ WAS_MARKED,
19
+ MANAGED_EFFECT
14
20
  } from '#client/constants';
15
21
  import { snapshot } from '../../shared/clone.js';
16
22
  import { untrack } from '../runtime.js';
@@ -30,11 +36,13 @@ export function root(effect) {
30
36
  /**
31
37
  *
32
38
  * @param {Effect} effect
39
+ * @param {boolean} append_effect
40
+ * @returns {string}
33
41
  */
34
- export function log_effect_tree(effect, depth = 0) {
42
+ function effect_label(effect, append_effect = false) {
35
43
  const flags = effect.f;
36
44
 
37
- let label = '(unknown)';
45
+ let label = `(unknown ${append_effect ? 'effect' : ''})`;
38
46
 
39
47
  if ((flags & ROOT_EFFECT) !== 0) {
40
48
  label = 'root';
@@ -42,6 +50,8 @@ export function log_effect_tree(effect, depth = 0) {
42
50
  label = 'boundary';
43
51
  } else if ((flags & BLOCK_EFFECT) !== 0) {
44
52
  label = 'block';
53
+ } else if ((flags & MANAGED_EFFECT) !== 0) {
54
+ label = 'managed';
45
55
  } else if ((flags & ASYNC) !== 0) {
46
56
  label = 'async';
47
57
  } else if ((flags & BRANCH_EFFECT) !== 0) {
@@ -52,6 +62,20 @@ export function log_effect_tree(effect, depth = 0) {
52
62
  label = 'effect';
53
63
  }
54
64
 
65
+ if (append_effect && !label.endsWith('effect')) {
66
+ label += ' effect';
67
+ }
68
+
69
+ return label;
70
+ }
71
+ /**
72
+ *
73
+ * @param {Effect} effect
74
+ */
75
+ export function log_effect_tree(effect, depth = 0) {
76
+ const flags = effect.f;
77
+ const label = effect_label(effect);
78
+
55
79
  let status =
56
80
  (flags & CLEAN) !== 0 ? 'clean' : (flags & MAYBE_DIRTY) !== 0 ? 'maybe dirty' : 'dirty';
57
81
 
@@ -86,13 +110,13 @@ export function log_effect_tree(effect, depth = 0) {
86
110
  console.groupEnd();
87
111
  }
88
112
 
89
- if (effect.nodes_start && effect.nodes_end) {
113
+ if (effect.nodes) {
90
114
  // eslint-disable-next-line no-console
91
- console.log(effect.nodes_start);
115
+ console.log(effect.nodes.start);
92
116
 
93
- if (effect.nodes_start !== effect.nodes_end) {
117
+ if (effect.nodes.start !== effect.nodes.end) {
94
118
  // eslint-disable-next-line no-console
95
- console.log(effect.nodes_end);
119
+ console.log(effect.nodes.end);
96
120
  }
97
121
  }
98
122
 
@@ -140,3 +164,337 @@ function log_dep(dep) {
140
164
  );
141
165
  }
142
166
  }
167
+
168
+ /**
169
+ * Logs all reactions of a source or derived transitively
170
+ * @param {Derived | Value} signal
171
+ */
172
+ export function log_reactions(signal) {
173
+ /** @type {Set<Derived | Value>} */
174
+ const visited = new Set();
175
+
176
+ /**
177
+ * Returns an array of flag names that are set on the given flags bitmask
178
+ * @param {number} flags
179
+ * @returns {string[]}
180
+ */
181
+ function get_derived_flag_names(flags) {
182
+ /** @type {string[]} */
183
+ const names = [];
184
+
185
+ if ((flags & CLEAN) !== 0) names.push('CLEAN');
186
+ if ((flags & DIRTY) !== 0) names.push('DIRTY');
187
+ if ((flags & MAYBE_DIRTY) !== 0) names.push('MAYBE_DIRTY');
188
+ if ((flags & CONNECTED) !== 0) names.push('CONNECTED');
189
+ if ((flags & WAS_MARKED) !== 0) names.push('WAS_MARKED');
190
+ if ((flags & INERT) !== 0) names.push('INERT');
191
+ if ((flags & DESTROYED) !== 0) names.push('DESTROYED');
192
+
193
+ return names;
194
+ }
195
+
196
+ /**
197
+ * @param {Derived | Value} d
198
+ * @param {number} depth
199
+ */
200
+ function log_derived(d, depth) {
201
+ const flags = d.f;
202
+ const flag_names = get_derived_flag_names(flags);
203
+ const flags_str = flag_names.length > 0 ? `(${flag_names.join(', ')})` : '(no flags)';
204
+
205
+ // eslint-disable-next-line no-console
206
+ console.group(
207
+ `%c${flags & DERIVED ? '$derived' : '$state'} %c${d.label ?? '<unknown>'} %c${flags_str}`,
208
+ 'font-weight: bold; color: CornflowerBlue',
209
+ 'font-weight: normal; color: inherit',
210
+ 'font-weight: normal; color: gray'
211
+ );
212
+
213
+ // eslint-disable-next-line no-console
214
+ console.log(untrack(() => snapshot(d.v)));
215
+
216
+ if ('fn' in d) {
217
+ // eslint-disable-next-line no-console
218
+ console.log('%cfn:', 'font-weight: bold', d.fn);
219
+ }
220
+
221
+ if (d.reactions !== null && d.reactions.length > 0) {
222
+ // eslint-disable-next-line no-console
223
+ console.group('%creactions', 'font-weight: bold');
224
+
225
+ for (const reaction of d.reactions) {
226
+ if ((reaction.f & DERIVED) !== 0) {
227
+ const derived_reaction = /** @type {Derived} */ (reaction);
228
+
229
+ if (visited.has(derived_reaction)) {
230
+ // eslint-disable-next-line no-console
231
+ console.log(
232
+ `%c$derived %c${derived_reaction.label ?? '<unknown>'} %c(already seen)`,
233
+ 'font-weight: bold; color: CornflowerBlue',
234
+ 'font-weight: normal; color: inherit',
235
+ 'font-weight: bold; color: orange'
236
+ );
237
+ } else {
238
+ visited.add(derived_reaction);
239
+ log_derived(derived_reaction, depth + 1);
240
+ }
241
+ } else {
242
+ // It's an effect
243
+ const label = effect_label(/** @type {Effect} */ (reaction), true);
244
+ const status = (flags & MAYBE_DIRTY) !== 0 ? 'maybe dirty' : 'dirty';
245
+
246
+ // Collect parent statuses
247
+ /** @type {string[]} */
248
+ const parent_statuses = [];
249
+ let show = false;
250
+ let current = /** @type {Effect} */ (reaction).parent;
251
+ while (current !== null) {
252
+ const parent_flags = current.f;
253
+ if ((parent_flags & (ROOT_EFFECT | BRANCH_EFFECT)) !== 0) {
254
+ const parent_status = (parent_flags & CLEAN) !== 0 ? 'clean' : 'not clean';
255
+ if (parent_status === 'clean' && parent_statuses.includes('not clean')) show = true;
256
+ parent_statuses.push(parent_status);
257
+ }
258
+ if (!current.parent) break;
259
+ current = current.parent;
260
+ }
261
+
262
+ // Check if reaction is reachable from root
263
+ const seen_effects = new Set();
264
+ let reachable = false;
265
+ /**
266
+ * @param {Effect | null} effect
267
+ */
268
+ function check_reachable(effect) {
269
+ if (effect === null || reachable) return;
270
+ if (effect === reaction) {
271
+ reachable = true;
272
+ return;
273
+ }
274
+ if (effect.f & DESTROYED) return;
275
+ if (seen_effects.has(effect)) {
276
+ throw new Error('');
277
+ }
278
+ seen_effects.add(effect);
279
+ let child = effect.first;
280
+ while (child !== null) {
281
+ check_reachable(child);
282
+ child = child.next;
283
+ }
284
+ }
285
+ try {
286
+ if (current) check_reachable(current);
287
+ } catch (e) {
288
+ // eslint-disable-next-line no-console
289
+ console.log(
290
+ `%c⚠️ Circular reference detected in effect tree`,
291
+ 'font-weight: bold; color: red',
292
+ seen_effects
293
+ );
294
+ }
295
+
296
+ if (!reachable) {
297
+ // eslint-disable-next-line no-console
298
+ console.log(
299
+ `%c⚠️ Effect is NOT reachable from its parent chain`,
300
+ 'font-weight: bold; color: red'
301
+ );
302
+ }
303
+
304
+ const parent_status_str = show ? ` (${parent_statuses.join(', ')})` : '';
305
+
306
+ // eslint-disable-next-line no-console
307
+ console.log(
308
+ `%c${label} (${status})${parent_status_str}`,
309
+ `font-weight: bold; color: ${parent_status_str ? 'red' : 'green'}`,
310
+ reaction
311
+ );
312
+ }
313
+ }
314
+
315
+ // eslint-disable-next-line no-console
316
+ console.groupEnd();
317
+ } else {
318
+ // eslint-disable-next-line no-console
319
+ console.log('%cno reactions', 'font-style: italic; color: gray');
320
+ }
321
+
322
+ // eslint-disable-next-line no-console
323
+ console.groupEnd();
324
+ }
325
+
326
+ // eslint-disable-next-line no-console
327
+ console.group(`%cDerived Reactions Graph`, 'font-weight: bold; color: purple');
328
+
329
+ visited.add(signal);
330
+ log_derived(signal, 0);
331
+
332
+ // eslint-disable-next-line no-console
333
+ console.groupEnd();
334
+ }
335
+
336
+ /**
337
+ * Traverses an effect tree and logs branches where a non-clean branch exists below a clean branch
338
+ * @param {Effect} effect
339
+ */
340
+ export function log_inconsistent_branches(effect) {
341
+ const root_effect = root(effect);
342
+
343
+ /**
344
+ * @typedef {{
345
+ * effect: Effect,
346
+ * status: 'clean' | 'maybe dirty' | 'dirty',
347
+ * parent_clean: boolean,
348
+ * children: BranchInfo[]
349
+ * }} BranchInfo
350
+ */
351
+
352
+ /**
353
+ * Collects branch effects from the tree
354
+ * @param {Effect} eff
355
+ * @param {boolean} parent_clean - whether any ancestor branch is clean
356
+ * @returns {BranchInfo[]}
357
+ */
358
+ function collect_branches(eff, parent_clean) {
359
+ /** @type {BranchInfo[]} */
360
+ const branches = [];
361
+ const flags = eff.f;
362
+ const is_branch = (flags & BRANCH_EFFECT) !== 0;
363
+
364
+ if (is_branch) {
365
+ const status =
366
+ (flags & CLEAN) !== 0 ? 'clean' : (flags & MAYBE_DIRTY) !== 0 ? 'maybe dirty' : 'dirty';
367
+
368
+ /** @type {BranchInfo[]} */
369
+ const child_branches = [];
370
+
371
+ let child = eff.first;
372
+ while (child !== null) {
373
+ child_branches.push(...collect_branches(child, status === 'clean'));
374
+ child = child.next;
375
+ }
376
+
377
+ branches.push({
378
+ effect: eff,
379
+ status,
380
+ parent_clean,
381
+ children: child_branches
382
+ });
383
+ } else {
384
+ // Not a branch, continue traversing
385
+ let child = eff.first;
386
+ while (child !== null) {
387
+ branches.push(...collect_branches(child, parent_clean));
388
+ child = child.next;
389
+ }
390
+ }
391
+
392
+ return branches;
393
+ }
394
+
395
+ /**
396
+ * Checks if a branch tree contains any inconsistencies (non-clean below clean)
397
+ * @param {BranchInfo} branch
398
+ * @param {boolean} ancestor_clean
399
+ * @returns {boolean}
400
+ */
401
+ function has_inconsistency(branch, ancestor_clean) {
402
+ const is_inconsistent = ancestor_clean && branch.status !== 'clean';
403
+ if (is_inconsistent) return true;
404
+
405
+ const new_ancestor_clean = ancestor_clean || branch.status === 'clean';
406
+ for (const child of branch.children) {
407
+ if (has_inconsistency(child, new_ancestor_clean)) return true;
408
+ }
409
+ return false;
410
+ }
411
+
412
+ /**
413
+ * Logs a branch and its children, but only if there are inconsistencies
414
+ * @param {BranchInfo} branch
415
+ * @param {boolean} ancestor_clean
416
+ * @param {number} depth
417
+ */
418
+ function log_branch(branch, ancestor_clean, depth) {
419
+ const is_inconsistent = ancestor_clean && branch.status !== 'clean';
420
+ const new_ancestor_clean = ancestor_clean || branch.status === 'clean';
421
+
422
+ // Only log if this branch or any descendant has an inconsistency
423
+ if (!has_inconsistency(branch, ancestor_clean) && !is_inconsistent) {
424
+ return;
425
+ }
426
+
427
+ const style = is_inconsistent
428
+ ? 'font-weight: bold; color: red'
429
+ : branch.status === 'clean'
430
+ ? 'font-weight: normal; color: green'
431
+ : 'font-weight: bold; color: orange';
432
+
433
+ const warning = is_inconsistent ? ' ⚠️ INCONSISTENT' : '';
434
+
435
+ // eslint-disable-next-line no-console
436
+ console.group(`%cbranch (${branch.status})${warning}`, style);
437
+
438
+ // eslint-disable-next-line no-console
439
+ console.log('%ceffect:', 'font-weight: bold', branch.effect);
440
+
441
+ if (branch.effect.fn) {
442
+ // eslint-disable-next-line no-console
443
+ console.log('%cfn:', 'font-weight: bold', branch.effect.fn);
444
+ }
445
+
446
+ if (branch.effect.deps !== null) {
447
+ // eslint-disable-next-line no-console
448
+ console.groupCollapsed('%cdeps', 'font-weight: normal');
449
+ for (const dep of branch.effect.deps) {
450
+ log_dep(dep);
451
+ }
452
+ // eslint-disable-next-line no-console
453
+ console.groupEnd();
454
+ }
455
+
456
+ if (is_inconsistent) {
457
+ log_effect_tree(branch.effect);
458
+ } else if (branch.children.length > 0) {
459
+ // eslint-disable-next-line no-console
460
+ console.group('%cchild branches', 'font-weight: bold');
461
+ for (const child of branch.children) {
462
+ log_branch(child, new_ancestor_clean, depth + 1);
463
+ }
464
+ // eslint-disable-next-line no-console
465
+ console.groupEnd();
466
+ }
467
+
468
+ // eslint-disable-next-line no-console
469
+ console.groupEnd();
470
+ }
471
+
472
+ const branches = collect_branches(root_effect, false);
473
+
474
+ // Check if there are any inconsistencies at all
475
+ let has_any_inconsistency = false;
476
+ for (const branch of branches) {
477
+ if (has_inconsistency(branch, false)) {
478
+ has_any_inconsistency = true;
479
+ break;
480
+ }
481
+ }
482
+
483
+ if (!has_any_inconsistency) {
484
+ // eslint-disable-next-line no-console
485
+ console.log('%cNo inconsistent branches found', 'font-weight: bold; color: green');
486
+ return;
487
+ }
488
+
489
+ // eslint-disable-next-line no-console
490
+ console.group(`%cInconsistent Branches (non-clean below clean)`, 'font-weight: bold; color: red');
491
+
492
+ for (const branch of branches) {
493
+ log_branch(branch, false, 0);
494
+ }
495
+
496
+ // eslint-disable-next-line no-console
497
+ console.groupEnd();
498
+
499
+ return true;
500
+ }
@@ -1,4 +1,4 @@
1
- /** @import { EachItem, EachState, Effect, MaybeSource, Source, TemplateNode, TransitionManager, Value } from '#client' */
1
+ /** @import { EachItem, 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,
@@ -43,18 +43,6 @@ import { DEV } from 'esm-env';
43
43
  import { derived_safe_equal } from '../../reactivity/deriveds.js';
44
44
  import { current_batch } from '../../reactivity/batch.js';
45
45
 
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
- }
57
-
58
46
  /**
59
47
  * @param {any} _
60
48
  * @param {number} i
@@ -243,8 +231,6 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
243
231
 
244
232
  if (is_reactive_index) {
245
233
  internal_set(/** @type {Value<number>} */ (item.i), i);
246
- } else {
247
- item.i = i;
248
234
  }
249
235
 
250
236
  if (defer) {
@@ -397,7 +383,7 @@ function reconcile(state, array, anchor, flags, get_key) {
397
383
  // offscreen == coming in now, no animation in that case,
398
384
  // else this would happen https://github.com/sveltejs/svelte/issues/17181
399
385
  if (item.o) {
400
- item.a?.measure();
386
+ item.e.nodes?.a?.measure();
401
387
  (to_animate ??= new Set()).add(item);
402
388
  }
403
389
  }
@@ -432,7 +418,7 @@ function reconcile(state, array, anchor, flags, get_key) {
432
418
  if ((item.e.f & INERT) !== 0) {
433
419
  resume_effect(item.e);
434
420
  if (is_animated) {
435
- item.a?.unfix();
421
+ item.e.nodes?.a?.unfix();
436
422
  (to_animate ??= new Set()).delete(item);
437
423
  }
438
424
  }
@@ -485,7 +471,7 @@ function reconcile(state, array, anchor, flags, get_key) {
485
471
  matched = [];
486
472
  stashed = [];
487
473
 
488
- while (current !== null && current.k !== key) {
474
+ while (current !== null && current !== item) {
489
475
  // If the each block isn't inert and an item has an effect that is already inert,
490
476
  // skip over adding it to our seen Set as the item is already being handled
491
477
  if ((current.e.f & INERT) === 0) {
@@ -529,11 +515,11 @@ function reconcile(state, array, anchor, flags, get_key) {
529
515
 
530
516
  if (is_animated) {
531
517
  for (i = 0; i < destroy_length; i += 1) {
532
- to_destroy[i].a?.measure();
518
+ to_destroy[i].e.nodes?.a?.measure();
533
519
  }
534
520
 
535
521
  for (i = 0; i < destroy_length; i += 1) {
536
- to_destroy[i].a?.fix();
522
+ to_destroy[i].e.nodes?.a?.fix();
537
523
  }
538
524
  }
539
525
 
@@ -557,7 +543,7 @@ function reconcile(state, array, anchor, flags, get_key) {
557
543
  queue_micro_task(() => {
558
544
  if (to_animate === undefined) return;
559
545
  for (item of to_animate) {
560
- item.a?.apply();
546
+ item.e.nodes?.a?.apply();
561
547
  }
562
548
  });
563
549
  }
@@ -576,7 +562,6 @@ function reconcile(state, array, anchor, flags, get_key) {
576
562
  * @returns {EachItem}
577
563
  */
578
564
  function create_item(anchor, prev, value, key, index, render_fn, flags, get_collection) {
579
- var previous_each_item = current_each_item;
580
565
  var reactive = (flags & EACH_ITEM_REACTIVE) !== 0;
581
566
  var mutable = (flags & EACH_ITEM_IMMUTABLE) === 0;
582
567
 
@@ -598,7 +583,6 @@ function create_item(anchor, prev, value, key, index, render_fn, flags, get_coll
598
583
  i,
599
584
  v,
600
585
  k: key,
601
- a: null,
602
586
  // @ts-expect-error
603
587
  e: null,
604
588
  o: false,
@@ -606,27 +590,21 @@ function create_item(anchor, prev, value, key, index, render_fn, flags, get_coll
606
590
  next: null
607
591
  };
608
592
 
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));
593
+ if (anchor === null) {
594
+ var fragment = document.createDocumentFragment();
595
+ fragment.append((anchor = create_text()));
596
+ }
618
597
 
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
- }
598
+ item.e = branch(() => render_fn(/** @type {Node} */ (anchor), v, i, get_collection));
625
599
 
626
- return item;
627
- } finally {
628
- current_each_item = previous_each_item;
600
+ if (prev !== null) {
601
+ // we only need to set `prev.next = item`, because
602
+ // `item.prev = prev` was set on initialization.
603
+ // the effects themselves are already linked
604
+ prev.next = item;
629
605
  }
606
+
607
+ return item;
630
608
  }
631
609
 
632
610
  /**
@@ -635,10 +613,12 @@ function create_item(anchor, prev, value, key, index, render_fn, flags, get_coll
635
613
  * @param {Text | Element | Comment} anchor
636
614
  */
637
615
  function move(item, next, anchor) {
638
- var end = item.next ? /** @type {TemplateNode} */ (item.next.e.nodes_start) : anchor;
616
+ if (!item.e.nodes) return;
617
+
618
+ var end = item.next ? /** @type {EffectNodes} */ (item.next.e.nodes).start : anchor;
639
619
 
640
- var dest = next ? /** @type {TemplateNode} */ (next.e.nodes_start) : anchor;
641
- var node = /** @type {TemplateNode} */ (item.e.nodes_start);
620
+ var dest = next ? /** @type {EffectNodes} */ (next.e.nodes).start : anchor;
621
+ var node = /** @type {TemplateNode} */ (item.e.nodes.start);
642
622
 
643
623
  while (node !== null && node !== end) {
644
624
  var next_node = /** @type {TemplateNode} */ (get_next_sibling(node));
@@ -54,9 +54,9 @@ export function html(node, get_value, svg = false, mathml = false, skip_warning
54
54
  return;
55
55
  }
56
56
 
57
- if (effect.nodes_start !== null) {
58
- remove_effect_dom(effect.nodes_start, /** @type {TemplateNode} */ (effect.nodes_end));
59
- effect.nodes_start = effect.nodes_end = null;
57
+ if (effect.nodes !== null) {
58
+ remove_effect_dom(effect.nodes.start, /** @type {TemplateNode} */ (effect.nodes.end));
59
+ effect.nodes = null;
60
60
  }
61
61
 
62
62
  if (value === '') return;
@@ -1,4 +1,4 @@
1
- /** @import { Effect, TemplateNode } from '#client' */
1
+ /** @import { Effect, EffectNodes, TemplateNode } from '#client' */
2
2
  import { FILENAME, NAMESPACE_SVG } from '../../../../constants.js';
3
3
  import {
4
4
  hydrate_next,
@@ -10,7 +10,6 @@ import {
10
10
  import { create_text, get_first_child } from '../operations.js';
11
11
  import { block, teardown } from '../../reactivity/effects.js';
12
12
  import { set_should_intro } from '../../render.js';
13
- import { current_each_item, set_current_each_item } from './each.js';
14
13
  import { active_effect } from '../../runtime.js';
15
14
  import { component_context, dev_stack } from '../../context.js';
16
15
  import { DEV } from 'esm-env';
@@ -18,6 +17,7 @@ import { EFFECT_TRANSPARENT, ELEMENT_NODE } from '#client/constants';
18
17
  import { assign_nodes } from '../template.js';
19
18
  import { is_raw_text_element } from '../../../../utils.js';
20
19
  import { BranchManager } from './branches.js';
20
+ import { set_animation_effect_override } from '../elements/transitions.js';
21
21
 
22
22
  /**
23
23
  * @param {Comment | Element} node
@@ -48,11 +48,10 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio
48
48
  var anchor = /** @type {TemplateNode} */ (hydrating ? hydrate_node : node);
49
49
 
50
50
  /**
51
- * The keyed `{#each ...}` item block, if any, that this element is inside.
52
51
  * We track this so we can set it when changing the element, allowing any
53
52
  * `animate:` directive to bind itself to the correct block
54
53
  */
55
- var each_item_block = current_each_item;
54
+ var parent_effect = /** @type {Effect} */ (active_effect);
56
55
 
57
56
  var branches = new BranchManager(anchor, false);
58
57
 
@@ -67,10 +66,6 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio
67
66
  }
68
67
 
69
68
  branches.ensure(next_tag, (anchor) => {
70
- // See explanation of `each_item_block` above
71
- var previous_each_item = current_each_item;
72
- set_current_each_item(each_item_block);
73
-
74
69
  if (next_tag) {
75
70
  element = hydrating
76
71
  ? /** @type {Element} */ (element)
@@ -112,21 +107,23 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio
112
107
  }
113
108
  }
114
109
 
110
+ set_animation_effect_override(parent_effect);
111
+
115
112
  // `child_anchor` is undefined if this is a void element, but we still
116
113
  // need to call `render_fn` in order to run actions etc. If the element
117
114
  // contains children, it's a user error (which is warned on elsewhere)
118
115
  // and the DOM will be silently discarded
119
116
  render_fn(element, child_anchor);
117
+
118
+ set_animation_effect_override(null);
120
119
  }
121
120
 
122
121
  // we do this after calling `render_fn` so that child effects don't override `nodes.end`
123
- /** @type {Effect} */ (active_effect).nodes_end = element;
122
+ /** @type {Effect & { nodes: EffectNodes }} */ (active_effect).nodes.end = element;
124
123
 
125
124
  anchor.before(element);
126
125
  }
127
126
 
128
- set_current_each_item(previous_each_item);
129
-
130
127
  if (hydrating) {
131
128
  set_hydrate_node(anchor);
132
129
  }