component props

pull/16607/head
Jack Goodall 1 month ago
parent d700bf1bc5
commit 484885d718

@ -1,4 +1,4 @@
/** @import { BlockStatement, Expression, ExpressionStatement, Identifier, MemberExpression, Pattern, Property, SequenceExpression, Statement } from 'estree' */ /** @import { BlockStatement, Expression, ExpressionStatement, Identifier, MemberExpression, Pattern, Property, SequenceExpression, Statement, SpreadElement } from 'estree' */
/** @import { AST } from '#compiler' */ /** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../../types.js' */ /** @import { ComponentContext } from '../../types.js' */
import { dev, is_ignored } from '../../../../../state.js'; import { dev, is_ignored } from '../../../../../state.js';
@ -8,6 +8,7 @@ import { add_svelte_meta, build_bind_this, Memoizer, validate_binding } from '..
import { build_attribute_value } from '../shared/element.js'; import { build_attribute_value } from '../shared/element.js';
import { build_event_handler } from './events.js'; import { build_event_handler } from './events.js';
import { determine_slot } from '../../../../../utils/slot.js'; import { determine_slot } from '../../../../../utils/slot.js';
import { init_spread_bindings } from '../../../shared/spread_bindings.js';
/** /**
* @param {AST.Component | AST.SvelteComponent | AST.SvelteSelf} node * @param {AST.Component | AST.SvelteComponent | AST.SvelteSelf} node
@ -48,7 +49,7 @@ export function build_component(node, component_name, context) {
/** @type {Property[]} */ /** @type {Property[]} */
const custom_css_props = []; const custom_css_props = [];
/** @type {Identifier | MemberExpression | SequenceExpression | null} */ /** @type {Identifier | MemberExpression | SequenceExpression | SpreadElement | null} */
let bind_this = null; let bind_this = null;
/** @type {ExpressionStatement[]} */ /** @type {ExpressionStatement[]} */
@ -196,6 +197,16 @@ export function build_component(node, component_name, context) {
push_prop(b.init(attribute.name, value)); push_prop(b.init(attribute.name, value));
} }
} else if (attribute.type === 'BindDirective') { } else if (attribute.type === 'BindDirective') {
if (attribute.expression.type === 'SpreadElement') {
const { get, set } = init_spread_bindings(attribute.expression, context);
if (attribute.name === 'this') {
bind_this = attribute.expression;
} else {
push_prop(b.get(attribute.name, [b.return(b.call(get))]), true);
push_prop(b.set(attribute.name, [b.stmt(b.call(set, b.id('$$value')))]), true);
}
} else {
const expression = /** @type {Expression} */ (context.visit(attribute.expression)); const expression = /** @type {Expression} */ (context.visit(attribute.expression));
if ( if (
@ -257,7 +268,10 @@ export function build_component(node, component_name, context) {
// Delay prop pushes so bindings come at the end, to avoid spreads overwriting them // Delay prop pushes so bindings come at the end, to avoid spreads overwriting them
if (is_store_sub) { if (is_store_sub) {
push_prop( push_prop(
b.get(attribute.name, [b.stmt(b.call('$.mark_store_binding')), b.return(expression)]), b.get(attribute.name, [
b.stmt(b.call('$.mark_store_binding')),
b.return(expression)
]),
true true
); );
} else { } else {
@ -271,11 +285,14 @@ export function build_component(node, component_name, context) {
); );
push_prop( push_prop(
b.set(attribute.name, [b.stmt(/** @type {Expression} */ (context.visit(assignment)))]), b.set(attribute.name, [
b.stmt(/** @type {Expression} */ (context.visit(assignment)))
]),
true true
); );
} }
} }
}
} else if (attribute.type === 'AttachTag') { } else if (attribute.type === 'AttachTag') {
const evaluated = context.state.scope.evaluate(attribute.expression); const evaluated = context.state.scope.evaluate(attribute.expression);

@ -5,6 +5,7 @@ import { empty_comment, build_attribute_value } from './utils.js';
import * as b from '#compiler/builders'; import * as b from '#compiler/builders';
import { is_element_node } from '../../../../nodes.js'; import { is_element_node } from '../../../../nodes.js';
import { dev } from '../../../../../state.js'; import { dev } from '../../../../../state.js';
import { init_spread_bindings } from '../../../shared/spread_bindings.js';
/** /**
* @param {AST.Component | AST.SvelteComponent | AST.SvelteSelf} node * @param {AST.Component | AST.SvelteComponent | AST.SvelteSelf} node
@ -93,7 +94,17 @@ export function build_inline_component(node, expression, context) {
const value = build_attribute_value(attribute.value, context, false, true); const value = build_attribute_value(attribute.value, context, false, true);
push_prop(b.prop('init', b.key(attribute.name), value)); push_prop(b.prop('init', b.key(attribute.name), value));
} else if (attribute.type === 'BindDirective' && attribute.name !== 'this') { } else if (attribute.type === 'BindDirective' && attribute.name !== 'this') {
if (attribute.expression.type === 'SequenceExpression') { if (attribute.expression.type === 'SpreadElement') {
const { get, set } = init_spread_bindings(attribute.expression, context);
push_prop(b.get(attribute.name, [b.return(b.call(get))]));
push_prop(
b.set(attribute.name, [
b.stmt(b.call(set, b.id('$$value'))),
b.stmt(b.assignment('=', b.id('$$settled'), b.false))
])
);
} else if (attribute.expression.type === 'SequenceExpression') {
const [get, set] = /** @type {SequenceExpression} */ (context.visit(attribute.expression)) const [get, set] = /** @type {SequenceExpression} */ (context.visit(attribute.expression))
.expressions; .expressions;
const get_id = b.id(context.state.scope.generate('bind_get')); const get_id = b.id(context.state.scope.generate('bind_get'));

@ -0,0 +1,16 @@
<script>
let { a = $bindable() } = $props();
const bindings = $derived([
() => a,
(v) => {
console.log('b', v);
a = v;
}
]);
</script>
<input
type="value"
bind:value={...bindings}
/>

@ -0,0 +1,26 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
import { assert_ok } from '../../../suite';
export default test({
async test({ assert, target, logs }) {
const [input, checkbox] = target.querySelectorAll('input');
input.value = '2';
input.dispatchEvent(new window.Event('input'));
flushSync();
assert.htmlEqual(
target.innerHTML,
`<button>a: 2</button><input type="value"><div><input type="checkbox" ></div>`
);
assert.deepEqual(logs, ['b', '2', 'a', '2']);
flushSync(() => {
checkbox.click();
});
assert.deepEqual(logs, ['b', '2', 'a', '2', 'check', false]);
}
});

@ -0,0 +1,30 @@
<script>
import Child from './Child.svelte';
let a = $state(0);
const a_bindings = [
() => a,
(v) => {
console.log('a', v);
a = v;
}
]
let check = $state(true);
const check_bindings = [
() => check,
(v) => {
console.log('check', v);
check = v;
}
]
</script>
<button onclick={() => a++}>a: {a}</button>
<Child
bind:a={...a_bindings}
/>
<div>
<input type="checkbox" bind:checked={...check_bindings} />
</div>
Loading…
Cancel
Save