diff --git a/CHANGELOG.md b/CHANGELOG.md index 65f6cf75fd..ed864b216f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +* Fix checkbox `bind:group` in nested `{#each}` contexts ([#5811](https://github.com/sveltejs/svelte/issues/5811)) * Add graphics roles as known ARIA roles ([#5822](https://github.com/sveltejs/svelte/pull/5822)) ## 3.31.0 diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index 1352464d4e..c511bb0b95 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -39,6 +39,7 @@ export default class Block { dependencies: Set = new Set(); bindings: Map; + binding_group_initialised: Set = new Set(); chunks: { declarations: Array; diff --git a/src/compiler/compile/render_dom/Renderer.ts b/src/compiler/compile/render_dom/Renderer.ts index d6de977330..be3feed143 100644 --- a/src/compiler/compile/render_dom/Renderer.ts +++ b/src/compiler/compile/render_dom/Renderer.ts @@ -32,7 +32,7 @@ export default class Renderer { blocks: Array = []; readonly: Set = new Set(); meta_bindings: Array = []; // initial values for e.g. window.innerWidth, if there's a meta tag - binding_groups: Map Node; is_context: boolean; contexts: string[]; index: number }> = new Map(); + binding_groups: Map Node; is_context: boolean; contexts: string[]; index: number; keypath: string }> = new Map(); block: Block; fragment: FragmentWrapper; diff --git a/src/compiler/compile/render_dom/wrappers/Element/Binding.ts b/src/compiler/compile/render_dom/wrappers/Element/Binding.ts index de070aa5c9..7c8a339d00 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/Binding.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/Binding.ts @@ -116,11 +116,11 @@ export default class BindingWrapper { switch (this.node.name) { case 'group': { - const { binding_group, is_context, contexts, index } = get_binding_group(parent.renderer, this.node, block); + const { binding_group, is_context, contexts, index, keypath } = get_binding_group(parent.renderer, this.node, block); block.renderer.add_to_context('$$binding_groups'); - if (is_context) { + if (is_context && !block.binding_group_initialised.has(keypath)) { if (contexts.length > 1) { let binding_group = x`${block.renderer.reference('$$binding_groups')}[${index}]`; for (const name of contexts.slice(0, -1)) { @@ -133,6 +133,7 @@ export default class BindingWrapper { block.chunks.init.push( b`${binding_group(true)} = [];` ); + block.binding_group_initialised.add(keypath); } block.chunks.hydrate.push( @@ -257,8 +258,22 @@ function get_binding_group(renderer: Renderer, value: Binding, block: Block) { let keypath = parts.join('.'); const contexts = []; - + const contextual_dependencies = new Set(); + const { template_scope } = value.expression; + const add_contextual_dependency = (dep: string) => { + contextual_dependencies.add(dep); + const owner = template_scope.get_owner(dep); + if (owner.type === 'EachBlock') { + for (const dep of owner.expression.contextual_dependencies) { + add_contextual_dependency(dep); + } + } + }; for (const dep of value.expression.contextual_dependencies) { + add_contextual_dependency(dep); + } + + for (const dep of contextual_dependencies) { const context = block.bindings.get(dep); let key; let name; @@ -302,7 +317,8 @@ function get_binding_group(renderer: Renderer, value: Binding, block: Block) { }, is_context: contexts.length > 0, contexts, - index + index, + keypath }); } 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 76a594c09c..df7185bb69 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 @@ -17,10 +17,18 @@ export default function mark_each_block_bindings( }); if (binding.name === 'group') { + const add_index_binding = (name: string) => { + const each_block = parent.node.scope.get_owner(name); + if (each_block.type === 'EachBlock') { + each_block.has_index_binding = true; + for (const dep of each_block.expression.contextual_dependencies) { + add_index_binding(dep); + } + } + }; // for ``, we make sure that all the each blocks creates context with `index` for (const name of binding.expression.contextual_dependencies) { - const each_block = parent.node.scope.get_owner(name); - (each_block as EachBlock).has_index_binding = true; + add_index_binding(name); } } } diff --git a/test/runtime/samples/binding-input-group-each-7/_config.js b/test/runtime/samples/binding-input-group-each-7/_config.js new file mode 100644 index 0000000000..3063141983 --- /dev/null +++ b/test/runtime/samples/binding-input-group-each-7/_config.js @@ -0,0 +1,53 @@ +export default { + html: ` + + + + + + + + + + + + + + + + + + + `, + + async test({ assert, component, target, window }) { + const inputs = target.querySelectorAll('input'); + const checked = new Set(); + const checkInbox = async (i) => { + checked.add(i); + inputs[i].checked = true; + await inputs[i].dispatchEvent(event); + }; + + for (let i = 0; i < 18; i++) { + assert.equal(inputs[i].checked, checked.has(i)); + } + + const event = new window.Event('change'); + + await checkInbox(2); + for (let i = 0; i < 18; i++) { + assert.equal(inputs[i].checked, checked.has(i)); + } + + await checkInbox(12); + for (let i = 0; i < 18; i++) { + assert.equal(inputs[i].checked, checked.has(i)); + } + + await checkInbox(8); + for (let i = 0; i < 18; i++) { + assert.equal(inputs[i].checked, checked.has(i)); + } + } +}; diff --git a/test/runtime/samples/binding-input-group-each-7/main.svelte b/test/runtime/samples/binding-input-group-each-7/main.svelte new file mode 100644 index 0000000000..6680a49f32 --- /dev/null +++ b/test/runtime/samples/binding-input-group-each-7/main.svelte @@ -0,0 +1,15 @@ + + +{#each list as { id, data }} + {#each data as item} + + + + {/each} +{/each}