support spreading object with get/set methods

pull/16607/head
Jack Goodall 1 month ago
parent 9c6028b10f
commit 59a96c9a97

@ -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 {

@ -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

@ -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')
);

@ -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 };
}

@ -10,14 +10,18 @@ export default test({
flushSync();
assert.htmlEqual(target.innerHTML, `<input type="checkbox" >`.repeat(3));
assert.htmlEqual(target.innerHTML, `<input type="checkbox" >`.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])
]);
}
});

@ -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 };
}
</script>
@ -20,5 +26,6 @@
<input type="checkbox" bind:checked={...check_bindings} />
<!-- <input type="checkbox" bind:checked={...check_bindings} /> -->
<input type="checkbox" bind:checked={...getBindings()} />
<input type="checkbox" bind:checked={...getArrayBindings()} />
<input type="checkbox" bind:checked={...getObjectBindings()} />

Loading…
Cancel
Save