diff --git a/.changeset/olive-shirts-complain.md b/.changeset/olive-shirts-complain.md new file mode 100644 index 0000000000..56387284db --- /dev/null +++ b/.changeset/olive-shirts-complain.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: react to mutated slot props in legacy mode diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index 1e11ac0162..ae7cbbec2b 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -2886,7 +2886,12 @@ export const template_visitors = { const name = node.expression === null ? node.name : node.expression.name; return b.const( name, - b.call('$.derived', b.thunk(b.member(b.id('$$slotProps'), b.id(node.name)))) + b.call( + // in legacy mode, sources can be mutated but they're not fine-grained. + // Using the safe-equal derived version ensures the slot is still updated + state.analysis.runes ? '$.derived' : '$.derived_safe_equal', + b.thunk(b.member(b.id('$$slotProps'), b.id(node.name))) + ) ); } }, diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 97804e11ce..dc117a26d3 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -1307,6 +1307,18 @@ export function derived(init) { return signal; } +/** + * @template V + * @param {() => V} init + * @returns {import('./types.js').ComputationSignal} + */ +/*#__NO_SIDE_EFFECTS__*/ +export function derived_safe_equal(init) { + const signal = derived(init); + signal.e = safe_equal; + return signal; +} + /** * @template V * @param {V} initial_value diff --git a/packages/svelte/src/internal/index.js b/packages/svelte/src/internal/index.js index 709ec9dee9..3d6fd591cc 100644 --- a/packages/svelte/src/internal/index.js +++ b/packages/svelte/src/internal/index.js @@ -7,6 +7,7 @@ export { source, mutable_source, derived, + derived_safe_equal, prop, user_effect, render_effect, diff --git a/packages/svelte/tests/runtime-legacy/samples/component-slot-let-mutated/Nested.svelte b/packages/svelte/tests/runtime-legacy/samples/component-slot-let-mutated/Nested.svelte new file mode 100644 index 0000000000..e1a46088ef --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/component-slot-let-mutated/Nested.svelte @@ -0,0 +1,9 @@ + + +
+ {#each things as thing} + + {/each} +
diff --git a/packages/svelte/tests/runtime-legacy/samples/component-slot-let-mutated/_config.js b/packages/svelte/tests/runtime-legacy/samples/component-slot-let-mutated/_config.js new file mode 100644 index 0000000000..165bbbf586 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/component-slot-let-mutated/_config.js @@ -0,0 +1,25 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: ` + +
+ hello +
+ `, + + async test({ assert, target }) { + target.querySelector('button')?.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + +
+ bye +
+ ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-legacy/samples/component-slot-let-mutated/main.svelte b/packages/svelte/tests/runtime-legacy/samples/component-slot-let-mutated/main.svelte new file mode 100644 index 0000000000..9f9eafcd7e --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/component-slot-let-mutated/main.svelte @@ -0,0 +1,10 @@ + + + + + {thing.text} +