diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
index da2502b6f5..90b2a36f95 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
@@ -363,7 +363,12 @@ export function client_component(source, analysis, options) {
}
if (analysis.uses_props || analysis.uses_rest_props) {
- const to_remove = [b.literal('children'), b.literal('$$slots'), b.literal('$$events')];
+ const to_remove = [
+ b.literal('children'),
+ b.literal('$$slots'),
+ b.literal('$$events'),
+ b.literal('$$legacy')
+ ];
if (analysis.custom_element) {
to_remove.push(b.literal('$$host'));
}
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js
index 884a7addfa..4743edef5e 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js
@@ -89,7 +89,7 @@ export function serialize_get_binding(node, state) {
}
if (binding.kind === 'prop' || binding.kind === 'bindable_prop') {
- if (!state.analysis.runes || binding.reassigned || binding.initial) {
+ if (!state.analysis.runes || binding.reassigned || binding.initial || binding.mutated) {
return b.call(node);
}
@@ -391,7 +391,7 @@ export function serialize_set_binding(node, context, fallback, prefix, options)
),
b.call('$.untrack', b.id('$' + left_name))
);
- } else if (!state.analysis.runes) {
+ } else if (!state.analysis.runes || (binding.mutated && binding.kind === 'bindable_prop')) {
if (binding.kind === 'bindable_prop') {
return b.call(
left,
@@ -402,7 +402,8 @@ export function serialize_set_binding(node, context, fallback, prefix, options)
value
),
b.call(left)
- ])
+ ]),
+ b.true
);
} else {
return b.call(
@@ -539,6 +540,7 @@ function get_hoistable_params(node, context) {
// If we are referencing a simple $$props value, then we need to reference the object property instead
(binding.kind === 'prop' || binding.kind === 'bindable_prop') &&
!binding.reassigned &&
+ !binding.mutated && // this can be removed once legacy mode is gone
binding.initial === null &&
!context.state.analysis.accessors
) {
@@ -602,7 +604,9 @@ export function get_prop_source(binding, state, name, initial) {
if (
state.analysis.accessors ||
- (state.analysis.immutable ? binding.reassigned : binding.mutated)
+ (state.analysis.immutable
+ ? binding.reassigned || (state.analysis.runes && binding.mutated)
+ : binding.mutated)
) {
flags |= PROPS_IS_UPDATED;
}
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js
index b35e01b2d7..94347a7111 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js
@@ -240,7 +240,11 @@ export const javascript_visitors_runes = {
assert.equal(declarator.id.type, 'ObjectPattern');
/** @type {string[]} */
- const seen = [];
+ const seen = ['$$slots', '$$events', '$$legacy'];
+
+ if (state.analysis.custom_element) {
+ seen.push('$$host');
+ }
for (const property of declarator.id.properties) {
if (property.type === 'Property') {
@@ -264,7 +268,9 @@ export const javascript_visitors_runes = {
initial = b.call('$.proxy', initial);
}
- if (binding.reassigned || state.analysis.accessors || initial) {
+ // Until legacy mode is gone, we also need to use the prop source when only mutated is true,
+ // because the parent could be a legacy component which needs coarse-grained reactivity
+ if (binding.reassigned || binding.mutated || state.analysis.accessors || initial) {
declarations.push(b.declarator(id, get_prop_source(binding, state, name, initial)));
}
} else {
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 e7d01f33db..3134409b85 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
@@ -900,6 +900,10 @@ function serialize_inline_component(node, component_name, context) {
push_prop(b.init('$$slots', b.object(serialized_slots)));
}
+ if (!context.state.analysis.runes) {
+ push_prop(b.init('$$legacy', b.true));
+ }
+
const props_expression =
props_and_spreads.length === 0 ||
(props_and_spreads.length === 1 && Array.isArray(props_and_spreads[0]))
diff --git a/packages/svelte/src/internal/client/reactivity/props.js b/packages/svelte/src/internal/client/reactivity/props.js
index 5d4634f913..f57a9f448d 100644
--- a/packages/svelte/src/internal/client/reactivity/props.js
+++ b/packages/svelte/src/internal/client/reactivity/props.js
@@ -267,9 +267,16 @@ export function prop(props, key, flags, fallback) {
// intermediate mode — prop is written to, but the parent component had
// `bind:foo` which means we can just call `$$props.foo = value` directly
if (setter) {
- return function (/** @type {V} */ value) {
- if (arguments.length === 1) {
- /** @type {Function} */ (setter)(value);
+ var legacy_parent = props.$$legacy;
+ return function (/** @type {V} */ value, /** @type {boolean} */ mutation) {
+ if (arguments.length > 0) {
+ // We don't want to notify if the value was mutated and the parent is in runes mode.
+ // In that case the state proxy (if it exists) should take care of the notification.
+ // If the parent is not in runes mode, we need to notify on mutation, too, that the prop
+ // has changed because the parent will not be able to detect the change otherwise.
+ if (!runes || !mutation || legacy_parent) {
+ /** @type {Function} */ (setter)(value);
+ }
return value;
} else {
return getter();
diff --git a/packages/svelte/tests/runtime-runes/samples/binding-interop/Component1.svelte b/packages/svelte/tests/runtime-runes/samples/binding-interop/Component1.svelte
new file mode 100644
index 0000000000..9a21ff4a1a
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/binding-interop/Component1.svelte
@@ -0,0 +1,9 @@
+
+
+{#if primitive}
+
+{:else}
+
+{/if}
diff --git a/packages/svelte/tests/runtime-runes/samples/binding-interop/Component2.svelte b/packages/svelte/tests/runtime-runes/samples/binding-interop/Component2.svelte
new file mode 100644
index 0000000000..9f9724c9a4
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/binding-interop/Component2.svelte
@@ -0,0 +1,9 @@
+
+
+{#if primitive}
+
+{:else}
+
+{/if}
diff --git a/packages/svelte/tests/runtime-runes/samples/binding-interop/Legacy.svelte b/packages/svelte/tests/runtime-runes/samples/binding-interop/Legacy.svelte
new file mode 100644
index 0000000000..765355fbf3
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/binding-interop/Legacy.svelte
@@ -0,0 +1,24 @@
+