diff --git a/.changeset/silent-rocks-yell.md b/.changeset/silent-rocks-yell.md
new file mode 100644
index 000000000000..115e7724b193
--- /dev/null
+++ b/.changeset/silent-rocks-yell.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: exclude `bind:this` from reactive state validation
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 af10f0a4cd6f..7e6501efe488 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
@@ -2826,9 +2826,18 @@ export const template_visitors = {
BindDirective(node, context) {
const { state, path, visit } = context;
const expression = node.expression;
+ const property = binding_properties[node.name];
if (
expression.type === 'MemberExpression' &&
+ (node.name !== 'this' ||
+ path.some(
+ ({ type }) =>
+ type === 'IfBlock' ||
+ type === 'EachBlock' ||
+ type === 'AwaitBlock' ||
+ type === 'KeyBlock'
+ )) &&
context.state.options.dev &&
context.state.analysis.runes
) {
@@ -2859,8 +2868,7 @@ export const template_visitors = {
/** @type {CallExpression} */
let call_expr;
- const property = binding_properties[node.name];
- if (property && property.event) {
+ if (property?.event) {
call_expr = b.call(
'$.bind_property',
b.literal(node.name),
diff --git a/packages/svelte/src/compiler/phases/bindings.js b/packages/svelte/src/compiler/phases/bindings.js
index eee84473a0c3..6714a6e77d35 100644
--- a/packages/svelte/src/compiler/phases/bindings.js
+++ b/packages/svelte/src/compiler/phases/bindings.js
@@ -15,7 +15,8 @@ export const binding_properties = {
// media
currentTime: {
valid_elements: ['audio', 'video'],
- omit_in_ssr: true
+ omit_in_ssr: true,
+ bidirectional: true
},
duration: {
valid_elements: ['audio', 'video'],
@@ -25,7 +26,8 @@ export const binding_properties = {
focused: {},
paused: {
valid_elements: ['audio', 'video'],
- omit_in_ssr: true
+ omit_in_ssr: true,
+ bidirectional: true
},
buffered: {
valid_elements: ['audio', 'video'],
@@ -41,15 +43,18 @@ export const binding_properties = {
},
volume: {
valid_elements: ['audio', 'video'],
- omit_in_ssr: true
+ omit_in_ssr: true,
+ bidirectional: true
},
muted: {
valid_elements: ['audio', 'video'],
- omit_in_ssr: true
+ omit_in_ssr: true,
+ bidirectional: true
},
playbackRate: {
valid_elements: ['audio', 'video'],
- omit_in_ssr: true
+ omit_in_ssr: true,
+ bidirectional: true
},
seeking: {
valid_elements: ['audio', 'video'],
@@ -124,11 +129,13 @@ export const binding_properties = {
},
scrollX: {
valid_elements: ['svelte:window'],
- omit_in_ssr: true
+ omit_in_ssr: true,
+ bidirectional: true
},
scrollY: {
valid_elements: ['svelte:window'],
- omit_in_ssr: true
+ omit_in_ssr: true,
+ bidirectional: true
},
online: {
valid_elements: ['svelte:window'],
@@ -180,23 +187,28 @@ export const binding_properties = {
omit_in_ssr: true // no corresponding attribute
},
checked: {
- valid_elements: ['input']
+ valid_elements: ['input'],
+ bidirectional: true
},
group: {
- valid_elements: ['input']
+ valid_elements: ['input'],
+ bidirectional: true
},
// various
this: {
omit_in_ssr: true
},
innerText: {
- invalid_elements: ['svelte:window', 'svelte:document']
+ invalid_elements: ['svelte:window', 'svelte:document'],
+ bidirectional: true
},
innerHTML: {
- invalid_elements: ['svelte:window', 'svelte:document']
+ invalid_elements: ['svelte:window', 'svelte:document'],
+ bidirectional: true
},
textContent: {
- invalid_elements: ['svelte:window', 'svelte:document']
+ invalid_elements: ['svelte:window', 'svelte:document'],
+ bidirectional: true
},
open: {
event: 'toggle',
@@ -204,10 +216,12 @@ export const binding_properties = {
valid_elements: ['details']
},
value: {
- valid_elements: ['input', 'textarea', 'select']
+ valid_elements: ['input', 'textarea', 'select'],
+ bidirectional: true
},
files: {
valid_elements: ['input'],
- omit_in_ssr: true
+ omit_in_ssr: true,
+ bidirectional: true
}
};
diff --git a/packages/svelte/tests/runtime-runes/samples/binding-property-static/_config.js b/packages/svelte/tests/runtime-runes/samples/binding-property-static/_config.js
index 67bdc497a4d8..7202aadfa431 100644
--- a/packages/svelte/tests/runtime-runes/samples/binding-property-static/_config.js
+++ b/packages/svelte/tests/runtime-runes/samples/binding-property-static/_config.js
@@ -7,10 +7,11 @@ export default test({
async test({ assert, warnings }) {
assert.deepEqual(warnings, [
- `\`bind:value={pojo.value}\` (main.svelte:50:7) is binding to a non-reactive property`,
- `\`bind:value={frozen.value}\` (main.svelte:51:7) is binding to a non-reactive property`,
- `\`bind:value={pojo.value}\` (main.svelte:52:7) is binding to a non-reactive property`,
- `\`bind:value={frozen.value}\` (main.svelte:53:7) is binding to a non-reactive property`
+ '`bind:value={pojo.value}` (main.svelte:50:7) is binding to a non-reactive property',
+ '`bind:value={frozen.value}` (main.svelte:51:7) is binding to a non-reactive property',
+ '`bind:value={pojo.value}` (main.svelte:52:7) is binding to a non-reactive property',
+ '`bind:value={frozen.value}` (main.svelte:53:7) is binding to a non-reactive property',
+ '`bind:this={pojo.value}` (main.svelte:55:6) is binding to a non-reactive property'
]);
}
});
diff --git a/packages/svelte/tests/runtime-runes/samples/binding-property-static/main.svelte b/packages/svelte/tests/runtime-runes/samples/binding-property-static/main.svelte
index c07062e3b27d..3195aa35eaf5 100644
--- a/packages/svelte/tests/runtime-runes/samples/binding-property-static/main.svelte
+++ b/packages/svelte/tests/runtime-runes/samples/binding-property-static/main.svelte
@@ -51,6 +51,9 @@