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 @@ +{#if value} +
+{/if} @@ -59,3 +62,4 @@ +