diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eda3003ef..da084600b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Fix reactivity when passing `$$props` to a `` ([#3364](https://github.com/sveltejs/svelte/issues/3364)) * Fix unneeded invalidation of `$$props` and `$$restProps` ([#4993](https://github.com/sveltejs/svelte/issues/4993), [#5118](https://github.com/sveltejs/svelte/issues/5118)) * Provide better compiler error message when mismatched tags are due to autoclosing of tags ([#5049](https://github.com/sveltejs/svelte/issues/5049)) +* Fix `bind:group` when using contextual reference ([#5174](https://github.com/sveltejs/svelte/issues/5174)) ## 3.24.0 diff --git a/src/compiler/compile/nodes/Binding.ts b/src/compiler/compile/nodes/Binding.ts index bcb07280b0..c561e72b99 100644 --- a/src/compiler/compile/nodes/Binding.ts +++ b/src/compiler/compile/nodes/Binding.ts @@ -41,7 +41,8 @@ export default class Binding extends Node { this.raw_expression = JSON.parse(JSON.stringify(info.expression)); const { name } = get_object(this.expression.node); - this.is_contextual = scope.names.has(name); + + this.is_contextual = Array.from(this.expression.references).some(name => scope.names.has(name)); // make sure we track this as a mutable ref if (scope.is_let(name)) { @@ -49,7 +50,7 @@ export default class Binding extends Node { code: 'invalid-binding', message: 'Cannot bind to a variable declared with the let: directive' }); - } else if (this.is_contextual) { + } else if (scope.names.has(name)) { if (scope.is_await(name)) { component.error(this, { code: 'invalid-binding', diff --git a/src/compiler/compile/nodes/shared/Expression.ts b/src/compiler/compile/nodes/shared/Expression.ts index 8bfc2bffce..bbbc1b2f2d 100644 --- a/src/compiler/compile/nodes/shared/Expression.ts +++ b/src/compiler/compile/nodes/shared/Expression.ts @@ -24,7 +24,7 @@ export default class Expression { component: Component; owner: Owner; node: any; - references: Set; + references: Set = new Set(); dependencies: Set = new Set(); contextual_dependencies: Set = new Set(); @@ -50,7 +50,7 @@ export default class Expression { this.template_scope = template_scope; this.owner = owner; - const { dependencies, contextual_dependencies } = this; + const { dependencies, contextual_dependencies, references } = this; let { map, scope } = create_scopes(info); this.scope = scope; @@ -75,6 +75,7 @@ export default class Expression { if (is_reference(node, parent)) { const { name, nodes } = flatten_reference(node); + references.add(name); if (scope.has(name)) return; diff --git a/src/compiler/compile/render_dom/wrappers/shared/mark_each_block_bindings.ts b/src/compiler/compile/render_dom/wrappers/shared/mark_each_block_bindings.ts index 490d52bd9d..884d4b88b8 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/mark_each_block_bindings.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/mark_each_block_bindings.ts @@ -2,7 +2,6 @@ import EachBlock from "../../../nodes/EachBlock"; import InlineComponentWrapper from "../InlineComponent"; import ElementWrapper from "../Element"; import Binding from "../../../nodes/Binding"; -import get_object from "../../../utils/get_object"; export default function mark_each_block_bindings( parent: ElementWrapper | InlineComponentWrapper, @@ -10,9 +9,12 @@ export default function mark_each_block_bindings( ) { // we need to ensure that the each block creates a context including // the list and the index, if they're not otherwise referenced - const object = get_object(binding.expression.node).name; - const each_block = parent.node.scope.get_owner(object); - (each_block as EachBlock).has_binding = true; + binding.expression.references.forEach(name => { + const each_block = parent.node.scope.get_owner(name); + if (each_block) { + (each_block as EachBlock).has_binding = true; + } + }); if (binding.name === "group") { // for ``, we make sure that all the each blocks creates context with `index` diff --git a/test/runtime/samples/binding-input-group-each-6/_config.js b/test/runtime/samples/binding-input-group-each-6/_config.js new file mode 100644 index 0000000000..9eb251bf5d --- /dev/null +++ b/test/runtime/samples/binding-input-group-each-6/_config.js @@ -0,0 +1,87 @@ +export default { + html: ` + + + +

+ + + +

+ + + +

+ `, + + async test({ assert, component, target, window }) { + const inputs = target.querySelectorAll('input'); + assert.equal(inputs[0].checked, false); + assert.equal(inputs[1].checked, false); + assert.equal(inputs[2].checked, false); + + assert.equal(inputs[3].checked, false); + assert.equal(inputs[4].checked, false); + assert.equal(inputs[5].checked, false); + + assert.equal(inputs[6].checked, false); + assert.equal(inputs[7].checked, false); + assert.equal(inputs[8].checked, false); + + const event = new window.Event('change'); + + inputs[2].checked = true; + await inputs[2].dispatchEvent(event); + + assert.htmlEqual(target.innerHTML, ` + + + +

z

+ + + +

+ + + +

+ `); + + inputs[4].checked = true; + await inputs[4].dispatchEvent(event); + + assert.htmlEqual(target.innerHTML, ` + + + +

z

+ + + +

y

+ + + +

+ `); + + inputs[5].checked = true; + await inputs[5].dispatchEvent(event); + + assert.htmlEqual(target.innerHTML, ` + + + +

z

+ + + +

y, z

+ + + +

+ `); + } +}; diff --git a/test/runtime/samples/binding-input-group-each-6/main.svelte b/test/runtime/samples/binding-input-group-each-6/main.svelte new file mode 100644 index 0000000000..85be939e8a --- /dev/null +++ b/test/runtime/samples/binding-input-group-each-6/main.svelte @@ -0,0 +1,22 @@ + + +{#each Object.keys(list) as key} + {#each values as value} + + {/each} +

{list[key].join(', ')}

+{/each}