mirror of https://github.com/sveltejs/svelte
fix: don't overwrite an unchanged spread `value` (#18864)
Fixes #18862 When a `value` is applied through a spread (`<input {...props} />`), `set_attributes` always writes `element.value`, even if the element already has that value. Writing `value` on a number input clears any incomplete text: typing `250.` makes `input.value` `''` (the text is still visible, but `validity.badInput` is `true`). If the spread `value` then becomes `''` too, writing `''` erases what the user typed. This is what happens with SvelteKit remote forms: `fields.quantity.as('number')` coerces `''` to `undefined`, the spread `value` becomes `''`, and Svelte writes it back to the input. When `.` is typed: - Chromium clears the input in every locale, because `250.` is always incomplete input there. - WebKit/Safari clears it in locales where `.` is not the decimal separator (e.g. `fr-CH`), which is where the reporter saw it. A plain `<input type="number">` never clears. Neither does the same `value` in Svelte without a spread (`value={...}`), because `set_value` already skips writing when `element.value === value`. This PR adds the same check to the spread path. Svelte 3 had this check too (#3426, #3495); it was lost in Svelte 5. The first write still always happens. Writing `value` sets the input's dirty value flag, and without that flag a `defaultValue` applied afterwards would change the current value (covered by `form-default-value-spread`). This also likely affects sveltejs/kit#16270 (same symptom, plus a cursor jump caused by redundant writes). That issue is handled on the kit side in sveltejs/kit#16320; this PR is independent of it. The test runs in a real browser (`runtime-browser`). It types with `document.execCommand('insertText')`, which goes through Chromium's own editing, so `250.` is real incomplete input. The locale is not mocked. The test fails without this change and passes with it. ### Before submitting the PR, please make sure you do the following - [x] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs - [x] Prefix your PR title with `feat:`, `fix:`, `chore:`, or `docs:`. - [x] This message body should clearly illustrate what problems it solves. - [x] Ideally, include a test that fails without this PR but passes with it. - [x] If this PR changes code within `packages/svelte/src`, add a changeset (`npx changeset`). ### Tests and linting - [x] Run the tests with `pnpm test` and lint the project with `pnpm lint` --------- Co-authored-by: Paolo Ricciuti <ricciutipaolo@gmail.com>pull/18821/merge
parent
325620ba63
commit
38098a9311
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'svelte': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: don't overwrite an unchanged spread `value`, preserving incomplete number input
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
import { flushSync } from 'svelte';
|
||||||
|
import { ok, test } from '../../assert';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
async test({ assert, target }) {
|
||||||
|
const input = target.querySelector('input');
|
||||||
|
ok(input);
|
||||||
|
|
||||||
|
input.focus();
|
||||||
|
|
||||||
|
// we need to use `document.execCommand('insertText', false, ...)` to simulate user input
|
||||||
|
// because directly setting an invalid value to `input.value` would simply clear the input
|
||||||
|
// and dispatching an event would not update the input correctly
|
||||||
|
document.execCommand('insertText', false, '1');
|
||||||
|
flushSync();
|
||||||
|
// `1e` is incomplete on every platform, unlike `1.` which Chromium on Linux accepts as `1`
|
||||||
|
document.execCommand('insertText', false, 'e');
|
||||||
|
flushSync();
|
||||||
|
|
||||||
|
assert.equal(input.value, '');
|
||||||
|
assert.equal(input.validity.badInput, true);
|
||||||
|
|
||||||
|
document.execCommand('insertText', false, '5');
|
||||||
|
flushSync();
|
||||||
|
|
||||||
|
assert.equal(input.value, '1e5');
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
<script>
|
||||||
|
let value = $state('');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input {...{ type: 'number', value }} oninput={(e) => (value = e.currentTarget.value)} />
|
||||||
Loading…
Reference in new issue