mirror of https://github.com/sveltejs/svelte
feat: improve `bind:group` behavior (#7892)
track all `#each` variables that could result in a change to the inputs and also update the `$$binding_groups` variable which holds the references to the inputs of each group accordingly. Fixes #7633 Fixes #6112 Fixes #7884pull/8349/head
parent
6476e9b34f
commit
0966d1d282
@ -0,0 +1,52 @@
|
||||
// https://github.com/sveltejs/svelte/issues/7633
|
||||
export default {
|
||||
async test({ assert, target, component, window }) {
|
||||
let inputs = target.querySelectorAll('input');
|
||||
|
||||
assert.equal(inputs[0].checked, true);
|
||||
assert.equal(inputs[1].checked, false);
|
||||
assert.equal(inputs[2].checked, false);
|
||||
|
||||
component.moveDown(0);
|
||||
component.moveDown(1);
|
||||
await Promise.resolve();
|
||||
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<div class="item">
|
||||
b <label><input name="current" type="radio" value="b"> current</label>
|
||||
</div>
|
||||
<div class="item">
|
||||
c <label><input name="current" type="radio" value="c"> current</label>
|
||||
</div>
|
||||
<div class="item">
|
||||
a <label><input name="current" type="radio" value="a"> current</label>
|
||||
</div>
|
||||
`
|
||||
);
|
||||
|
||||
// after shifting order, should still keep the correct radio checked
|
||||
inputs = target.querySelectorAll('input');
|
||||
assert.equal(inputs[0].checked, false);
|
||||
assert.equal(inputs[1].checked, false);
|
||||
assert.equal(inputs[2].checked, true);
|
||||
|
||||
(component.current = 'b');
|
||||
await Promise.resolve();
|
||||
|
||||
inputs = target.querySelectorAll('input');
|
||||
assert.equal(inputs[0].checked, true);
|
||||
assert.equal(inputs[1].checked, false);
|
||||
assert.equal(inputs[2].checked, false);
|
||||
|
||||
component.moveDown(1);
|
||||
await Promise.resolve();
|
||||
|
||||
// after shifting order, should still keep the correct radio checked
|
||||
inputs = target.querySelectorAll('input');
|
||||
assert.equal(inputs[0].checked, true);
|
||||
assert.equal(inputs[1].checked, false);
|
||||
assert.equal(inputs[2].checked, false);
|
||||
}
|
||||
};
|
@ -0,0 +1,36 @@
|
||||
<script>
|
||||
export let list = [
|
||||
{ name: "a", text: "This is a test." },
|
||||
{ name: "b", text: "This is another test." },
|
||||
{ name: "c", text: "This is also a test." },
|
||||
];
|
||||
export let current = "a";
|
||||
export function moveUp(i) {
|
||||
list = [
|
||||
...list.slice(0, Math.max(i - 1, 0)),
|
||||
list[i],
|
||||
list[i - 1],
|
||||
...list.slice(i + 1),
|
||||
];
|
||||
}
|
||||
export function moveDown(i) {
|
||||
moveUp(i + 1);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each list as item (item.name)}
|
||||
<div class="item">
|
||||
{item.name}
|
||||
{#if true}
|
||||
<label
|
||||
><input
|
||||
type="radio"
|
||||
name="current"
|
||||
bind:group={current}
|
||||
value={item.name}
|
||||
/> current</label
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
|
@ -0,0 +1,88 @@
|
||||
// https://github.com/sveltejs/svelte/issues/6112
|
||||
export default {
|
||||
async test({ assert, target, component, window }) {
|
||||
let inputs = target.querySelectorAll('input');
|
||||
|
||||
const check = (set) => {
|
||||
for (let i = 0; i < inputs.length; i++) {
|
||||
assert.equal(inputs[i].checked, set.has(i));
|
||||
}
|
||||
};
|
||||
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<div>1</div>
|
||||
<div>2
|
||||
<div class="arg">
|
||||
<input type="radio" value="a">
|
||||
<input type="radio" value="b">
|
||||
</div>
|
||||
<div class="arg">
|
||||
<input type="radio" value="c">
|
||||
<input type="radio" value="d">
|
||||
</div>
|
||||
</div>
|
||||
<div>3
|
||||
<div class="arg">
|
||||
<input type="radio" value="a">
|
||||
<input type="radio" value="b">
|
||||
</div>
|
||||
<div class="arg">
|
||||
<input type="radio" value="c">
|
||||
<input type="radio" value="d">
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
);
|
||||
|
||||
check(new Set([0, 2, 5, 6]));
|
||||
|
||||
const event = new window.Event('change');
|
||||
|
||||
// dom to value
|
||||
inputs[3].checked = true;
|
||||
await inputs[3].dispatchEvent(event);
|
||||
|
||||
check(new Set([0, 3, 5, 6]));
|
||||
assert.equal(component.pipelineOperations[1].operation.args[1].value, 'd');
|
||||
|
||||
// remove item
|
||||
component.pipelineOperations = component.pipelineOperations.slice(1);
|
||||
await Promise.resolve();
|
||||
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<div>2
|
||||
<div class="arg">
|
||||
<input type="radio" value="a">
|
||||
<input type="radio" value="b">
|
||||
</div>
|
||||
<div class="arg">
|
||||
<input type="radio" value="c">
|
||||
<input type="radio" value="d">
|
||||
</div>
|
||||
</div>
|
||||
<div>3
|
||||
<div class="arg">
|
||||
<input type="radio" value="a">
|
||||
<input type="radio" value="b">
|
||||
</div>
|
||||
<div class="arg">
|
||||
<input type="radio" value="c">
|
||||
<input type="radio" value="d">
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
);
|
||||
|
||||
inputs = target.querySelectorAll('input');
|
||||
check(new Set([0, 3, 5, 6]));
|
||||
|
||||
inputs[2].checked = true;
|
||||
await inputs[2].dispatchEvent(event);
|
||||
|
||||
check(new Set([0, 2, 5, 6]));
|
||||
}
|
||||
};
|
@ -0,0 +1,60 @@
|
||||
<script>
|
||||
export let pipelineOperations = [
|
||||
{
|
||||
operation: {
|
||||
name: "foo",
|
||||
args: [],
|
||||
},
|
||||
id: 1,
|
||||
},
|
||||
{
|
||||
operation: {
|
||||
name: "bar",
|
||||
args: [
|
||||
{
|
||||
name: "bar_1",
|
||||
value: "a",
|
||||
options: [{ value: "a" }, { value: "b" }],
|
||||
},
|
||||
{
|
||||
name: "bar_2",
|
||||
value: "c",
|
||||
options: [{ value: "c" }, { value: "d" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
id: 2,
|
||||
},
|
||||
{
|
||||
operation: {
|
||||
name: "baz",
|
||||
args: [
|
||||
{
|
||||
name: "baz_1",
|
||||
value: "b",
|
||||
options: [{ value: "a" }, { value: "b" }],
|
||||
},
|
||||
{
|
||||
name: "baz_2",
|
||||
value: "c",
|
||||
options: [{ value: "c" }, { value: "d" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
id: 3,
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
{#each pipelineOperations as { operation, id } (id)}
|
||||
<div>
|
||||
{id}
|
||||
{#each operation.args as arg}
|
||||
<div class="arg">
|
||||
{#each arg.options as { value }}
|
||||
<input type="radio" bind:group={arg.value} {value} />
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
@ -0,0 +1,89 @@
|
||||
// https://github.com/sveltejs/svelte/issues/6112
|
||||
export default {
|
||||
async test({ assert, target, component, window }) {
|
||||
let inputs = target.querySelectorAll('input');
|
||||
|
||||
const check = (set) => {
|
||||
for (let i = 0; i < inputs.length; i++) {
|
||||
assert.equal(inputs[i].checked, set.has(i));
|
||||
}
|
||||
};
|
||||
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<div>1</div>
|
||||
<div>2
|
||||
<div class="arg">
|
||||
<input type="checkbox" value="a">
|
||||
<input type="checkbox" value="b">
|
||||
</div>
|
||||
<div class="arg">
|
||||
<input type="checkbox" value="c">
|
||||
<input type="checkbox" value="d">
|
||||
</div>
|
||||
</div>
|
||||
<div>3
|
||||
<div class="arg">
|
||||
<input type="checkbox" value="a">
|
||||
<input type="checkbox" value="b">
|
||||
</div>
|
||||
<div class="arg">
|
||||
<input type="checkbox" value="c">
|
||||
<input type="checkbox" value="d">
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
);
|
||||
|
||||
check(new Set([0, 2]));
|
||||
|
||||
const event = new window.Event('change');
|
||||
|
||||
// dom to value
|
||||
inputs[3].checked = true;
|
||||
await inputs[3].dispatchEvent(event);
|
||||
|
||||
check(new Set([0, 2, 3]));
|
||||
assert.deepEqual(component.pipelineOperations[1].operation.args[1].value, ['c', 'd']);
|
||||
|
||||
// remove item
|
||||
component.pipelineOperations = component.pipelineOperations.slice(1);
|
||||
await Promise.resolve();
|
||||
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<div>2
|
||||
<div class="arg">
|
||||
<input type="checkbox" value="a">
|
||||
<input type="checkbox" value="b">
|
||||
</div>
|
||||
<div class="arg">
|
||||
<input type="checkbox" value="c">
|
||||
<input type="checkbox" value="d">
|
||||
</div>
|
||||
</div>
|
||||
<div>3
|
||||
<div class="arg">
|
||||
<input type="checkbox" value="a">
|
||||
<input type="checkbox" value="b">
|
||||
</div>
|
||||
<div class="arg">
|
||||
<input type="checkbox" value="c">
|
||||
<input type="checkbox" value="d">
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
);
|
||||
|
||||
inputs = target.querySelectorAll('input');
|
||||
check(new Set([0, 2, 3]));
|
||||
|
||||
inputs[5].checked = true;
|
||||
await inputs[5].dispatchEvent(event);
|
||||
|
||||
check(new Set([0, 2, 3, 5]));
|
||||
assert.deepEqual(component.pipelineOperations[1].operation.args[0].value, ['b']);
|
||||
}
|
||||
};
|
@ -0,0 +1,60 @@
|
||||
<script>
|
||||
export let pipelineOperations = [
|
||||
{
|
||||
operation: {
|
||||
name: "foo",
|
||||
args: [],
|
||||
},
|
||||
id: 1,
|
||||
},
|
||||
{
|
||||
operation: {
|
||||
name: "bar",
|
||||
args: [
|
||||
{
|
||||
name: "bar_1",
|
||||
value: ["a"],
|
||||
options: [{ value: "a" }, { value: "b" }],
|
||||
},
|
||||
{
|
||||
name: "bar_2",
|
||||
value: ["c"],
|
||||
options: [{ value: "c" }, { value: "d" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
id: 2,
|
||||
},
|
||||
{
|
||||
operation: {
|
||||
name: "baz",
|
||||
args: [
|
||||
{
|
||||
name: "baz_1",
|
||||
value: [],
|
||||
options: [{ value: "a" }, { value: "b" }],
|
||||
},
|
||||
{
|
||||
name: "baz_2",
|
||||
value: [],
|
||||
options: [{ value: "c" }, { value: "d" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
id: 3,
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
{#each pipelineOperations as { operation, id } (id)}
|
||||
<div>
|
||||
{id}
|
||||
{#each operation.args as arg}
|
||||
<div class="arg">
|
||||
{#each arg.options as { value }}
|
||||
<input type="checkbox" bind:group={arg.value} {value} />
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
@ -0,0 +1,30 @@
|
||||
export default {
|
||||
async test({ assert, target, window }) {
|
||||
const [input1, input2] = target.querySelectorAll('input[type=text]');
|
||||
const radio = target.querySelector('input[type=radio]');
|
||||
|
||||
assert.equal(radio.checked, false);
|
||||
|
||||
const event = new window.Event('input');
|
||||
|
||||
input1.value = 'world';
|
||||
await input1.dispatchEvent(event);
|
||||
assert.equal(radio.checked, true);
|
||||
|
||||
input2.value = 'foo';
|
||||
await input2.dispatchEvent(event);
|
||||
assert.equal(radio.checked, false);
|
||||
|
||||
input1.value = 'foo';
|
||||
await input1.dispatchEvent(event);
|
||||
assert.equal(radio.checked, true);
|
||||
|
||||
input1.value = 'bar';
|
||||
await input1.dispatchEvent(event);
|
||||
assert.equal(radio.checked, false);
|
||||
|
||||
input2.value = 'bar';
|
||||
await input2.dispatchEvent(event);
|
||||
assert.equal(radio.checked, true);
|
||||
}
|
||||
};
|
@ -0,0 +1,10 @@
|
||||
<script>
|
||||
let name = 'world';
|
||||
let current = '';
|
||||
</script>
|
||||
|
||||
<input type="radio" name="current" bind:group={current} value={name}>
|
||||
|
||||
<input type="text" bind:value={current} />
|
||||
|
||||
<input type="text" bind:value={name} />
|
@ -0,0 +1,78 @@
|
||||
// https://github.com/sveltejs/svelte/issues/7884
|
||||
export default {
|
||||
async test({ assert, target, component, window }) {
|
||||
let inputs = target.querySelectorAll('input');
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<p>{"foo":[],"bar":[]}</p>
|
||||
<h2>foo</h2>
|
||||
<ul>
|
||||
<li><label><input name="foo" type="checkbox" value="1"> 1</label></li>
|
||||
<li><label><input name="foo" type="checkbox" value="2"> 2</label></li>
|
||||
<li><label><input name="foo" type="checkbox" value="3"> 3</label></li>
|
||||
</ul>
|
||||
<h2>bar</h2>
|
||||
<ul>
|
||||
<li><label><input name="bar" type="checkbox" value="1"> 1</label></li>
|
||||
<li><label><input name="bar" type="checkbox" value="2"> 2</label></li>
|
||||
<li><label><input name="bar" type="checkbox" value="3"> 3</label></li>
|
||||
</ul>
|
||||
`);
|
||||
|
||||
const event = new window.Event('change');
|
||||
|
||||
inputs[0].checked = true;
|
||||
await inputs[0].dispatchEvent(event);
|
||||
inputs[2].checked = true;
|
||||
await inputs[2].dispatchEvent(event);
|
||||
inputs[3].checked = true;
|
||||
await inputs[3].dispatchEvent(event);
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<p>{"foo":[1,3],"bar":[1]}</p>
|
||||
<h2>foo</h2>
|
||||
<ul>
|
||||
<li><label><input name="foo" type="checkbox" value="1"> 1</label></li>
|
||||
<li><label><input name="foo" type="checkbox" value="2"> 2</label></li>
|
||||
<li><label><input name="foo" type="checkbox" value="3"> 3</label></li>
|
||||
</ul>
|
||||
<h2>bar</h2>
|
||||
<ul>
|
||||
<li><label><input name="bar" type="checkbox" value="1"> 1</label></li>
|
||||
<li><label><input name="bar" type="checkbox" value="2"> 2</label></li>
|
||||
<li><label><input name="bar" type="checkbox" value="3"> 3</label></li>
|
||||
</ul>
|
||||
`);
|
||||
|
||||
await component.update();
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<p>{"foo":[1,3],"bar":[1],"qux":[]}</p>
|
||||
<h2>qux</h2>
|
||||
<ul>
|
||||
<li><label><input name="qux" type="checkbox" value="4"> 4</label></li>
|
||||
<li><label><input name="qux" type="checkbox" value="5"> 5</label></li>
|
||||
<li><label><input name="qux" type="checkbox" value="6"> 6</label></li>
|
||||
</ul>
|
||||
`);
|
||||
|
||||
inputs = target.querySelectorAll('input');
|
||||
inputs[0].checked = true;
|
||||
await inputs[0].dispatchEvent(event);
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<p>{"foo":[1,3],"bar":[1],"qux":[4]}</p>
|
||||
<h2>qux</h2>
|
||||
<ul>
|
||||
<li><label><input name="qux" type="checkbox" value="4"> 4</label></li>
|
||||
<li><label><input name="qux" type="checkbox" value="5"> 5</label></li>
|
||||
<li><label><input name="qux" type="checkbox" value="6"> 6</label></li>
|
||||
</ul>
|
||||
`);
|
||||
assert.equal(inputs[0].checked, true);
|
||||
assert.equal(inputs[1].checked, false);
|
||||
assert.equal(inputs[2].checked, false);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,36 @@
|
||||
<script>
|
||||
let keys = ["foo", "bar"];
|
||||
let values = [1, 2, 3];
|
||||
|
||||
const object = {};
|
||||
|
||||
$: keys.forEach((key) => {
|
||||
// Make sure Svelte has an array to bind to
|
||||
if (!object[key]) {
|
||||
object[key] = [];
|
||||
}
|
||||
});
|
||||
|
||||
export function update() {
|
||||
keys = ["qux"];
|
||||
values = [4, 5, 6];
|
||||
}
|
||||
</script>
|
||||
|
||||
<p>
|
||||
{JSON.stringify(object)}
|
||||
</p>
|
||||
|
||||
{#each keys as key (key)}
|
||||
<h2>{key}</h2>
|
||||
<ul>
|
||||
{#each values as value (value)}
|
||||
<li>
|
||||
<label>
|
||||
<input type="checkbox" name={key} {value} bind:group={object[key]} />
|
||||
{value}
|
||||
</label>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/each}
|
@ -0,0 +1,49 @@
|
||||
// https://github.com/sveltejs/svelte/issues/7633
|
||||
export default {
|
||||
async test({ assert, target, component, window }) {
|
||||
let inputs = target.querySelectorAll('input');
|
||||
|
||||
assert.equal(inputs[0].checked, true);
|
||||
assert.equal(inputs[1].checked, false);
|
||||
assert.equal(inputs[2].checked, false);
|
||||
|
||||
await component.moveDown(0);
|
||||
await component.moveDown(1);
|
||||
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<div class="item">
|
||||
b <label><input name="current" type="radio" value="b"> current</label>
|
||||
</div>
|
||||
<div class="item">
|
||||
c <label><input name="current" type="radio" value="c"> current</label>
|
||||
</div>
|
||||
<div class="item">
|
||||
a <label><input name="current" type="radio" value="a"> current</label>
|
||||
</div>
|
||||
`
|
||||
);
|
||||
|
||||
// after shifting order, should still keep the correct radio checked
|
||||
inputs = target.querySelectorAll('input');
|
||||
assert.equal(inputs[0].checked, false);
|
||||
assert.equal(inputs[1].checked, false);
|
||||
assert.equal(inputs[2].checked, true);
|
||||
|
||||
await (component.current = 'b');
|
||||
|
||||
inputs = target.querySelectorAll('input');
|
||||
assert.equal(inputs[0].checked, true);
|
||||
assert.equal(inputs[1].checked, false);
|
||||
assert.equal(inputs[2].checked, false);
|
||||
|
||||
await component.moveDown(1);
|
||||
|
||||
// after shifting order, should still keep the correct radio checked
|
||||
inputs = target.querySelectorAll('input');
|
||||
assert.equal(inputs[0].checked, true);
|
||||
assert.equal(inputs[1].checked, false);
|
||||
assert.equal(inputs[2].checked, false);
|
||||
}
|
||||
};
|
@ -0,0 +1,36 @@
|
||||
<script>
|
||||
export let list = [
|
||||
{ name: "a", text: "This is a test." },
|
||||
{ name: "b", text: "This is another test." },
|
||||
{ name: "c", text: "This is also a test." },
|
||||
];
|
||||
export let current = "a";
|
||||
export function moveUp(i) {
|
||||
list = [
|
||||
...list.slice(0, Math.max(i - 1, 0)),
|
||||
list[i],
|
||||
list[i - 1],
|
||||
...list.slice(i + 1),
|
||||
];
|
||||
}
|
||||
export function moveDown(i) {
|
||||
moveUp(i + 1);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each list as item}
|
||||
<div class="item">
|
||||
{item.name}
|
||||
{#if true}
|
||||
<label
|
||||
><input
|
||||
type="radio"
|
||||
name="current"
|
||||
bind:group={current}
|
||||
value={item.name}
|
||||
/> current</label
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
|
Loading…
Reference in new issue