fix: ensure each block inert items are disposed of if the each block is also inert (#13930)

Fixes #13926
pull/14064/head
Dominic Gannaway 5 days ago committed by GitHub
parent e47ee22628
commit 3999fed4ca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure each block inert items are disposed of if the each block is also inert

@ -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<V>, index: number | Source<number>) => 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;

@ -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) {

@ -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,
'<button>Toggle</button><div><div>1</div><div>2</div><div>3</div></div>'
);
const btn1 = target.querySelector('button');
btn1?.click();
flushSync();
raf.tick(250);
assert.htmlEqual(
target.innerHTML,
'<button>Toggle</button><div style="opacity: 0.5;"><div>1</div><div>2</div><div>3</div></div>'
);
await Promise.resolve();
flushSync();
raf.tick(500);
assert.htmlEqual(
target.innerHTML,
'<button>Toggle</button><div style=""><div>3</div><div>4</div></div>'
);
}
});

@ -0,0 +1,28 @@
<script>
function fade(_) {
return {
duration: 500,
css: t => `opacity: ${t}`,
}
}
let toggle = $state(true);
let items = $state([ 1, 2, 3 ]);
const handle_toggle = async () => {
toggle = false;
await Promise.resolve();
items = [3, 4];
toggle = true;
};
</script>
<button onclick={handle_toggle}>Toggle</button>
{#if toggle}
<div transition:fade>
{#each items as item (item)}
<div>{item}</div>
{/each}
</div>
{/if}
Loading…
Cancel
Save