From b448a62823c8fc1884461850659f113cfd2c6bbc Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Mon, 16 Mar 2026 21:19:25 +0100 Subject: [PATCH] fix: resume inert effects when they come from offscreen The offscreen branch was missing the "resume inert effects" logic that was just below; it never reached that because of the early continue. Fixes #17851 --- .changeset/fair-hands-relate.md | 5 +++ .../src/internal/client/dom/blocks/each.js | 8 +++++ .../async-each-await-stale-rows/_config.js | 32 +++++++++++++++++++ .../async-each-await-stale-rows/main.svelte | 28 ++++++++++++++++ 4 files changed, 73 insertions(+) create mode 100644 .changeset/fair-hands-relate.md create mode 100644 packages/svelte/tests/runtime-runes/samples/async-each-await-stale-rows/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-each-await-stale-rows/main.svelte diff --git a/.changeset/fair-hands-relate.md b/.changeset/fair-hands-relate.md new file mode 100644 index 0000000000..fd7b3585ad --- /dev/null +++ b/.changeset/fair-hands-relate.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: resume inert effects when they come from offscreen diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index b248ce5544..93c1e2f155 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -500,6 +500,14 @@ function reconcile(state, array, anchor, flags, get_key) { move(effect, next, anchor); prev = effect; + if ((effect.f & INERT) !== 0) { + resume_effect(effect); + if (is_animated) { + effect.nodes?.a?.unfix(); + (to_animate ??= new Set()).delete(effect); + } + } + matched = []; stashed = []; diff --git a/packages/svelte/tests/runtime-runes/samples/async-each-await-stale-rows/_config.js b/packages/svelte/tests/runtime-runes/samples/async-each-await-stale-rows/_config.js new file mode 100644 index 0000000000..7025e4000e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-each-await-stale-rows/_config.js @@ -0,0 +1,32 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + const spam = /** @type {HTMLButtonElement} */ (target.querySelector('button.spam')); + const resolve = /** @type {HTMLButtonElement} */ (target.querySelector('button.resolve')); + + resolve.click(); + await tick(); + + for (let i = 0; i < 5; i += 1) { + spam.click(); + await tick(); + } + + for (let i = 0; i < 5; i += 1) { + resolve.click(); + await tick(); + } + + assert.equal(target.querySelectorAll('div').length, 1); + assert.htmlEqual( + target.innerHTML, + ` + + +
5
+ ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-each-await-stale-rows/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-each-await-stale-rows/main.svelte new file mode 100644 index 0000000000..aeed4fa306 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-each-await-stale-rows/main.svelte @@ -0,0 +1,28 @@ + + + + + + + {#each [value.id] as s (s)} + {await wait()} +
{s}
+ {/each} + + {#snippet pending()} +

pending

+ {/snippet} +