Skip to content

Commit 59a96c9

Browse files
committed
support spreading object with get/set methods
1 parent 9c6028b commit 59a96c9

File tree

6 files changed

+73
-30
lines changed

6 files changed

+73
-30
lines changed

packages/svelte/src/compiler/phases/3-transform/client/visitors/BindDirective.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,23 @@ import { is_text_attribute } from '../../../../utils/ast.js';
66
import * as b from '#compiler/builders';
77
import { binding_properties } from '../../../bindings.js';
88
import { build_attribute_value } from './shared/element.js';
9-
import { build_bind_this, validate_binding, handle_spread_binding } from './shared/utils.js';
9+
import { build_bind_this, validate_binding } from './shared/utils.js';
10+
import { handle_spread_binding } from '../../shared/spread_bindings.js';
1011

1112
/**
1213
* @param {AST.BindDirective} node
1314
* @param {ComponentContext} context
1415
*/
1516
export function BindDirective(node, context) {
1617
let get, set;
17-
18+
1819
// Handle SpreadElement by creating a variable declaration before visiting
1920
if (node.expression.type === 'SpreadElement') {
20-
const { get: getter, set: setter } = handle_spread_binding(node.expression, context.state, context.visit);
21+
const { get: getter, set: setter } = handle_spread_binding(
22+
node.expression,
23+
context.state,
24+
context.visit
25+
);
2126
get = getter;
2227
set = setter;
2328
} else {

packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { regex_is_valid_identifier } from '../../../../patterns.js';
99
import is_reference from 'is-reference';
1010
import { dev, is_ignored, locator, component_name } from '../../../../../state.js';
1111
import { build_getter } from '../../utils.js';
12+
import { handle_spread_binding } from '../../../shared/spread_bindings.js';
1213

1314
/**
1415
* A utility for extracting complex expressions (such as call expressions)
@@ -202,25 +203,6 @@ export function parse_directive_name(name) {
202203
return expression;
203204
}
204205

205-
/**
206-
* Handles SpreadElement by creating a variable declaration and returning getter/setter expressions
207-
* @param {SpreadElement} spread_expression
208-
* @param {ComponentClientTransformState} state
209-
* @param {function} visit
210-
* @returns {{get: Expression, set: Expression}}
211-
*/
212-
export function handle_spread_binding(spread_expression, state, visit) {
213-
// Generate a unique variable name for this spread binding
214-
const id = b.id(state.scope.generate('$$bindings'));
215-
216-
const visited_expression = /** @type {Expression} */ (visit(spread_expression.argument));
217-
state.init.push(b.const(id, visited_expression));
218-
219-
const get = b.member(id, b.literal(0), true);
220-
const set = b.member(id, b.literal(1), true);
221-
return { get, set };
222-
}
223-
224206
/**
225207
* Serializes `bind:this` for components and elements.
226208
* @param {Identifier | MemberExpression | SequenceExpression | SpreadElement} expression

packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
is_load_error_element
2222
} from '../../../../../../utils.js';
2323
import { escape_html } from '../../../../../../escaping.js';
24+
import { handle_spread_binding } from '../../../shared/spread_bindings.js';
2425

2526
const WHITESPACE_INSENSITIVE_ATTRIBUTES = ['class', 'style'];
2627

@@ -118,15 +119,23 @@ export function build_element_attributes(node, context) {
118119

119120
let expression = /** @type {Expression} */ (context.visit(attribute.expression));
120121

121-
if (expression.type === 'SequenceExpression') {
122+
// Handle SpreadElement for bind directives
123+
if (attribute.expression.type === 'SpreadElement') {
124+
const { get } = handle_spread_binding(attribute.expression, context.state, context.visit);
125+
expression = b.call(get);
126+
} else if (expression.type === 'SequenceExpression') {
122127
expression = b.call(expression.expressions[0]);
123128
}
124129

125130
if (is_content_editable_binding(attribute.name)) {
126131
content = expression;
127132
} else if (attribute.name === 'value' && node.name === 'textarea') {
128133
content = b.call('$.escape', expression);
129-
} else if (attribute.name === 'group' && attribute.expression.type !== 'SequenceExpression') {
134+
} else if (
135+
attribute.name === 'group' &&
136+
attribute.expression.type !== 'SequenceExpression' &&
137+
attribute.expression.type !== 'SpreadElement'
138+
) {
130139
const value_attribute = /** @type {AST.Attribute | undefined} */ (
131140
node.attributes.find((attr) => attr.type === 'Attribute' && attr.name === 'value')
132141
);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/** @import { Expression, MemberExpression, SequenceExpression, SpreadElement, Literal, Super, UpdateExpression, ExpressionStatement } from 'estree' */
2+
/** @import { ComponentClientTransformState } from '../client/types.js' */
3+
/** @import { ComponentServerTransformState } from '../server/types.js' */
4+
import * as b from '#compiler/builders';
5+
6+
/**
7+
* Handles SpreadElement by creating a variable declaration and returning getter/setter expressions
8+
* @param {SpreadElement} spread_expression
9+
* @param {ComponentClientTransformState | ComponentServerTransformState} state
10+
* @param {function} visit
11+
* @returns {{get: Expression, set: Expression}}
12+
*/
13+
export function handle_spread_binding(spread_expression, state, visit) {
14+
// Generate a unique variable name for this spread binding
15+
const id = b.id(state.scope.generate('$$bindings'));
16+
17+
const visited_expression = /** @type {Expression} */ (visit(spread_expression.argument));
18+
state.init.push(b.const(id, visited_expression));
19+
20+
// Create conditional expressions that work for both arrays and objects
21+
// Array.isArray($$bindings) ? $$bindings[0] : $$bindings.get
22+
const get = b.conditional(
23+
b.call('Array.isArray', id),
24+
b.member(id, b.literal(0), true),
25+
b.member(id, b.id('get'))
26+
);
27+
28+
// Array.isArray($$bindings) ? $$bindings[1] : $$bindings.set
29+
const set = b.conditional(
30+
b.call('Array.isArray', id),
31+
b.member(id, b.literal(1), true),
32+
b.member(id, b.id('set'))
33+
);
34+
35+
return { get, set };
36+
}

packages/svelte/tests/runtime-runes/samples/bind-spread/_config.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,18 @@ export default test({
1010

1111
flushSync();
1212

13-
assert.htmlEqual(target.innerHTML, `<input type="checkbox" >`.repeat(3));
13+
assert.htmlEqual(target.innerHTML, `<input type="checkbox" >`.repeat(4));
1414

1515
// assert.deepEqual(logs, ['b', '2', 'a', '2']);
1616

1717
flushSync(() => {
1818
checkboxes.forEach((checkbox) => checkbox.click());
1919
});
20-
assert.deepEqual(logs, ['getBindings', ...repeatArray(3, ['check', false])]);
20+
assert.deepEqual(logs, [
21+
'getArrayBindings',
22+
'getObjectBindings',
23+
...repeatArray(4, ['check', false])
24+
]);
2125
}
2226
});
2327

packages/svelte/tests/runtime-runes/samples/bind-spread/main.svelte

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,23 @@
99
}
1010
];
1111
12-
function getBindings() {
13-
console.log('getBindings');
12+
function getArrayBindings() {
13+
console.log('getArrayBindings');
1414
return check_bindings;
1515
}
16+
17+
function getObjectBindings() {
18+
console.log('getObjectBindings');
19+
const [get, set] = check_bindings;
20+
return { get, set };
21+
}
1622
</script>
1723

1824

1925
<input type="checkbox" bind:checked={check_bindings[0], check_bindings[1]} />
2026

2127
<input type="checkbox" bind:checked={...check_bindings} />
2228

23-
<!-- <input type="checkbox" bind:checked={...check_bindings} /> -->
24-
<input type="checkbox" bind:checked={...getBindings()} />
29+
<input type="checkbox" bind:checked={...getArrayBindings()} />
30+
31+
<input type="checkbox" bind:checked={...getObjectBindings()} />

0 commit comments

Comments
 (0)