From be0818552de9d282f5033bf8d1be3de34ce01794 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 14 Jul 2025 16:20:14 -0400 Subject: [PATCH 1/7] chore: squelch hydration warning in test suite (#16336) --- .../runtime-runes/samples/error-boundary-9/_config.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-9/_config.js b/packages/svelte/tests/runtime-runes/samples/error-boundary-9/_config.js index 9664c233b7d5..9bb12e768d8d 100644 --- a/packages/svelte/tests/runtime-runes/samples/error-boundary-9/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-9/_config.js @@ -1,7 +1,13 @@ import { test } from '../../test'; export default test({ - test({ assert, target, logs }) { + test({ assert, target, logs, warnings, variant }) { + if (variant === 'hydrate') { + assert.deepEqual(warnings, [ + 'Hydration failed because the initial UI does not match what was rendered on the server' + ]); + } + assert.deepEqual(logs, ['error caught']); assert.htmlEqual(target.innerHTML, `
Error!
`); } From 96ff125fcfa8d60af978e01e300224386d86ef01 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 14 Jul 2025 21:38:53 -0400 Subject: [PATCH 2/7] chore: fix error boundary test (#16368) --- .../runtime-runes/samples/error-boundary-9/_config.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/svelte/tests/runtime-runes/samples/error-boundary-9/_config.js b/packages/svelte/tests/runtime-runes/samples/error-boundary-9/_config.js index 9bb12e768d8d..9664c233b7d5 100644 --- a/packages/svelte/tests/runtime-runes/samples/error-boundary-9/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/error-boundary-9/_config.js @@ -1,13 +1,7 @@ import { test } from '../../test'; export default test({ - test({ assert, target, logs, warnings, variant }) { - if (variant === 'hydrate') { - assert.deepEqual(warnings, [ - 'Hydration failed because the initial UI does not match what was rendered on the server' - ]); - } - + test({ assert, target, logs }) { assert.deepEqual(logs, ['error caught']); assert.htmlEqual(target.innerHTML, `
Error!
`); } From 2e49783afa5185dc8ccdfede9feee3fe15248294 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 14 Jul 2025 21:45:30 -0400 Subject: [PATCH 3/7] docs: fix code snippets (#16367) --- .../.generated/client-warnings.md | 19 +++++++++++++++++++ .../messages/client-warnings/warnings.md | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) 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/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()); From 79904b711329db9ad1927df2d5bde7eeb2909815 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 14 Jul 2025 22:08:08 -0400 Subject: [PATCH 4/7] docs: tweak (#16369) --- documentation/docs/02-runes/02-$state.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: From 1e4547b005d875aa1da0fa4f2f367a949700d93e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 14 Jul 2025 22:24:39 -0400 Subject: [PATCH 5/7] chore: add `@since` tags for `settled` and `experimental.async` (#16371) * chore: add `@since` tag for `settled` * same for compiler options * regenerate --- packages/svelte/src/compiler/types/index.d.ts | 10 ++++++-- .../svelte/src/internal/client/runtime.js | 1 + packages/svelte/types/index.d.ts | 23 +++++++++++++++---- 3 files changed, 27 insertions(+), 7 deletions(-) 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/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/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; }; } From 9134caca242f29170125e1c9fcb71bd8e2473ae1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 15 Jul 2025 11:55:30 -0400 Subject: [PATCH 6/7] fix: only skip updating bound `` if the input was the source of the change (#16373) * fix: only skip updating bound `` if the input was the source of the change * import Batch as type, not value --- .changeset/healthy-garlics-do.md | 5 +++ .../client/dom/elements/bindings/input.js | 14 ++++++- .../binding-update-while-focused/_config.js | 40 +++++++++++++++++++ .../binding-update-while-focused/main.svelte | 16 ++++++++ 4 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 .changeset/healthy-garlics-do.md create mode 100644 packages/svelte/tests/runtime-runes/samples/binding-update-while-focused/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/binding-update-while-focused/main.svelte diff --git a/.changeset/healthy-garlics-do.md b/.changeset/healthy-garlics-do.md new file mode 100644 index 000000000000..c27ace34de35 --- /dev/null +++ b/.changeset/healthy-garlics-do.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: only skip updating bound `` if the input was the source of the change 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/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}

From b23f1e0a43a06263e52b07296de57a12e53da750 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 11:57:52 -0400 Subject: [PATCH 7/7] Version Packages (#16374) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/healthy-garlics-do.md | 5 ----- packages/svelte/CHANGELOG.md | 6 ++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 .changeset/healthy-garlics-do.md diff --git a/.changeset/healthy-garlics-do.md b/.changeset/healthy-garlics-do.md deleted file mode 100644 index c27ace34de35..000000000000 --- a/.changeset/healthy-garlics-do.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: only skip updating bound `` if the input was the source of the change 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/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/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'; 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