diff --git a/documentation/docs/02-runes/02-$state.md b/documentation/docs/02-runes/02-$state.md index 7c4571e575e3..8d3510e50c00 100644 --- a/documentation/docs/02-runes/02-$state.md +++ b/documentation/docs/02-runes/02-$state.md @@ -50,7 +50,7 @@ todos.push({ }); ``` -> [!NOTE] When you update properties of proxies, the original object is _not_ mutated. If you desire to use your own proxy handlers in a state proxy, [you should wrap the object _after_ wrapping it in `$state`](https://svelte.dev/playground/hello-world?version=latest#H4sIAAAAAAAACpWR3WoDIRCFX2UqhWyIJL3erAulL9C7XnQLMe5ksbUqOpsfln33YuyGFNJC8UKdc2bOhw7Myk9kJXsJ0nttO9jcR5KEG9AWJDwHdzwxznbaYGTl68Do5JM_FRifuh-9X8Y9Gkq1rYx4q66cJbQUWcmqqIL2VDe2IYMEbvuOikBADi-GJDSkXG-phId0G-frye2DO2psQYDFQ0Ys8gQO350dUkEydEg82T0GOs0nsSG9g2IqgxACZueo2ZUlpdvoDC6N64qsg1QKY8T2bpZp8gpIfbCQ85Zn50Ud82HkeY83uDjspenxv3jXcSDyjPWf9L1vJf0GH666J-jLu1ery4dV257IWXBWGa0-xFDMQdTTn2ScxWKsn86ROsLwQxqrVR5QM84Ij8TKFD2-cUZSm4O2LSt30kQcvwCgCmfZnAIAAA==). +> [!NOTE] When you update properties of proxies, the original object is _not_ mutated. If you need to use your own proxy handlers in a state proxy, [you should wrap the object _after_ wrapping it in `$state`](https://svelte.dev/playground/hello-world?version=latest#H4sIAAAAAAAACpWR3WoDIRCFX2UqhWyIJL3erAulL9C7XnQLMe5ksbUqOpsfln33YuyGFNJC8UKdc2bOhw7Myk9kJXsJ0nttO9jcR5KEG9AWJDwHdzwxznbaYGTl68Do5JM_FRifuh-9X8Y9Gkq1rYx4q66cJbQUWcmqqIL2VDe2IYMEbvuOikBADi-GJDSkXG-phId0G-frye2DO2psQYDFQ0Ys8gQO350dUkEydEg82T0GOs0nsSG9g2IqgxACZueo2ZUlpdvoDC6N64qsg1QKY8T2bpZp8gpIfbCQ85Zn50Ud82HkeY83uDjspenxv3jXcSDyjPWf9L1vJf0GH666J-jLu1ery4dV257IWXBWGa0-xFDMQdTTn2ScxWKsn86ROsLwQxqrVR5QM84Ij8TKFD2-cUZSm4O2LSt30kQcvwCgCmfZnAIAAA==). Note that if you destructure a reactive value, the references are not reactive — as in normal JavaScript, they are evaluated at the point of destructuring: diff --git a/documentation/docs/98-reference/.generated/client-warnings.md b/documentation/docs/98-reference/.generated/client-warnings.md index 1c75faef5377..7548428e9784 100644 --- a/documentation/docs/98-reference/.generated/client-warnings.md +++ b/documentation/docs/98-reference/.generated/client-warnings.md @@ -43,6 +43,9 @@ Detected reactivity loss when reading `%name%`. This happens when state is read Svelte's signal-based reactivity works by tracking which bits of state are read when a template or `$derived(...)` expression executes. If an expression contains an `await`, Svelte transforms it such that any state _after_ the `await` is also tracked — in other words, in a case like this... ```js +let a = Promise.resolve(1); +let b = 2; +// ---cut--- let total = $derived(await a + b); ``` @@ -51,6 +54,9 @@ let total = $derived(await a + b); This does _not_ apply to an `await` that is not 'visible' inside the expression. In a case like this... ```js +let a = Promise.resolve(1); +let b = 2; +// ---cut--- async function sum() { return await a + b; } @@ -61,6 +67,13 @@ let total = $derived(await sum()); ...`total` will depend on `a` (which is read immediately) but not `b` (which is not). The solution is to pass the values into the function: ```js +let a = Promise.resolve(1); +let b = 2; +// ---cut--- +/** + * @param {Promise} a + * @param {number} b + */ async function sum(a, b) { return await a + b; } @@ -77,6 +90,9 @@ An async derived, `%name%` (%location%) was not read immediately after it resolv In a case like this... ```js +async function one() { return 1 } +async function two() { return 2 } +// ---cut--- let a = $derived(await one()); let b = $derived(await two()); ``` @@ -88,6 +104,9 @@ let b = $derived(await two()); You can solve this by creating the promises first and _then_ awaiting them: ```js +async function one() { return 1 } +async function two() { return 2 } +// ---cut--- let aPromise = $derived(one()); let bPromise = $derived(two()); diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index dfa3376e2531..d9f1a2629ccf 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,11 @@ # svelte +## 5.36.1 + +### Patch Changes + +- fix: only skip updating bound `` if the input was the source of the change ([#16373](https://github.com/sveltejs/svelte/pull/16373)) + ## 5.36.0 ### Minor Changes diff --git a/packages/svelte/messages/client-warnings/warnings.md b/packages/svelte/messages/client-warnings/warnings.md index 498c19a54756..13d9bfcd3bcd 100644 --- a/packages/svelte/messages/client-warnings/warnings.md +++ b/packages/svelte/messages/client-warnings/warnings.md @@ -37,6 +37,9 @@ function add() { Svelte's signal-based reactivity works by tracking which bits of state are read when a template or `$derived(...)` expression executes. If an expression contains an `await`, Svelte transforms it such that any state _after_ the `await` is also tracked — in other words, in a case like this... ```js +let a = Promise.resolve(1); +let b = 2; +// ---cut--- let total = $derived(await a + b); ``` @@ -45,6 +48,9 @@ let total = $derived(await a + b); This does _not_ apply to an `await` that is not 'visible' inside the expression. In a case like this... ```js +let a = Promise.resolve(1); +let b = 2; +// ---cut--- async function sum() { return await a + b; } @@ -55,6 +61,13 @@ let total = $derived(await sum()); ...`total` will depend on `a` (which is read immediately) but not `b` (which is not). The solution is to pass the values into the function: ```js +let a = Promise.resolve(1); +let b = 2; +// ---cut--- +/** + * @param {Promise} a + * @param {number} b + */ async function sum(a, b) { return await a + b; } @@ -69,6 +82,9 @@ let total = $derived(await sum(a, b)); In a case like this... ```js +async function one() { return 1 } +async function two() { return 2 } +// ---cut--- let a = $derived(await one()); let b = $derived(await two()); ``` @@ -80,6 +96,9 @@ let b = $derived(await two()); You can solve this by creating the promises first and _then_ awaiting them: ```js +async function one() { return 1 } +async function two() { return 2 } +// ---cut--- let aPromise = $derived(one()); let bPromise = $derived(two()); diff --git a/packages/svelte/package.json b/packages/svelte/package.json index a9fead571d10..aa76abaae426 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.36.0", + "version": "5.36.1", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/compiler/types/index.d.ts b/packages/svelte/src/compiler/types/index.d.ts index c4f41b724ac2..6211e69bd3e1 100644 --- a/packages/svelte/src/compiler/types/index.d.ts +++ b/packages/svelte/src/compiler/types/index.d.ts @@ -224,9 +224,15 @@ export interface ModuleCompileOptions { * Use this to filter out warnings. Return `true` to keep the warning, `false` to discard it. */ warningFilter?: (warning: Warning) => boolean; - /** Experimental options */ + /** + * Experimental options + * @since 5.36 + */ experimental?: { - /** Allow `await` keyword in deriveds, template expressions, and the top level of components */ + /** + * Allow `await` keyword in deriveds, template expressions, and the top level of components + * @since 5.36 + */ async?: boolean; }; } diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/input.js b/packages/svelte/src/internal/client/dom/elements/bindings/input.js index 569d1179e602..7c1fccea0fbc 100644 --- a/packages/svelte/src/internal/client/dom/elements/bindings/input.js +++ b/packages/svelte/src/internal/client/dom/elements/bindings/input.js @@ -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: // diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index b5f6822207ee..9fdb87239b45 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -526,6 +526,7 @@ export async function tick() { * Returns a promise that resolves once any state changes, and asynchronous work resulting from them, * have resolved and the DOM has been updated * @returns {Promise} + * @since 5.36 */ export function settled() { return Batch.ensure().settled(); diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 248b1a3f112c..f50b7d23cc16 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -4,5 +4,5 @@ * The current version, as set in package.json. * @type {string} */ -export const VERSION = '5.36.0'; +export const VERSION = '5.36.1'; export const PUBLIC_VERSION = '5'; diff --git a/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused/_config.js b/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused/_config.js new file mode 100644 index 000000000000..1d2e6dd4707c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused/_config.js @@ -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, + ` + +

