fix: ensure locally mutated bindable props persist with spreading props (#13190)

Fixes #13187. This ensures that we update the local fallback value in the case where the fallback is used so that we persist local changes to bindable props. Otherwise, any incoming changes from the outside will reset the incoming value back to the old fallback value.

---------

Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com>
pull/13204/head
Dominic Gannaway 2 months ago committed by GitHub
parent 25f67df911
commit 363f4a06c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure locally mutated bindable props persist with spreading props

@ -238,11 +238,17 @@ export function prop(props, key, flags, fallback) {
var fallback_value = /** @type {V} */ (fallback);
var fallback_dirty = true;
var fallback_used = false;
var get_fallback = () => {
if (lazy && fallback_dirty) {
fallback_used = true;
if (fallback_dirty) {
fallback_dirty = false;
fallback_value = untrack(/** @type {() => V} */ (fallback));
if (lazy) {
fallback_value = untrack(/** @type {() => V} */ (fallback));
} else {
fallback_value = /** @type {V} */ (fallback);
}
}
return fallback_value;
@ -264,6 +270,7 @@ export function prop(props, key, flags, fallback) {
var value = /** @type {V} */ (props[key]);
if (value === undefined) return get_fallback();
fallback_dirty = true;
fallback_used = false;
return value;
};
} else {
@ -351,6 +358,11 @@ export function prop(props, key, flags, fallback) {
if (!current_value.equals(new_value)) {
from_child = true;
set(inner_current_value, new_value);
// To ensure the fallback value is consistent when used with proxies, we
// update the local fallback_value, but only if the fallback is actively used
if (fallback_used && fallback_value !== undefined) {
fallback_value = new_value;
}
get(current_value); // force a synchronisation immediately
}

@ -0,0 +1,31 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
accessors: false,
test({ assert, target }) {
const [btn1, btn2] = target.querySelectorAll('button');
btn1.click();
flushSync();
assert.htmlEqual(
target.innerHTML,
`<button>set color</button> <button>set options</button> bar bar`
);
btn2.click();
flushSync();
assert.htmlEqual(
target.innerHTML,
`<button>set color</button> <button>set options</button> baz bar`
);
btn1.click();
flushSync();
assert.htmlEqual(
target.innerHTML,
`<button>set color</button> <button>set options</button> foo bar`
);
}
});

@ -0,0 +1,7 @@
<script>
let { options = $bindable('foo') } = $props();
options = 'bar'
</script>
{options}

@ -0,0 +1,24 @@
<script >
import Inner from "./inner.svelte";
let testProps = $state({
color: "red"
});
</script>
<button onclick={() => {
testProps = {
color: "blue"
};
}}>set color</button>
<button onclick={() => {
testProps = {
color: "pink",
options: 'baz'
};
}}>set options</button>
<Inner {...testProps} />
<Inner color={testProps.color} />
Loading…
Cancel
Save