mirror of https://github.com/sveltejs/svelte
Merge d4417cb245
into 2344b4052e
commit
af5c1a8472
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'svelte': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
feat: support for spreading function bindings
|
@ -0,0 +1,30 @@
|
|||||||
|
/** @import { Expression } from 'estree' */
|
||||||
|
/** @import { ComponentContext as ClientContext } from '../client/types.js' */
|
||||||
|
/** @import { ComponentContext as ServerContext } from '../server/types.js' */
|
||||||
|
import * as b from '#compiler/builders';
|
||||||
|
import { dev, source } from '../../../state.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes spread bindings for a SpreadElement in a bind directive.
|
||||||
|
* @param {Expression} spread_expression
|
||||||
|
* @param {ClientContext | ServerContext} context
|
||||||
|
* @returns {{ get: Expression, set: Expression }}
|
||||||
|
*/
|
||||||
|
export function init_spread_bindings(spread_expression, { state, visit }) {
|
||||||
|
const expression = /** @type {Expression} */ (visit(spread_expression));
|
||||||
|
const expression_text = dev
|
||||||
|
? b.literal(source.slice(spread_expression.start, spread_expression.end))
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
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', expression, expression_text)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return { get, set };
|
||||||
|
}
|
@ -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>
|
@ -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()} />
|
@ -0,0 +1,25 @@
|
|||||||
|
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, [
|
||||||
|
'getArrayBindings',
|
||||||
|
'getObjectBindings',
|
||||||
|
...repeatArray(checkboxes.length, ['check', false])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/** @template T */
|
||||||
|
function repeatArray(/** @type {number} */ times, /** @type {T[]} */ array) {
|
||||||
|
return /** @type {T[]} */ Array.from({ length: times }, () => array).flat();
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
<script>
|
||||||
|
let check = $state(true);
|
||||||
|
|
||||||
|
const get = () => check;
|
||||||
|
const set = (v) => {
|
||||||
|
console.log('check', v);
|
||||||
|
check = v;
|
||||||
|
};
|
||||||
|
const bindings = [get, set];
|
||||||
|
const nested = {deep: {
|
||||||
|
bindings: [get, set],}
|
||||||
|
};
|
||||||
|
|
||||||
|
function getArrayBindings() {
|
||||||
|
console.log('getArrayBindings');
|
||||||
|
return [get, set];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getObjectBindings() {
|
||||||
|
console.log('getObjectBindings');
|
||||||
|
return { get, set };
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input type="checkbox" bind:checked={get, set} />
|
||||||
|
|
||||||
|
<input type="checkbox" bind:checked={...bindings} />
|
||||||
|
|
||||||
|
<input type="checkbox" bind:checked={...nested.deep.bindings} />
|
||||||
|
|
||||||
|
<input type="checkbox" bind:checked={...getArrayBindings()} />
|
||||||
|
|
||||||
|
<input type="checkbox" bind:checked={...(() => [get, set])()} />
|
||||||
|
|
||||||
|
<input type="checkbox" bind:checked={...getObjectBindings()} />
|
Loading…
Reference in new issue