fix: only skip updating bound `<input>` if the input was the source of the change (#16373)

* fix: only skip updating bound `<input>` if the input was the source of the change

* import Batch as type, not value
pull/16374/head
Rich Harris 2 months ago committed by GitHub
parent 1e4547b005
commit 9134caca24
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: only skip updating bound `<input>` if the input was the source of the change

@ -1,3 +1,4 @@
/** @import { Batch } from '../../../reactivity/batch.js' */
import { DEV } from 'esm-env';
import { render_effect, teardown } from '../../../reactivity/effects.js';
import { listen_to_event_and_reset_event } from './shared.js';
@ -7,6 +8,7 @@ import { queue_micro_task } from '../../task.js';
import { hydrating } from '../../hydration.js';
import { untrack } from '../../../runtime.js';
import { is_runes } from '../../../context.js';
import { current_batch } from '../../../reactivity/batch.js';
/**
* @param {HTMLInputElement} input
@ -17,6 +19,8 @@ import { is_runes } from '../../../context.js';
export function bind_value(input, get, set = get) {
var runes = is_runes();
var batches = new WeakSet();
listen_to_event_and_reset_event(input, 'input', (is_reset) => {
if (DEV && input.type === 'checkbox') {
// TODO should this happen in prod too?
@ -28,6 +32,10 @@ export function bind_value(input, get, set = get) {
value = is_numberlike_input(input) ? to_number(value) : value;
set(value);
if (current_batch !== null) {
batches.add(current_batch);
}
// In runes mode, respect any validation in accessors (doesn't apply in legacy mode,
// because we use mutable state which ensures the render effect always runs)
if (runes && value !== (value = get())) {
@ -54,6 +62,10 @@ export function bind_value(input, get, set = get) {
(untrack(get) == null && input.value)
) {
set(is_numberlike_input(input) ? to_number(input.value) : input.value);
if (current_batch !== null) {
batches.add(current_batch);
}
}
render_effect(() => {
@ -64,7 +76,7 @@ export function bind_value(input, get, set = get) {
var value = get();
if (input === document.activeElement) {
if (input === document.activeElement && batches.has(/** @type {Batch} */ (current_batch))) {
// Never rewrite the contents of a focused input. We can get here if, for example,
// an update is deferred because of async work depending on the input:
//

@ -0,0 +1,40 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
mode: ['client', 'hydrate'],
async test({ assert, target }) {
const [input] = target.querySelectorAll('input');
flushSync(() => {
input.focus();
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true }));
});
assert.equal(input.value, '2');
assert.htmlEqual(
target.innerHTML,
`
<label>
<input /> arrow up/down
</label>
<p>value = 2</p>
`
);
flushSync(() => {
input.focus();
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
});
assert.equal(input.value, '1');
assert.htmlEqual(
target.innerHTML,
`
<label>
<input /> arrow up/down
</label>
<p>value = 1</p>
`
);
}
});

@ -0,0 +1,16 @@
<script>
let value = $state('1');
function onkeydown (e) {
let _v = parseFloat(value);
if (e.key === 'ArrowUp') _v += 1;
else if (e.key === 'ArrowDown') _v -= 1;
value = _v.toString();
}
</script>
<label>
<input bind:value {onkeydown} /> arrow up/down
</label>
<p>value = {value}</p>
Loading…
Cancel
Save