value = 2

+ ` + ); + + flushSync(() => { + input.focus(); + input.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true })); + }); + assert.equal(input.value, '1'); + assert.htmlEqual( + target.innerHTML, + ` + +

value = 1

+ ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused/main.svelte b/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused/main.svelte new file mode 100644 index 000000000000..4cc174e404aa --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused/main.svelte @@ -0,0 +1,16 @@ + + + + +

value = {value}

diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 72eb871db628..60795e6681fb 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -528,7 +528,8 @@ declare module 'svelte' { /** * Returns a promise that resolves once any state changes, and asynchronous work resulting from them, * have resolved and the DOM has been updated - * */ + * @since 5.36 + */ export function settled(): Promise; /** * When used inside a [`$derived`](https://svelte.dev/docs/svelte/$derived) or [`$effect`](https://svelte.dev/docs/svelte/$effect), @@ -1116,9 +1117,15 @@ declare module 'svelte/compiler' { * Use this to filter out warnings. Return `true` to keep the warning, `false` to discard it. */ warningFilter?: (warning: Warning) => boolean; - /** Experimental options */ + /** + * Experimental options + * @since 5.36 + */ experimental?: { - /** Allow `await` keyword in deriveds, template expressions, and the top level of components */ + /** + * Allow `await` keyword in deriveds, template expressions, and the top level of components + * @since 5.36 + */ async?: boolean; }; } @@ -3031,9 +3038,15 @@ declare module 'svelte/types/compiler/interfaces' { * Use this to filter out warnings. Return `true` to keep the warning, `false` to discard it. */ warningFilter?: (warning: Warning_1) => boolean; - /** Experimental options */ + /** + * Experimental options + * @since 5.36 + */ experimental?: { - /** Allow `await` keyword in deriveds, template expressions, and the top level of components */ + /** + * Allow `await` keyword in deriveds, template expressions, and the top level of components + * @since 5.36 + */ async?: boolean; }; } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy