diff --git a/.changeset/three-donkeys-jump.md b/.changeset/three-donkeys-jump.md new file mode 100644 index 000000000000..6cdc6c75561e --- /dev/null +++ b/.changeset/three-donkeys-jump.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: remove `$.unwrap` calls from `bind:group` diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index aa1e43196f34..aac76faa6085 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -1547,6 +1547,13 @@ const common_visitors = { }, RenderTag(node, context) { context.next({ ...context.state, render_tag: node }); + }, + EachBlock(node) { + if (node.key) { + // treat `{#each items as item, i (i)}` as a normal indexed block, everything else as keyed + node.metadata.keyed = + node.key.type !== 'Identifier' || !node.index || node.key.name !== node.index; + } } }; 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 cf0fd231e5fb..ca78a33a1c6e 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 @@ -2409,10 +2409,7 @@ export const template_visitors = { let flags = 0; - if ( - node.key && - (node.key.type !== 'Identifier' || !node.index || node.key.name !== node.index) - ) { + if (node.metadata.keyed) { flags |= EACH_KEYED; if (node.index) { @@ -2421,7 +2418,7 @@ export const template_visitors = { // In runes mode, if key === item, we don't need to wrap the item in a source const key_is_item = - node.key.type === 'Identifier' && + /** @type {Expression} */ (node.key).type === 'Identifier' && node.context.type === 'Identifier' && node.context.name === node.key.name; @@ -2614,8 +2611,11 @@ export const template_visitors = { /** @type {Expression} */ let key_function = b.id('$.index'); - if (node.key) { - const expression = /** @type {Expression} */ (context.visit(node.key, key_state)); + if (node.metadata.keyed) { + const expression = /** @type {Expression} */ ( + context.visit(/** @type {Expression} */ (node.key), key_state) + ); + key_function = b.arrow([node.context, index], expression); } @@ -3049,11 +3049,12 @@ export const template_visitors = { call_expr = b.call(`$.bind_focused`, state.node, setter); break; case 'group': { - /** @type {CallExpression[]} */ - const indexes = []; - for (const parent_each_block of node.metadata.parent_each_blocks) { - indexes.push(b.call('$.unwrap', parent_each_block.metadata.index)); - } + const indexes = node.metadata.parent_each_blocks.map((each) => { + // if we have a keyed block with an index, the index is wrapped in a source + return each.metadata.keyed && each.index + ? b.call('$.get', each.metadata.index) + : each.metadata.index; + }); // We need to additionally invoke the value attribute signal to register it as a dependency, // so that when the value is updated, the group binding is updated diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js index 1ba9851ee3c9..167f61e0345a 100644 --- a/packages/svelte/src/compiler/phases/scope.js +++ b/packages/svelte/src/compiler/phases/scope.js @@ -592,6 +592,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { } node.metadata = { + keyed: false, contains_group_binding: false, array_name: needs_array_deduplication ? state.scope.root.unique('$$array') : null, index: scope.root.unique('$$index'), diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index ec35a08227f6..e5bc072c1ad8 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -393,6 +393,7 @@ export interface EachBlock extends BaseNode { index?: string; key?: Expression; metadata: { + keyed: boolean; contains_group_binding: boolean; /** Set if something in the array expression is shadowed within the each block */ array_name: Identifier | null; diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/input.js b/packages/svelte/src/internal/client/dom/elements/bindings/input.js index ca83d90f2014..35dd229d39ff 100644 --- a/packages/svelte/src/internal/client/dom/elements/bindings/input.js +++ b/packages/svelte/src/internal/client/dom/elements/bindings/input.js @@ -73,13 +73,8 @@ export function bind_group(inputs, group_index, input, get_value, update) { if (group_index !== null) { for (var index of group_index) { - var group = binding_group; - // @ts-ignore - binding_group = group[index]; - if (binding_group === undefined) { - // @ts-ignore - binding_group = group[index] = []; - } + // @ts-expect-error + binding_group = binding_group[index] ??= []; } } diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 9de214c84798..d9537235f60d 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -1845,6 +1845,7 @@ declare module 'svelte/compiler' { index?: string; key?: Expression; metadata: { + keyed: boolean; contains_group_binding: boolean; /** Set if something in the array expression is shadowed within the each block */ array_name: Identifier | null;