fix: ensure correct each block element is moved during reconcilation

pull/12182/head
Dominic Gannaway 4 months ago
parent 23484d6875
commit 7d62528f7a

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: ensure correct each block element is moved during reconcilation

@ -482,10 +482,11 @@ function create_item(anchor, prev, next, value, key, index, render_fn, flags) {
/** /**
* @param {import('#client').TemplateNode} dom * @param {import('#client').TemplateNode} dom
* @param {import('#client').TemplateNode | null} sibling
* @param {import("#client").Effect} effect * @param {import("#client").Effect} effect
* @returns {import('#client').TemplateNode} * @returns {import('#client').TemplateNode}
*/ */
function get_adjusted_first_node(dom, effect) { function get_adjusted_first_node(dom, sibling, effect) {
if ((dom.nodeType === 3 && /** @type {Text} */ (dom).data === '') || dom.nodeType === 8) { if ((dom.nodeType === 3 && /** @type {Text} */ (dom).data === '') || dom.nodeType === 8) {
var adjusted = effect.first; var adjusted = effect.first;
var next; var next;
@ -498,7 +499,11 @@ function get_adjusted_first_node(dom, effect) {
} }
adjusted = next; adjusted = next;
} }
return get_first_node(/** @type {import("#client").Effect} */ (adjusted)); var adjusted_dom = get_first_node(/** @type {import("#client").Effect} */ (adjusted));
// If we have a sibling that contains the adjusted_dom, then use that instead.
if (sibling?.contains(adjusted_dom)) {
return sibling;
}
} }
return dom; return dom;
} }
@ -511,9 +516,13 @@ function get_adjusted_first_node(dom, effect) {
function get_first_node(effect) { function get_first_node(effect) {
var dom = effect.dom; var dom = effect.dom;
if (is_array(dom)) { if (is_array(dom)) {
return get_adjusted_first_node(dom[0], effect); return get_adjusted_first_node(dom[0], dom[1], effect);
} }
return get_adjusted_first_node(/** @type {import('#client').TemplateNode} **/ (dom), effect); return get_adjusted_first_node(
/** @type {import('#client').TemplateNode} **/ (dom),
null,
effect
);
} }
/** /**

@ -0,0 +1,27 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
html: `<ul><li>test (1) <span style="background-color: red; width: 20px; height: 20px; display: inline-block;"></span></li><li>test 2 (2)</li><li>test 3 (3)</li></ul><button>Swap items 1 &amp; 3</button>`,
async test({ assert, target }) {
const [btn1] = target.querySelectorAll('button');
flushSync(() => {
btn1.click();
});
flushSync(() => {
btn1.click();
});
flushSync(() => {
btn1.click();
});
assert.htmlEqual(
target.innerHTML,
`<ul><li>test (1) <span style="background-color: red; width: 20px; height: 20px; display: inline-block;"></span></li><li>test 2 (2)</li><li>test 3 (3)</li></ul><button>Swap items 1 &amp; 3</button>`
);
}
});

@ -0,0 +1,26 @@
<script>
const items = $state([
{ name: 'test', id: 1, color: 'red' },
{ name: 'test 2', id: 2 },
{ name: 'test 3', id: 3 },
]);
const onclick = () => {
const from = 0;
const to = 2;
items.splice(to, 0, items.splice(from, 1)[0]);
};
</script>
{#snippet renderItem(item)}
<li>
{item.name} ({item.id})
{#if item.color}<span style="background-color: {item.color}; width: 20px; height: 20px; display: inline-block;"></span>{/if}
</li>
{/snippet}
<ul>
{#each items as item (item.id)}
{@render renderItem(item)}
{/each}
</ul>
<button {onclick}>Swap items 1 & 3</button>
Loading…
Cancel
Save