mirror of https://github.com/sveltejs/svelte
Merge 2825bcee84
into b92a55994b
commit
52433e8056
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': minor
|
||||
---
|
||||
|
||||
feat: support for spreading function bindings
|
@ -0,0 +1,43 @@
|
||||
/** @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 = b.literal(source.slice(spread_expression.start, spread_expression.end));
|
||||
|
||||
const id = b.id(state.scope.generate('$$spread_binding'));
|
||||
state.init.push(
|
||||
b.const(
|
||||
id,
|
||||
b.call(
|
||||
'$.derived',
|
||||
b.thunk(
|
||||
dev ? b.call('$.validate_spread_bindings', expression, expression_text) : expression
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const is_server = state.options.generate === 'server';
|
||||
const binding = is_server ? b.call(id) : b.call('$.get', id);
|
||||
|
||||
const get = b.thunk(
|
||||
b.call(b.logical('??', b.member(binding, b.literal(0), true), b.id('$.noop')))
|
||||
);
|
||||
|
||||
const set = b.arrow(
|
||||
[b.id('$$value')],
|
||||
b.call(b.logical('??', b.member(binding, b.literal(1), true), b.id('$.noop')), b.id('$$value'))
|
||||
);
|
||||
|
||||
return { get, set };
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
<script>
|
||||
let { a = $bindable() } = $props();
|
||||
|
||||
const bindings = [
|
||||
() => a,
|
||||
(v) => {
|
||||
console.log('b', v);
|
||||
a = v;
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<input
|
||||
type="value"
|
||||
bind:value={...bindings}
|
||||
/>
|
@ -0,0 +1,25 @@
|
||||
import { flushSync } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
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,15 @@
|
||||
<script>
|
||||
function onchange(event) {
|
||||
console.log('change', event.currentTarget.checked);
|
||||
}
|
||||
</script>
|
||||
|
||||
<input type="checkbox" {onchange} />
|
||||
|
||||
<input type="checkbox" bind:checked={...[]} {onchange} />
|
||||
|
||||
<input type="checkbox" bind:checked={...[undefined, undefined]} {onchange} />
|
||||
|
||||
<input type="checkbox" bind:checked={...[null, null]} {onchange} />
|
||||
|
||||
<input type="checkbox" bind:checked={...{}} {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 @@
|
||||
<input type="checkbox" bind:checked={...['not a function', 'not a function' ]} />
|
@ -0,0 +1,17 @@
|
||||
import { flushSync } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
async test({ assert, target, logs }) {
|
||||
assert.htmlEqual(target.innerHTML, `<input><button>Toggle</button>`);
|
||||
|
||||
assert.deepEqual(logs, [false]);
|
||||
|
||||
const button = target.querySelector('button');
|
||||
|
||||
button?.click();
|
||||
flushSync();
|
||||
|
||||
assert.deepEqual(logs, [false, true]);
|
||||
}
|
||||
});
|
@ -0,0 +1,17 @@
|
||||
<script>
|
||||
let state = $state(false);
|
||||
let value = $state('hello')
|
||||
|
||||
function binding(state) {
|
||||
console.log(state);
|
||||
|
||||
return [
|
||||
() => value,
|
||||
(v) => value = v
|
||||
];
|
||||
}
|
||||
</script>
|
||||
|
||||
<input bind:value={...binding(state)} />
|
||||
|
||||
<button onclick={() => (state = !state)}>Toggle</button>
|
@ -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, ['getBindings', ...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,30 @@
|
||||
<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 getBindings() {
|
||||
console.log('getBindings');
|
||||
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={...getBindings()} />
|
||||
|
||||
<input type="checkbox" bind:checked={...(() => [get, set])()} />
|
Loading…
Reference in new issue