mirror of https://github.com/sveltejs/svelte
feat: default values for form elements (#14289)
* tests * typings * implement for defaultValue/defaultChecked on inputs * docs (draft) * selected * fix test * remove * tweak * changeset * untrack reads, they could be inside an effect * Apply suggestions from code review Co-authored-by: Rich Harris <rich.harris@vercel.com> * handle select reset case * handle reset case specifically: use different props/queries in that case * enhance test * fix --------- Co-authored-by: Rich Harris <rich.harris@vercel.com>pull/14553/head
parent
c55af4aa83
commit
a57e747cbb
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'svelte': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
feat: support `defaultValue/defaultChecked` for inputs
|
@ -0,0 +1,209 @@
|
|||||||
|
import { test } from '../../test';
|
||||||
|
import { flushSync } from 'svelte';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
async test({ assert, target }) {
|
||||||
|
/**
|
||||||
|
* @param {NodeListOf<any>} inputs
|
||||||
|
* @param {string} field
|
||||||
|
* @param {any | any[]} value
|
||||||
|
*/
|
||||||
|
function check_inputs(inputs, field, value) {
|
||||||
|
for (let i = 0; i < inputs.length; i++) {
|
||||||
|
assert.equal(inputs[i][field], Array.isArray(value) ? value[i] : value, `field ${i}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} input
|
||||||
|
* @param {string} field
|
||||||
|
* @param {any} value
|
||||||
|
*/
|
||||||
|
function set_input(input, field, value) {
|
||||||
|
input[field] = value;
|
||||||
|
input.dispatchEvent(
|
||||||
|
new Event(typeof value === 'boolean' ? 'change' : 'input', { bubbles: true })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {HTMLOptionElement} option
|
||||||
|
*/
|
||||||
|
function select_option(option) {
|
||||||
|
option.selected = true;
|
||||||
|
option.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
const after_reset = [];
|
||||||
|
|
||||||
|
const reset = /** @type {HTMLInputElement} */ (target.querySelector('input[type=reset]'));
|
||||||
|
const [test1, test2, test3, test4, test5] = target.querySelectorAll('div');
|
||||||
|
const [test6, test7, test8, test9] = target.querySelectorAll('select');
|
||||||
|
const [
|
||||||
|
test1_span,
|
||||||
|
test2_span,
|
||||||
|
test3_span,
|
||||||
|
test4_span,
|
||||||
|
test5_span,
|
||||||
|
test6_span,
|
||||||
|
test7_span,
|
||||||
|
test8_span,
|
||||||
|
test9_span
|
||||||
|
] = target.querySelectorAll('span');
|
||||||
|
|
||||||
|
{
|
||||||
|
/** @type {NodeListOf<HTMLInputElement | HTMLTextAreaElement>} */
|
||||||
|
const inputs = test1.querySelectorAll('input, textarea');
|
||||||
|
check_inputs(inputs, 'value', 'x');
|
||||||
|
assert.htmlEqual(test1_span.innerHTML, 'x x x x');
|
||||||
|
|
||||||
|
for (const input of inputs) {
|
||||||
|
set_input(input, 'value', 'foo');
|
||||||
|
}
|
||||||
|
flushSync();
|
||||||
|
check_inputs(inputs, 'value', 'foo');
|
||||||
|
assert.htmlEqual(test1_span.innerHTML, 'foo foo foo foo');
|
||||||
|
|
||||||
|
after_reset.push(() => {
|
||||||
|
console.log('-------------');
|
||||||
|
check_inputs(inputs, 'value', 'x');
|
||||||
|
assert.htmlEqual(test1_span.innerHTML, 'x x x x');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
/** @type {NodeListOf<HTMLInputElement | HTMLTextAreaElement>} */
|
||||||
|
const inputs = test2.querySelectorAll('input, textarea');
|
||||||
|
check_inputs(inputs, 'value', 'y');
|
||||||
|
assert.htmlEqual(test2_span.innerHTML, 'y y y y');
|
||||||
|
|
||||||
|
for (const input of inputs) {
|
||||||
|
set_input(input, 'value', 'foo');
|
||||||
|
}
|
||||||
|
flushSync();
|
||||||
|
check_inputs(inputs, 'value', 'foo');
|
||||||
|
assert.htmlEqual(test2_span.innerHTML, 'foo foo foo foo');
|
||||||
|
|
||||||
|
after_reset.push(() => {
|
||||||
|
check_inputs(inputs, 'value', 'x');
|
||||||
|
assert.htmlEqual(test2_span.innerHTML, 'x x x x');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
/** @type {NodeListOf<HTMLInputElement>} */
|
||||||
|
const inputs = test3.querySelectorAll('input');
|
||||||
|
check_inputs(inputs, 'checked', true);
|
||||||
|
assert.htmlEqual(test3_span.innerHTML, 'true true');
|
||||||
|
|
||||||
|
for (const input of inputs) {
|
||||||
|
set_input(input, 'checked', false);
|
||||||
|
}
|
||||||
|
flushSync();
|
||||||
|
check_inputs(inputs, 'checked', false);
|
||||||
|
assert.htmlEqual(test3_span.innerHTML, 'false false');
|
||||||
|
|
||||||
|
after_reset.push(() => {
|
||||||
|
check_inputs(inputs, 'checked', true);
|
||||||
|
assert.htmlEqual(test3_span.innerHTML, 'true true');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
/** @type {NodeListOf<HTMLInputElement>} */
|
||||||
|
const inputs = test4.querySelectorAll('input');
|
||||||
|
check_inputs(inputs, 'checked', false);
|
||||||
|
assert.htmlEqual(test4_span.innerHTML, 'false false');
|
||||||
|
|
||||||
|
after_reset.push(() => {
|
||||||
|
check_inputs(inputs, 'checked', true);
|
||||||
|
assert.htmlEqual(test4_span.innerHTML, 'true true');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
/** @type {NodeListOf<HTMLInputElement>} */
|
||||||
|
const inputs = test5.querySelectorAll('input');
|
||||||
|
check_inputs(inputs, 'checked', true);
|
||||||
|
assert.htmlEqual(test5_span.innerHTML, 'true');
|
||||||
|
|
||||||
|
after_reset.push(() => {
|
||||||
|
check_inputs(inputs, 'checked', false);
|
||||||
|
assert.htmlEqual(test5_span.innerHTML, 'false');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
/** @type {NodeListOf<HTMLOptionElement>} */
|
||||||
|
const options = test6.querySelectorAll('option');
|
||||||
|
check_inputs(options, 'selected', [false, true, false]);
|
||||||
|
assert.htmlEqual(test6_span.innerHTML, 'b');
|
||||||
|
|
||||||
|
select_option(options[2]);
|
||||||
|
flushSync();
|
||||||
|
check_inputs(options, 'selected', [false, false, true]);
|
||||||
|
assert.htmlEqual(test6_span.innerHTML, 'c');
|
||||||
|
|
||||||
|
after_reset.push(() => {
|
||||||
|
check_inputs(options, 'selected', [false, true, false]);
|
||||||
|
assert.htmlEqual(test6_span.innerHTML, 'b');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
/** @type {NodeListOf<HTMLOptionElement>} */
|
||||||
|
const options = test7.querySelectorAll('option');
|
||||||
|
check_inputs(options, 'selected', [false, true, false]);
|
||||||
|
assert.htmlEqual(test7_span.innerHTML, 'b');
|
||||||
|
|
||||||
|
select_option(options[2]);
|
||||||
|
flushSync();
|
||||||
|
check_inputs(options, 'selected', [false, false, true]);
|
||||||
|
assert.htmlEqual(test7_span.innerHTML, 'c');
|
||||||
|
|
||||||
|
after_reset.push(() => {
|
||||||
|
check_inputs(options, 'selected', [false, true, false]);
|
||||||
|
assert.htmlEqual(test7_span.innerHTML, 'b');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
/** @type {NodeListOf<HTMLOptionElement>} */
|
||||||
|
const options = test8.querySelectorAll('option');
|
||||||
|
check_inputs(options, 'selected', [false, false, true]);
|
||||||
|
assert.htmlEqual(test8_span.innerHTML, 'c');
|
||||||
|
|
||||||
|
select_option(options[0]);
|
||||||
|
flushSync();
|
||||||
|
check_inputs(options, 'selected', [true, false, false]);
|
||||||
|
assert.htmlEqual(test8_span.innerHTML, 'a');
|
||||||
|
|
||||||
|
after_reset.push(() => {
|
||||||
|
check_inputs(options, 'selected', [false, true, false]);
|
||||||
|
assert.htmlEqual(test8_span.innerHTML, 'b');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
/** @type {NodeListOf<HTMLOptionElement>} */
|
||||||
|
const options = test9.querySelectorAll('option');
|
||||||
|
check_inputs(options, 'selected', [false, false, true]);
|
||||||
|
assert.htmlEqual(test9_span.innerHTML, 'c');
|
||||||
|
|
||||||
|
select_option(options[0]);
|
||||||
|
flushSync();
|
||||||
|
check_inputs(options, 'selected', [true, false, false]);
|
||||||
|
assert.htmlEqual(test9_span.innerHTML, 'a');
|
||||||
|
|
||||||
|
after_reset.push(() => {
|
||||||
|
check_inputs(options, 'selected', [false, true, false]);
|
||||||
|
assert.htmlEqual(test9_span.innerHTML, 'b');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
reset.click();
|
||||||
|
await Promise.resolve();
|
||||||
|
flushSync();
|
||||||
|
after_reset.forEach((fn) => fn());
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,156 @@
|
|||||||
|
<script>
|
||||||
|
let value1 = $state();
|
||||||
|
let value2 = $state();
|
||||||
|
let value3 = $state();
|
||||||
|
let value4 = $state();
|
||||||
|
let value5 = $state();
|
||||||
|
let value6 = $state();
|
||||||
|
let value7 = $state();
|
||||||
|
let value8 = $state();
|
||||||
|
let value9 = $state('y');
|
||||||
|
let value10 = $state('y');
|
||||||
|
let value11 = $state('y');
|
||||||
|
let value12 = $state('y');
|
||||||
|
let value13 = $state('y');
|
||||||
|
let value14 = $state('y');
|
||||||
|
let value15 = $state('y');
|
||||||
|
let value16 = $state('y');
|
||||||
|
|
||||||
|
let checked1 = $state();
|
||||||
|
let checked2 = $state();
|
||||||
|
let checked3 = $state();
|
||||||
|
let checked4 = $state();
|
||||||
|
let checked5 = $state(false);
|
||||||
|
let checked6 = $state(false);
|
||||||
|
let checked7 = $state(false);
|
||||||
|
let checked8 = $state(false);
|
||||||
|
let checked9 = $state(true);
|
||||||
|
let checked10 = $state(true);
|
||||||
|
|
||||||
|
|
||||||
|
let selected1 = $state();
|
||||||
|
let selected2 = $state();
|
||||||
|
let selected3 = $state('c');
|
||||||
|
let selected4 = $state('c');
|
||||||
|
let selected5 = $state(['c']);
|
||||||
|
let selected6 = $state(['c']);
|
||||||
|
|
||||||
|
let defaultValue = $state('x');
|
||||||
|
let defaultChecked = $state(true);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form>
|
||||||
|
<p>Input/Textarea value</p>
|
||||||
|
<!-- defaultValue=x, value=undefined -->
|
||||||
|
<div class="test-1">
|
||||||
|
<input {defaultValue} bind:value={value1} />
|
||||||
|
<input {defaultValue} value={value2} />
|
||||||
|
<input defaultValue="x" bind:value={value3} />
|
||||||
|
<input defaultValue="x" value={value4} />
|
||||||
|
<textarea {defaultValue} value={value5}></textarea>
|
||||||
|
<textarea {defaultValue} bind:value={value6}></textarea>
|
||||||
|
<textarea defaultValue="x" value={value7}></textarea>
|
||||||
|
<textarea defaultValue="x" bind:value={value8}></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- defaultValue=x, value=y -->
|
||||||
|
<div class="test-2">
|
||||||
|
<input {defaultValue} bind:value={value9} />
|
||||||
|
<input {defaultValue} value={value10} />
|
||||||
|
<input defaultValue="x" value={value11} />
|
||||||
|
<input defaultValue="x" bind:value={value12} />
|
||||||
|
<textarea {defaultValue} value={value13}></textarea>
|
||||||
|
<textarea {defaultValue} bind:value={value14}></textarea>
|
||||||
|
<textarea defaultValue="x" value={value15}></textarea>
|
||||||
|
<textarea defaultValue="x" bind:value={value16}></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Input checked</p>
|
||||||
|
<!-- defaultChecked=true, checked=undefined -->
|
||||||
|
<div class="test-3">
|
||||||
|
<input type="checkbox" {defaultChecked} checked={checked1} />
|
||||||
|
<input type="checkbox" {defaultChecked} bind:checked={checked2} />
|
||||||
|
<input type="checkbox" defaultChecked checked={checked3} />
|
||||||
|
<input type="checkbox" defaultChecked bind:checked={checked4} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- defaultChecked=true, checked=false -->
|
||||||
|
<div class="test-4">
|
||||||
|
<input type="checkbox" {defaultChecked} checked={checked5} />
|
||||||
|
<input type="checkbox" {defaultChecked} bind:checked={checked6} />
|
||||||
|
<input type="checkbox" defaultChecked checked={checked7} />
|
||||||
|
<input type="checkbox" defaultChecked bind:checked={checked8} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- defaultChecked=false, checked=true -->
|
||||||
|
<div class="test-5">
|
||||||
|
<input type="checkbox" defaultChecked={false} checked={checked9} />
|
||||||
|
<input type="checkbox" defaultChecked={false} bind:checked={checked10} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- no support for bind:group; too complex + we may deprecate it in favor of bind:checked={get,set} -->
|
||||||
|
|
||||||
|
<p>Select (single)</p>
|
||||||
|
<!-- select with static checked, value=undefined-->
|
||||||
|
<select bind:value={selected1}>
|
||||||
|
<option value="a">A</option>
|
||||||
|
<option value="b" selected>B</option>
|
||||||
|
<option value="c">C</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- select with dynamic checked, value=undefined-->
|
||||||
|
<select bind:value={selected2}>
|
||||||
|
<option value="a">A</option>
|
||||||
|
<option value="b" selected={defaultChecked}>B</option>
|
||||||
|
<option value="c">C</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- select with static checked, value=something else than default-->
|
||||||
|
<select bind:value={selected3}>
|
||||||
|
<option value="a">A</option>
|
||||||
|
<option value="b" selected>B</option>
|
||||||
|
<option value="c">C</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- select with dynamic checked, value=something else than default-->
|
||||||
|
<select bind:value={selected4}>
|
||||||
|
<option value="a">A</option>
|
||||||
|
<option value="b" selected={defaultChecked}>B</option>
|
||||||
|
<option value="c">C</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<p>Select (multiple)</p>
|
||||||
|
<!-- There's no possibility to have the selected attribute influence a multi select initially,
|
||||||
|
because we require the value to be an array -->
|
||||||
|
|
||||||
|
<!-- select with static checked, value=something else than default-->
|
||||||
|
<select multiple bind:value={selected5}>
|
||||||
|
<option value="a">A</option>
|
||||||
|
<option value="b" selected>B</option>
|
||||||
|
<option value="c">C</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- select with dynamic checked, value=something else than default-->
|
||||||
|
<select multiple bind:value={selected6}>
|
||||||
|
<option value="a">A</option>
|
||||||
|
<option value="b" selected={defaultChecked}>B</option>
|
||||||
|
<option value="c">C</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<input type="reset" value="Reset" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Bound values:
|
||||||
|
<span class="test-1">{value1} {value3} {value6} {value8}</span>
|
||||||
|
<span class="test-2">{value9} {value12} {value14} {value16}</span>
|
||||||
|
<span class="test-3">{checked2} {checked4}</span>
|
||||||
|
<span class="test-4">{checked6} {checked8}</span>
|
||||||
|
<span class="test-5">{checked10}</span>
|
||||||
|
<span class="test-6">{selected1}</span>
|
||||||
|
<span class="test-7">{selected2}</span>
|
||||||
|
<span class="test-8">{selected3}</span>
|
||||||
|
<span class="test-9">{selected4}</span>
|
||||||
|
<span class="test-10">{selected5}</span>
|
||||||
|
<span class="test-11">{selected6}</span>
|
||||||
|
</p>
|
Loading…
Reference in new issue