mirror of https://github.com/sveltejs/svelte
parent
a9ffe159ec
commit
02ea592b52
@ -1,49 +1,32 @@
|
||||
/** @import { CallExpression, Expression, SpreadElement, Super } from 'estree' */
|
||||
/** @import { Expression, SpreadElement } from 'estree' */
|
||||
/** @import { Context } from 'zimmerframe' */
|
||||
/** @import { ComponentClientTransformState } from '../client/types.js' */
|
||||
/** @import { ComponentServerTransformState } from '../server/types.js' */
|
||||
/** @import { AST } from '#compiler' */
|
||||
import * as b from '#compiler/builders';
|
||||
import { dev, source } from '../../../state.js';
|
||||
|
||||
/**
|
||||
* Handles SpreadElement by creating a variable declaration and returning getter/setter expressions
|
||||
* Initializes spread bindings for a SpreadElement in a bind directive.
|
||||
* @param {SpreadElement} spread_expression
|
||||
* @param {ComponentClientTransformState | ComponentServerTransformState} state
|
||||
* @param {function} visit
|
||||
* @param {Context<AST.SvelteNode, ComponentClientTransformState> | Context<AST.SvelteNode, ComponentServerTransformState>} context
|
||||
* @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'));
|
||||
|
||||
export function init_spread_bindings(spread_expression, { state, visit }) {
|
||||
const visited_expression = /** @type {Expression} */ (visit(spread_expression.argument));
|
||||
state.init.push(b.const(id, visited_expression));
|
||||
|
||||
const noop = b.arrow([], b.block([]));
|
||||
|
||||
// Generate helper variables for clearer error messages
|
||||
const get = b.id(state.scope.generate(id.name + '_get'));
|
||||
const set = b.id(state.scope.generate(id.name + '_set'));
|
||||
const expression_text = dev
|
||||
? b.literal(source.slice(spread_expression.argument.start, spread_expression.argument.end))
|
||||
: undefined;
|
||||
|
||||
const getter = b.logical(
|
||||
'??',
|
||||
b.conditional(
|
||||
b.call('Array.isArray', id),
|
||||
b.member(id, b.literal(0), true),
|
||||
b.member(id, b.id('get'))
|
||||
),
|
||||
noop
|
||||
const id = state.scope.generate('$$spread_binding');
|
||||
const get = b.id(id + '_get');
|
||||
const set = b.id(id + '_set');
|
||||
state.init.push(
|
||||
b.const(
|
||||
b.array_pattern([get, set]),
|
||||
b.call('$.validate_spread_bindings', visited_expression, expression_text)
|
||||
)
|
||||
);
|
||||
const setter = b.logical(
|
||||
'??',
|
||||
b.conditional(
|
||||
b.call('Array.isArray', id),
|
||||
b.member(id, b.literal(1), true),
|
||||
b.member(id, b.id('set'))
|
||||
),
|
||||
noop
|
||||
);
|
||||
|
||||
state.init.push(b.const(get, getter));
|
||||
state.init.push(b.const(set, setter));
|
||||
|
||||
return { get, set };
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
import { flushSync } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
async test({ assert, target, logs }) {
|
||||
const checkboxes = target.querySelectorAll('input');
|
||||
|
||||
flushSync();
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `<input type="checkbox" >`.repeat(checkboxes.length));
|
||||
|
||||
checkboxes.forEach((checkbox) => checkbox.click());
|
||||
|
||||
assert.deepEqual(logs, repeatArray(checkboxes.length, ['change', true]));
|
||||
}
|
||||
});
|
||||
|
||||
/** @template T */
|
||||
function repeatArray(/** @type {number} */ times, /** @type {T[]} */ array) {
|
||||
return /** @type {T[]} */ Array.from({ length: times }, () => array).flat();
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<script>
|
||||
const empty_bindings_array = []
|
||||
const empty_bindings_object = {}
|
||||
const incompatible_bindings_object = {
|
||||
read() {
|
||||
console.log('read');
|
||||
return true;
|
||||
},
|
||||
write(v) {
|
||||
console.log('write', v);
|
||||
}
|
||||
}
|
||||
const undefined_bindings_array = [undefined, undefined];
|
||||
const undefined_bindings_object = { get: undefined, set: undefined };
|
||||
const null_bindings_array = [null, null];
|
||||
const null_bindings_object = { get: null, set: null };
|
||||
|
||||
function onchange(event) {
|
||||
console.log('change', event.currentTarget.checked);
|
||||
}
|
||||
</script>
|
||||
|
||||
<input type="checkbox" {onchange} />
|
||||
|
||||
<input type="checkbox" bind:checked={...empty_bindings_array} {onchange} />
|
||||
|
||||
<input type="checkbox" bind:checked={...empty_bindings_object} {onchange} />
|
||||
|
||||
<input type="checkbox" bind:checked={...incompatible_bindings_object} {onchange} />
|
||||
|
||||
<input type="checkbox" bind:checked={...undefined_bindings_array} {onchange} />
|
||||
|
||||
<input type="checkbox" bind:checked={...undefined_bindings_object} {onchange} />
|
||||
|
||||
<input type="checkbox" bind:checked={...null_bindings_array} {onchange} />
|
||||
|
||||
<input type="checkbox" bind:checked={...null_bindings_object} {onchange} />
|
@ -0,0 +1,9 @@
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
expect_unhandled_rejections: true,
|
||||
compileOptions: {
|
||||
dev: true
|
||||
},
|
||||
error: 'invalid_spread_bindings'
|
||||
});
|
@ -0,0 +1,7 @@
|
||||
<script>
|
||||
function getInvalidBindings() {
|
||||
return { get: 'not a function', set: 'not a function' };
|
||||
}
|
||||
</script>
|
||||
|
||||
<input type="checkbox" bind:checked={...getInvalidBindings()} />
|
Loading…
Reference in new issue