Skip to content

Commit e976d40

Browse files
committed
fix: correctly reconcile each blocks after outroing branches are resumed
1 parent f9c2b9e commit e976d40

File tree

5 files changed

+57
-10
lines changed

5 files changed

+57
-10
lines changed

.changeset/flat-cars-say.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: correctly reconcile each blocks after outroing branches are resumed

packages/svelte/src/internal/client/dom/blocks/each.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -533,11 +533,7 @@ function reconcile(state, array, anchor, flags, get_key) {
533533
stashed = [];
534534

535535
while (current !== null && current.k !== key) {
536-
// If the each block isn't inert and an item has an effect that is already inert,
537-
// skip over adding it to our seen Set as the item is already being handled
538-
if ((current.e.f & INERT) === 0) {
539-
(seen ??= new Set()).add(current);
540-
}
536+
(seen ??= new Set()).add(current);
541537
stashed.push(current);
542538
current = current.next;
543539
}
@@ -570,7 +566,15 @@ function reconcile(state, array, anchor, flags, get_key) {
570566
}
571567

572568
if (current !== null || seen !== undefined) {
573-
var to_destroy = seen === undefined ? [] : array_from(seen);
569+
var to_destroy = [];
570+
571+
if (seen !== undefined) {
572+
for (item of seen) {
573+
if ((item.e.f & INERT) === 0) {
574+
to_destroy.push(item);
575+
}
576+
}
577+
}
574578

575579
while (current !== null) {
576580
// If the each block isn't inert, then inert effects are currently outroing and will be removed once the transition is finished

packages/svelte/tests/runtime-runes/samples/each-updates-12/_config.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@ export default test({
55
async test({ assert, target, raf }) {
66
const [clear, push] = target.querySelectorAll('button');
77

8-
raf.tick(0);
9-
108
flushSync(() => clear.click());
11-
raf.tick(1);
12-
139
flushSync(() => push.click());
1410
raf.tick(500);
1511

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
async test({ assert, target, raf }) {
6+
const [clear, reverse] = target.querySelectorAll('button');
7+
8+
flushSync(() => clear.click());
9+
flushSync(() => reverse.click());
10+
raf.tick(1);
11+
12+
assert.htmlEqual(
13+
target.innerHTML,
14+
`
15+
<button>clear</button>
16+
<button>reverse</button>
17+
<span style="opacity: 1;">c</span>
18+
<span style="opacity: 1;">b</span>
19+
<span style="opacity: 1;">a</span>
20+
`
21+
);
22+
}
23+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<script>
2+
function fade(node) {
3+
return {
4+
duration: 1000,
5+
tick(t) {
6+
node.style.opacity = t;
7+
}
8+
}
9+
}
10+
11+
let items = $state(['a', 'b', 'c']);
12+
</script>
13+
14+
<button onclick={() => items = []}>clear</button>
15+
<button onclick={() => items = ['c', 'b', 'a']}>reverse</button>
16+
17+
{#each items as item (item)}
18+
<span transition:fade={{duration: 1000}}>{item}</span>
19+
{/each}

0 commit comments

Comments
 (0)