Skip to content

Commit 88728e3

Browse files
authored
fix: bind null option and input values consistently (#8328)
Null and undefined `value` bindings should always be set to an empty string. This allows native browser validation of `required` fields to work as expected with placeholder options. Placeholder options bound to null are necessary in forms where the field is conditionally required, and the bound value is posted to an API endpoint which requires it to be a nullable number or object rather than a string. fixes #8312
1 parent 9460616 commit 88728e3

File tree

4 files changed

+44
-4
lines changed

4 files changed

+44
-4
lines changed

src/compiler/compile/render_dom/wrappers/Element/Attribute.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
172172
}
173173

174174
if (is_indirectly_bound_value) {
175-
const update_value = b`${element.var}.value = ${element.var}.__value;`;
175+
const update_value = b`@set_input_value(${element.var}, ${element.var}.__value);`;
176176
block.chunks.hydrate.push(update_value);
177177

178178
updater = b`

test/js/samples/select-dynamic-value/expected.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import {
88
insert,
99
noop,
1010
safe_not_equal,
11-
select_option
11+
select_option,
12+
set_input_value
1213
} from "svelte/internal";
1314

1415
function create_fragment(ctx) {
@@ -24,9 +25,9 @@ function create_fragment(ctx) {
2425
option1 = element("option");
2526
option1.textContent = "2";
2627
option0.__value = "1";
27-
option0.value = option0.__value;
28+
set_input_value(option0, option0.__value);
2829
option1.__value = "2";
29-
option1.value = option1.__value;
30+
set_input_value(option1, option1.__value);
3031
},
3132
m(target, anchor) {
3233
insert(target, select, anchor);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const items = [ { id: 'a' }, { id: 'b' } ];
2+
3+
export default {
4+
props: {
5+
foo: null,
6+
items
7+
},
8+
9+
test({ assert, component, target }) {
10+
const select = target.querySelector( 'select' );
11+
const options = target.querySelectorAll( 'option' );
12+
13+
assert.equal( options[0].selected, true );
14+
assert.equal( options[0].disabled, true );
15+
assert.equal( options[1].selected, false );
16+
assert.equal( options[1].disabled, false );
17+
18+
// placeholder option value must be blank string for native required field validation
19+
assert.equal( options[0].value, '' );
20+
assert.equal( select.checkValidity(), false );
21+
22+
component.foo = items[0];
23+
24+
assert.equal( options[0].selected, false );
25+
assert.equal( options[1].selected, true );
26+
assert.equal( select.checkValidity(), true );
27+
}
28+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script>
2+
export let foo;
3+
export let items;
4+
</script>
5+
6+
<select bind:value={foo} required>
7+
<option value={null} disabled>Select an option</option>
8+
{#each items as item}
9+
<option value={item}>{item.id}</option>
10+
{/each}
11+
</select>

0 commit comments

Comments
 (0)