From 3999fed4ca30b35f26b9758afd3240614889e8b7 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 31 Oct 2024 11:02:30 +0000 Subject: [PATCH] fix: ensure each block inert items are disposed of if the each block is also inert (#13930) Fixes #13926 --- .changeset/curly-dogs-breathe.md | 5 +++ .../src/internal/client/dom/blocks/each.js | 18 ++++++----- .../src/internal/client/reactivity/effects.js | 5 ++- .../samples/transition-each-3/_config.js | 31 +++++++++++++++++++ .../samples/transition-each-3/main.svelte | 28 +++++++++++++++++ 5 files changed, 78 insertions(+), 9 deletions(-) create mode 100644 .changeset/curly-dogs-breathe.md create mode 100644 packages/svelte/tests/runtime-runes/samples/transition-each-3/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/transition-each-3/main.svelte diff --git a/.changeset/curly-dogs-breathe.md b/.changeset/curly-dogs-breathe.md new file mode 100644 index 0000000000..9409c38141 --- /dev/null +++ b/.changeset/curly-dogs-breathe.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure each block inert items are disposed of if the each block is also inert diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index e10aed3867..0fef7d343b 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -35,7 +35,7 @@ import { source, mutable_source, internal_set } from '../../reactivity/sources.j import { array_from, is_array } from '../../../shared/utils.js'; import { INERT } from '../../constants.js'; import { queue_micro_task } from '../task.js'; -import { active_effect } from '../../runtime.js'; +import { active_effect, active_reaction } from '../../runtime.js'; /** * The row of a keyed each block that is currently updating. We track this @@ -204,7 +204,8 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f } if (!hydrating) { - reconcile(array, state, anchor, render_fn, flags, get_key); + var effect = /** @type {Effect} */ (active_reaction); + reconcile(array, state, anchor, render_fn, flags, (effect.f & INERT) !== 0, get_key); } if (fallback_fn !== null) { @@ -248,10 +249,11 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f * @param {Element | Comment | Text} anchor * @param {(anchor: Node, item: MaybeSource, index: number | Source) => void} render_fn * @param {number} flags + * @param {boolean} is_inert * @param {(value: V, index: number) => any} get_key * @returns {void} */ -function reconcile(array, state, anchor, render_fn, flags, get_key) { +function reconcile(array, state, anchor, render_fn, flags, is_inert, get_key) { var is_animated = (flags & EACH_IS_ANIMATED) !== 0; var should_update = (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0; @@ -390,9 +392,9 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) { stashed = []; while (current !== null && current.k !== key) { - // If the item has an effect that is already inert, skip over adding it - // to our seen Set as the item is already being handled - if ((current.e.f & INERT) === 0) { + // If the each block isn't inert and an item has an effect that is already inert, + // skip over adding it to our seen Set as the item is already being handled + if (is_inert || (current.e.f & INERT) === 0) { (seen ??= new Set()).add(current); } stashed.push(current); @@ -415,8 +417,8 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) { var to_destroy = seen === undefined ? [] : array_from(seen); while (current !== null) { - // Inert effects are currently outroing and will be removed once the transition is finished - if ((current.e.f & INERT) === 0) { + // If the each block isn't inert, then inert effects are currently outroing and will be removed once the transition is finished + if (is_inert || (current.e.f & INERT) === 0) { to_destroy.push(current); } current = current.next; diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index daf28eeec2..66b489cf54 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -577,7 +577,6 @@ export function resume_effect(effect) { */ function resume_children(effect, local) { if ((effect.f & INERT) === 0) return; - effect.f ^= INERT; // If a dependency of this effect changed while it was paused, // apply the change now @@ -585,6 +584,10 @@ function resume_children(effect, local) { update_effect(effect); } + // Ensure we toggle the flag after possibly updating the effect so that + // each block logic can correctly operate on inert items + effect.f ^= INERT; + var child = effect.first; while (child !== null) { diff --git a/packages/svelte/tests/runtime-runes/samples/transition-each-3/_config.js b/packages/svelte/tests/runtime-runes/samples/transition-each-3/_config.js new file mode 100644 index 0000000000..ebacf8e8b8 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/transition-each-3/_config.js @@ -0,0 +1,31 @@ +import { flushSync } from '../../../../src/index-client.js'; +import { test } from '../../test'; + +export default test({ + async test({ assert, raf, target }) { + assert.htmlEqual( + target.innerHTML, + '
1
2
3
' + ); + + const btn1 = target.querySelector('button'); + btn1?.click(); + flushSync(); + raf.tick(250); + + assert.htmlEqual( + target.innerHTML, + '
1
2
3
' + ); + + await Promise.resolve(); + + flushSync(); + raf.tick(500); + + assert.htmlEqual( + target.innerHTML, + '
3
4
' + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/transition-each-3/main.svelte b/packages/svelte/tests/runtime-runes/samples/transition-each-3/main.svelte new file mode 100644 index 0000000000..e2ae300dc9 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/transition-each-3/main.svelte @@ -0,0 +1,28 @@ + + + + +{#if toggle} +
+ {#each items as item (item)} +
{item}
+ {/each} +
+{/if}