diff --git a/.changeset/brave-carrots-draw.md b/.changeset/brave-carrots-draw.md new file mode 100644 index 000000000..4e60193fd --- /dev/null +++ b/.changeset/brave-carrots-draw.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: more robust moving of each item nodes diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index 2232206b6..e23286d5b 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -323,7 +323,6 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) { if (matched.length < stashed.length) { // more efficient to move later items to the front var start = stashed[0]; - var local_anchor = start.o; var j; prev = start.prev; @@ -331,18 +330,18 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) { var a = matched[0]; var b = matched[matched.length - 1]; - link(a.prev, b.next); - link(prev, a); - link(b, start); - for (j = 0; j < matched.length; j += 1) { - move(matched[j], local_anchor); + move(matched[j], start, anchor); } for (j = 0; j < stashed.length; j += 1) { seen.delete(stashed[j]); } + link(a.prev, b.next); + link(prev, a); + link(b, start); + current = start; prev = b; i -= 1; @@ -352,7 +351,7 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) { } else { // more efficient to move earlier items to the back seen.delete(item); - move(item, current ? current.o : anchor); + move(item, current, anchor); link(item.prev, item.next); link(item, prev.next); @@ -492,21 +491,19 @@ function create_item(open, anchor, prev, next, value, key, index, render_fn, fla /** * @param {import('#client').EachItem} item + * @param {import('#client').EachItem | null} next * @param {Text | Element | Comment} anchor */ -function move(item, anchor) { - anchor.before(item.o); +function move(item, next, anchor) { + var end = item.next ? item.next.o : anchor; + var dest = next ? next.o : anchor; - var dom = item.e.dom; + var node = /** @type {import('#client').TemplateNode} */ (item.o); - if (dom !== null) { - if (is_array(dom)) { - for (var i = 0; i < dom.length; i++) { - anchor.before(dom[i]); - } - } else { - anchor.before(dom); - } + while (node !== end) { + var next_node = /** @type {import('#client').TemplateNode} */ (node.nextSibling); + dest.before(node); + node = next_node; } } diff --git a/packages/svelte/tests/runtime-runes/samples/each-keyed-child-effect/_config.js b/packages/svelte/tests/runtime-runes/samples/each-keyed-child-effect/_config.js new file mode 100644 index 000000000..d4e13c391 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/each-keyed-child-effect/_config.js @@ -0,0 +1,43 @@ +import { flushSync } from 'svelte'; +import { ok, test } from '../../test'; + +let ascending = ` + +

1

+

(1)

+

2

+

(2)

+

3

+

(3)

+`; + +let descending = ` + +

3

+

(3)

+

2

+

(2)

+

1

+

(1)

+`; + +export default test({ + html: ascending, + + async test({ assert, target }) { + const btn = target.querySelector('button'); + ok(btn); + + flushSync(() => btn.click()); + assert.htmlEqual(target.innerHTML, descending); + + flushSync(() => btn.click()); + assert.htmlEqual(target.innerHTML, ascending); + + flushSync(() => btn.click()); + assert.htmlEqual(target.innerHTML, descending); + + flushSync(() => btn.click()); + assert.htmlEqual(target.innerHTML, ascending); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/each-keyed-child-effect/main.svelte b/packages/svelte/tests/runtime-runes/samples/each-keyed-child-effect/main.svelte new file mode 100644 index 000000000..9c6605084 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/each-keyed-child-effect/main.svelte @@ -0,0 +1,12 @@ + + + + +{#each array as item (item)} +

{item}

+ {#if true} +

({item})

+ {/if} +{/each}