-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
fix: make <select> <option value> behavior consistent
#12316
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| 'svelte': patch | ||
| --- | ||
|
|
||
| fix: make `<select>` `<option value>` behavior consistent |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -155,10 +155,20 @@ export function set_custom_element_data(node, prop, value) { | |
| export function set_attributes(element, prev, next, lowercase_attributes, css_hash) { | ||
| var has_hash = css_hash.length !== 0; | ||
| var current = prev || {}; | ||
| var is_option_element = element.tagName === 'OPTION'; | ||
|
|
||
| for (var key in prev) { | ||
| if (!(key in next)) { | ||
| next[key] = null; | ||
| if (is_option_element && key === 'value') { | ||
| // Because of the "set falsy option values to the empty string" logic below, which can't | ||
| // differentiate between a missing value and an explicitly set value of null or undefined, | ||
| // we need to remove the attribute here and delete the key from the object. | ||
| element.removeAttribute(key); | ||
| delete current[key]; | ||
| delete next[key]; | ||
| } else { | ||
| next[key] = null; | ||
| } | ||
|
||
| } | ||
| } | ||
|
|
||
|
|
@@ -178,6 +188,20 @@ export function set_attributes(element, prev, next, lowercase_attributes, css_ha | |
| for (const key in next) { | ||
| // let instead of var because referenced in a closure | ||
| let value = next[key]; | ||
|
|
||
| // Up here because we want to do this for the initial value, too, even if it's undefined, | ||
| // and this wouldn't be reached in case of undefined because of the equality check below | ||
| if (is_option_element && key === 'value' && value == null) { | ||
| // The <option> element is a special case because removing the value attribute means | ||
| // the value is set to the text content of the option element, and setting the value | ||
| // to null or undefined means the value is set to the string "null" or "undefined". | ||
| // To align with how we handle this case in non-spread-scenarios, this logic is needed. | ||
| // @ts-ignore | ||
| element.value = element.__value = ''; | ||
| current[key] = value; | ||
| continue; | ||
| } | ||
|
|
||
| var prev_value = current[key]; | ||
| if (value === prev_value) continue; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| import { flushSync } from 'svelte'; | ||
| import { test } from '../../test'; | ||
|
|
||
| // <option value> is special because falsy values should result in an empty string value attribute | ||
| export default test({ | ||
| mode: ['client'], | ||
| test({ assert, target }) { | ||
| assert.htmlEqual( | ||
| target.innerHTML, | ||
| ` | ||
| <select> | ||
| <option value="">Default</option> | ||
| </select> | ||
|
|
||
| <select> | ||
| <option value="">Default</option> | ||
| </select> | ||
|
|
||
| <select> | ||
| <option value="">Default</option> | ||
| </select> | ||
|
|
||
| <select> | ||
| <option value="">Default</option> | ||
| </select> | ||
|
|
||
| <select> | ||
| <option value="">Default</option> | ||
| </select> | ||
|
|
||
| <button>update reactive spread</button> | ||
| ` | ||
| ); | ||
|
|
||
| const btn = target.querySelector('button'); | ||
| btn?.click(); | ||
| flushSync(); | ||
|
|
||
| assert.htmlEqual( | ||
| target.innerHTML, | ||
| ` | ||
| <select> | ||
| <option value="">Default</option> | ||
| </select> | ||
|
|
||
| <select> | ||
| <option value="">Default</option> | ||
| </select> | ||
|
|
||
| <select> | ||
| <option value="">Default</option> | ||
| </select> | ||
|
|
||
| <select> | ||
| <option value="">Default</option> | ||
| </select> | ||
|
|
||
| <select> | ||
| <option>Default</option> | ||
| </select> | ||
|
|
||
| <button>update reactive spread</button> | ||
| ` | ||
| ); | ||
| } | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| <script> | ||
| let nonreactive = undefined; | ||
| let reactive = $state(); | ||
| let nonreactive_spread = { value: undefined }; | ||
| let reactive_spread = $state({ value: undefined }); | ||
| </script> | ||
|
|
||
| <select> | ||
| <option value={undefined}>Default</option> | ||
| </select> | ||
|
|
||
| <select> | ||
| <option value={nonreactive}>Default</option> | ||
| </select> | ||
|
|
||
| <select> | ||
| <option value={reactive}>Default</option> | ||
| </select> | ||
|
|
||
| <select> | ||
| <option {...nonreactive_spread}>Default</option> | ||
| </select> | ||
|
|
||
| <select> | ||
| <option {...reactive_spread}>Default</option> | ||
| </select> | ||
|
|
||
| <button onclick={() => (reactive_spread = {})}>update reactive spread</button> |
Uh oh!
There was an error while loading. Please reload this page.