diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/BindDirective.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/BindDirective.js
index 56792d8dac..74b3978f5d 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/BindDirective.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/BindDirective.js
@@ -6,7 +6,8 @@ import { is_text_attribute } from '../../../../utils/ast.js';
import * as b from '#compiler/builders';
import { binding_properties } from '../../../bindings.js';
import { build_attribute_value } from './shared/element.js';
-import { build_bind_this, validate_binding, handle_spread_binding } from './shared/utils.js';
+import { build_bind_this, validate_binding } from './shared/utils.js';
+import { handle_spread_binding } from '../../shared/spread_bindings.js';
/**
* @param {AST.BindDirective} node
@@ -14,10 +15,14 @@ import { build_bind_this, validate_binding, handle_spread_binding } from './shar
*/
export function BindDirective(node, context) {
let get, set;
-
+
// Handle SpreadElement by creating a variable declaration before visiting
if (node.expression.type === 'SpreadElement') {
- const { get: getter, set: setter } = handle_spread_binding(node.expression, context.state, context.visit);
+ const { get: getter, set: setter } = handle_spread_binding(
+ node.expression,
+ context.state,
+ context.visit
+ );
get = getter;
set = setter;
} else {
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js
index bac446cc0c..83d0a49959 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js
@@ -9,6 +9,7 @@ import { regex_is_valid_identifier } from '../../../../patterns.js';
import is_reference from 'is-reference';
import { dev, is_ignored, locator, component_name } from '../../../../../state.js';
import { build_getter } from '../../utils.js';
+import { handle_spread_binding } from '../../../shared/spread_bindings.js';
/**
* A utility for extracting complex expressions (such as call expressions)
@@ -202,25 +203,6 @@ export function parse_directive_name(name) {
return expression;
}
-/**
- * Handles SpreadElement by creating a variable declaration and returning getter/setter expressions
- * @param {SpreadElement} spread_expression
- * @param {ComponentClientTransformState} state
- * @param {function} visit
- * @returns {{get: Expression, set: Expression}}
- */
-export function handle_spread_binding(spread_expression, state, visit) {
- // Generate a unique variable name for this spread binding
- const id = b.id(state.scope.generate('$$bindings'));
-
- const visited_expression = /** @type {Expression} */ (visit(spread_expression.argument));
- state.init.push(b.const(id, visited_expression));
-
- const get = b.member(id, b.literal(0), true);
- const set = b.member(id, b.literal(1), true);
- return { get, set };
-}
-
/**
* Serializes `bind:this` for components and elements.
* @param {Identifier | MemberExpression | SequenceExpression | SpreadElement} expression
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js
index 7207564ef9..0ce26571e5 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js
@@ -21,6 +21,7 @@ import {
is_load_error_element
} from '../../../../../../utils.js';
import { escape_html } from '../../../../../../escaping.js';
+import { handle_spread_binding } from '../../../shared/spread_bindings.js';
const WHITESPACE_INSENSITIVE_ATTRIBUTES = ['class', 'style'];
@@ -118,7 +119,11 @@ export function build_element_attributes(node, context) {
let expression = /** @type {Expression} */ (context.visit(attribute.expression));
- if (expression.type === 'SequenceExpression') {
+ // Handle SpreadElement for bind directives
+ if (attribute.expression.type === 'SpreadElement') {
+ const { get } = handle_spread_binding(attribute.expression, context.state, context.visit);
+ expression = b.call(get);
+ } else if (expression.type === 'SequenceExpression') {
expression = b.call(expression.expressions[0]);
}
@@ -126,7 +131,11 @@ export function build_element_attributes(node, context) {
content = expression;
} else if (attribute.name === 'value' && node.name === 'textarea') {
content = b.call('$.escape', expression);
- } else if (attribute.name === 'group' && attribute.expression.type !== 'SequenceExpression') {
+ } else if (
+ attribute.name === 'group' &&
+ attribute.expression.type !== 'SequenceExpression' &&
+ attribute.expression.type !== 'SpreadElement'
+ ) {
const value_attribute = /** @type {AST.Attribute | undefined} */ (
node.attributes.find((attr) => attr.type === 'Attribute' && attr.name === 'value')
);
diff --git a/packages/svelte/src/compiler/phases/3-transform/shared/spread_bindings.js b/packages/svelte/src/compiler/phases/3-transform/shared/spread_bindings.js
new file mode 100644
index 0000000000..01afe26b4b
--- /dev/null
+++ b/packages/svelte/src/compiler/phases/3-transform/shared/spread_bindings.js
@@ -0,0 +1,36 @@
+/** @import { Expression, MemberExpression, SequenceExpression, SpreadElement, Literal, Super, UpdateExpression, ExpressionStatement } from 'estree' */
+/** @import { ComponentClientTransformState } from '../client/types.js' */
+/** @import { ComponentServerTransformState } from '../server/types.js' */
+import * as b from '#compiler/builders';
+
+/**
+ * Handles SpreadElement by creating a variable declaration and returning getter/setter expressions
+ * @param {SpreadElement} spread_expression
+ * @param {ComponentClientTransformState | ComponentServerTransformState} state
+ * @param {function} visit
+ * @returns {{get: Expression, set: Expression}}
+ */
+export function handle_spread_binding(spread_expression, state, visit) {
+ // Generate a unique variable name for this spread binding
+ const id = b.id(state.scope.generate('$$bindings'));
+
+ const visited_expression = /** @type {Expression} */ (visit(spread_expression.argument));
+ state.init.push(b.const(id, visited_expression));
+
+ // Create conditional expressions that work for both arrays and objects
+ // Array.isArray($$bindings) ? $$bindings[0] : $$bindings.get
+ const get = b.conditional(
+ b.call('Array.isArray', id),
+ b.member(id, b.literal(0), true),
+ b.member(id, b.id('get'))
+ );
+
+ // Array.isArray($$bindings) ? $$bindings[1] : $$bindings.set
+ const set = b.conditional(
+ b.call('Array.isArray', id),
+ b.member(id, b.literal(1), true),
+ b.member(id, b.id('set'))
+ );
+
+ return { get, set };
+}
diff --git a/packages/svelte/tests/runtime-runes/samples/bind-spread/_config.js b/packages/svelte/tests/runtime-runes/samples/bind-spread/_config.js
index ae971acaf8..d2b13c9b19 100644
--- a/packages/svelte/tests/runtime-runes/samples/bind-spread/_config.js
+++ b/packages/svelte/tests/runtime-runes/samples/bind-spread/_config.js
@@ -10,14 +10,18 @@ export default test({
flushSync();
- assert.htmlEqual(target.innerHTML, ``.repeat(3));
+ assert.htmlEqual(target.innerHTML, ``.repeat(4));
// assert.deepEqual(logs, ['b', '2', 'a', '2']);
flushSync(() => {
checkboxes.forEach((checkbox) => checkbox.click());
});
- assert.deepEqual(logs, ['getBindings', ...repeatArray(3, ['check', false])]);
+ assert.deepEqual(logs, [
+ 'getArrayBindings',
+ 'getObjectBindings',
+ ...repeatArray(4, ['check', false])
+ ]);
}
});
diff --git a/packages/svelte/tests/runtime-runes/samples/bind-spread/main.svelte b/packages/svelte/tests/runtime-runes/samples/bind-spread/main.svelte
index 13646ef83a..d2cb8491e6 100644
--- a/packages/svelte/tests/runtime-runes/samples/bind-spread/main.svelte
+++ b/packages/svelte/tests/runtime-runes/samples/bind-spread/main.svelte
@@ -9,10 +9,16 @@
}
];
- function getBindings() {
- console.log('getBindings');
+ function getArrayBindings() {
+ console.log('getArrayBindings');
return check_bindings;
}
+
+ function getObjectBindings() {
+ console.log('getObjectBindings');
+ const [get, set] = check_bindings;
+ return { get, set };
+ }
@@ -20,5 +26,6 @@
-
-
+
+
+