From 743d52a893cb67e10e1ee09707e7852459febf2f Mon Sep 17 00:00:00 2001 From: 7nik Date: Sat, 14 Feb 2026 14:30:01 +0200 Subject: [PATCH 01/10] fix: don't crash on undefined `document.contentType` (#17707) closes #17706 --- .changeset/curvy-pants-hide.md | 5 +++++ packages/svelte/src/internal/client/constants.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/curvy-pants-hide.md diff --git a/.changeset/curvy-pants-hide.md b/.changeset/curvy-pants-hide.md new file mode 100644 index 0000000000..86ff3957b2 --- /dev/null +++ b/.changeset/curvy-pants-hide.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't crash on undefined `document.contentType` diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index 524f35bccf..7b118a5b5f 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -67,7 +67,7 @@ export const STALE_REACTION = new (class StaleReactionError extends Error { message = 'The reaction that called `getAbortSignal()` was re-run or destroyed'; })(); -export const IS_XHTML = /* @__PURE__ */ globalThis.document?.contentType.includes('xml') ?? false; +export const IS_XHTML = /* @__PURE__ */ globalThis.document?.contentType?.includes('xml') ?? false; export const ELEMENT_NODE = 1; export const TEXT_NODE = 3; export const COMMENT_NODE = 8; From 7299ffc39fad9135f3f4d0a804fe08987ace2ff5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 14 Feb 2026 15:45:20 -0500 Subject: [PATCH 02/10] Version Packages (#17704) This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## svelte@5.51.1 ### Patch Changes - fix: don't crash on undefined `document.contentType` ([#17707](https://github.com/sveltejs/svelte/pull/17707)) - fix: use symbols for encapsulated event delegation ([#17703](https://github.com/sveltejs/svelte/pull/17703)) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/curvy-pants-hide.md | 5 ----- .changeset/purple-eagles-enjoy.md | 5 ----- packages/svelte/CHANGELOG.md | 8 ++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 5 files changed, 10 insertions(+), 12 deletions(-) delete mode 100644 .changeset/curvy-pants-hide.md delete mode 100644 .changeset/purple-eagles-enjoy.md diff --git a/.changeset/curvy-pants-hide.md b/.changeset/curvy-pants-hide.md deleted file mode 100644 index 86ff3957b2..0000000000 --- a/.changeset/curvy-pants-hide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: don't crash on undefined `document.contentType` diff --git a/.changeset/purple-eagles-enjoy.md b/.changeset/purple-eagles-enjoy.md deleted file mode 100644 index 318f85a946..0000000000 --- a/.changeset/purple-eagles-enjoy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: use symbols for encapsulated event delegation diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 542ae4c8f6..489482522d 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,13 @@ # svelte +## 5.51.1 + +### Patch Changes + +- fix: don't crash on undefined `document.contentType` ([#17707](https://github.com/sveltejs/svelte/pull/17707)) + +- fix: use symbols for encapsulated event delegation ([#17703](https://github.com/sveltejs/svelte/pull/17703)) + ## 5.51.0 ### Minor Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 3b650af156..0199c9263c 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.51.0", + "version": "5.51.1", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 688c22df03..f9a6708e6f 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.51.0'; +export const VERSION = '5.51.1'; export const PUBLIC_VERSION = '5'; From 01f1937a98ff26589524fb1c8bcab0de2649133f Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Sun, 15 Feb 2026 10:44:23 +0100 Subject: [PATCH 03/10] fix: take async into consideration for dev delegated handlers (#17710) Closes #17709 ### 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` --- .changeset/lemon-mails-guess.md | 5 +++++ .../phases/3-transform/client/visitors/shared/events.js | 3 ++- .../samples/event-handler-async-delegated/_config.js | 9 +++++++++ .../samples/event-handler-async-delegated/main.svelte | 8 ++++++++ 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 .changeset/lemon-mails-guess.md create mode 100644 packages/svelte/tests/runtime-runes/samples/event-handler-async-delegated/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/event-handler-async-delegated/main.svelte diff --git a/.changeset/lemon-mails-guess.md b/.changeset/lemon-mails-guess.md new file mode 100644 index 0000000000..9156bac953 --- /dev/null +++ b/.changeset/lemon-mails-guess.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: take async into consideration for dev delegated handlers diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/events.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/events.js index 9485a33b3d..f01bf08694 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/events.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/events.js @@ -70,7 +70,8 @@ export function build_event(context, event_name, handler, capture, passive, dele fn = b.function( b.id(name), handler.params, - handler.body.type === 'BlockStatement' ? handler.body : b.block([b.return(handler.body)]) + handler.body.type === 'BlockStatement' ? handler.body : b.block([b.return(handler.body)]), + handler.async ); } diff --git a/packages/svelte/tests/runtime-runes/samples/event-handler-async-delegated/_config.js b/packages/svelte/tests/runtime-runes/samples/event-handler-async-delegated/_config.js new file mode 100644 index 0000000000..eb5b2a1675 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/event-handler-async-delegated/_config.js @@ -0,0 +1,9 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true + }, + async test() {} +}); diff --git a/packages/svelte/tests/runtime-runes/samples/event-handler-async-delegated/main.svelte b/packages/svelte/tests/runtime-runes/samples/event-handler-async-delegated/main.svelte new file mode 100644 index 0000000000..bffcad6059 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/event-handler-async-delegated/main.svelte @@ -0,0 +1,8 @@ + From dd9fc0d1ad948f264a48e2ebdf4f7f5fb8ddb876 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 15 Feb 2026 11:00:50 +0100 Subject: [PATCH 04/10] Warn on non-destructured `$props()` reads in runes mode (#17708) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Non-destructured `$props()` access in runes mode silently skipped the `state_referenced_locally` warning, leading to missed guidance when users read `props` via identifiers or member expressions. - **Analyzer behavior** - Include `rest_prop` bindings in `state_referenced_locally` detection so reads of `$props()` identifiers warn consistently with destructured props. - **Validation coverage** - Add a validator fixture for `$props()` identifiers and update the `props-identifier` snapshot expectations to capture the new warnings. Example: ```svelte ```
Original prompt > > ---- > > *This section details on the original issue you should resolve* > > False negative for `state_referenced_locally` warning on not destructured `$props` access? > ### Describe the bug > > I was looking for a workaround for sveltejs/svelte#17669 and thought of not destructuring the `$props` directly; to my surprise there were no warnings at all. > > > ### Reproduction > > ```js > const props = $props(); > const { model } = props; // missing warning > > const value = props.model.value; // missing warning > ``` > > [Playground](https://svelte.dev/playground/untitled?version=5.50.2#H4sIAAAAAAAACn2QT4vCQAzFv0oIe1CQ9l51YY97lj1tPYxtXAam6TAT_1H63U0HUax1j3nvJeT3OmTTEBb4w2LFUY0L3FtHEYvfDuXiB28QVL8lv7zP4pGcDNrORJrSq5aFWPQMrmIVrJfPkktROQp00LQ1OehhDR8-tD7O5ku174GjcQdSM8WyNC0hz4HOniqhGk4msOW_klf54zrPNkTwzVUbgsZuz8z1G6GzYCHhQP3iDdV47Zltwv2XMEGN6Cbgk5vIGhujAj3AXstI4WxcycvivZFn7q1OxrqT5RqLvXGR-itXywVk_AEAAA) > > ### Logs > > ```shell > > ``` > > ### System Info > > ```shell > REPL - Svelte v.5.50.2 > ``` > > ### Severity > > annoyance > > ## Comments on the Issue (you are @copilot in this section) > > > >
- Fixes sveltejs/svelte#17685 --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Rich-Harris <1162160+Rich-Harris@users.noreply.github.com> Co-authored-by: Rich Harris Co-authored-by: Paolo Ricciuti --- .changeset/loose-baboons-visit.md | 5 + .../phases/2-analyze/visitors/Identifier.js | 3 +- .../client/index.svelte.warnings.json | 142 ++++++++++++++++++ .../server/index.svelte.warnings.json | 142 ++++++++++++++++++ .../input.svelte | 6 + .../warnings.json | 26 ++++ 6 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 .changeset/loose-baboons-visit.md create mode 100644 packages/svelte/tests/snapshot/samples/props-identifier/_expected/client/index.svelte.warnings.json create mode 100644 packages/svelte/tests/snapshot/samples/props-identifier/_expected/server/index.svelte.warnings.json create mode 100644 packages/svelte/tests/validator/samples/state-referenced-locally-props-identifier/input.svelte create mode 100644 packages/svelte/tests/validator/samples/state-referenced-locally-props-identifier/warnings.json diff --git a/.changeset/loose-baboons-visit.md b/.changeset/loose-baboons-visit.md new file mode 100644 index 0000000000..09c50deb5e --- /dev/null +++ b/.changeset/loose-baboons-visit.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: emit state_referenced_locally warning for non-destructured props diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js index 9daae33dd7..5c1e8031b8 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js @@ -115,7 +115,8 @@ export function Identifier(node, context) { !should_proxy(binding.initial.arguments[0], context.state.scope)))) || binding.kind === 'raw_state' || binding.kind === 'derived' || - binding.kind === 'prop') && + binding.kind === 'prop' || + binding.kind === 'rest_prop') && // We're only concerned with reads here (parent.type !== 'AssignmentExpression' || parent.left !== node) && parent.type !== 'UpdateExpression' diff --git a/packages/svelte/tests/snapshot/samples/props-identifier/_expected/client/index.svelte.warnings.json b/packages/svelte/tests/snapshot/samples/props-identifier/_expected/client/index.svelte.warnings.json new file mode 100644 index 0000000000..81149a92f8 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/props-identifier/_expected/client/index.svelte.warnings.json @@ -0,0 +1,142 @@ +[ + { + "code": "state_referenced_locally", + "message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?\nhttps://svelte.dev/e/state_referenced_locally", + "filename": "packages/svelte/tests/snapshot/samples/props-identifier/index.svelte", + "start": { + "line": 3, + "column": 1, + "character": 33 + }, + "end": { + "line": 3, + "column": 6, + "character": 38 + }, + "position": [ + 33, + 38 + ], + "frame": "1: " + }, + { + "code": "state_referenced_locally", + "message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?\nhttps://svelte.dev/e/state_referenced_locally", + "filename": "packages/svelte/tests/snapshot/samples/props-identifier/index.svelte", + "start": { + "line": 9, + "column": 1, + "character": 120 + }, + "end": { + "line": 9, + "column": 6, + "character": 125 + }, + "position": [ + 120, + 125 + ], + "frame": " 7: props.a = true;\n 8: props[a] = true;\n 9: props;\n ^\n10: \n11: " + } +] \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/props-identifier/_expected/server/index.svelte.warnings.json b/packages/svelte/tests/snapshot/samples/props-identifier/_expected/server/index.svelte.warnings.json new file mode 100644 index 0000000000..81149a92f8 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/props-identifier/_expected/server/index.svelte.warnings.json @@ -0,0 +1,142 @@ +[ + { + "code": "state_referenced_locally", + "message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?\nhttps://svelte.dev/e/state_referenced_locally", + "filename": "packages/svelte/tests/snapshot/samples/props-identifier/index.svelte", + "start": { + "line": 3, + "column": 1, + "character": 33 + }, + "end": { + "line": 3, + "column": 6, + "character": 38 + }, + "position": [ + 33, + 38 + ], + "frame": "1: " + }, + { + "code": "state_referenced_locally", + "message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?\nhttps://svelte.dev/e/state_referenced_locally", + "filename": "packages/svelte/tests/snapshot/samples/props-identifier/index.svelte", + "start": { + "line": 9, + "column": 1, + "character": 120 + }, + "end": { + "line": 9, + "column": 6, + "character": 125 + }, + "position": [ + 120, + 125 + ], + "frame": " 7: props.a = true;\n 8: props[a] = true;\n 9: props;\n ^\n10: \n11: " + } +] \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/state-referenced-locally-props-identifier/input.svelte b/packages/svelte/tests/validator/samples/state-referenced-locally-props-identifier/input.svelte new file mode 100644 index 0000000000..980debdfaf --- /dev/null +++ b/packages/svelte/tests/validator/samples/state-referenced-locally-props-identifier/input.svelte @@ -0,0 +1,6 @@ + diff --git a/packages/svelte/tests/validator/samples/state-referenced-locally-props-identifier/warnings.json b/packages/svelte/tests/validator/samples/state-referenced-locally-props-identifier/warnings.json new file mode 100644 index 0000000000..b1385037e1 --- /dev/null +++ b/packages/svelte/tests/validator/samples/state-referenced-locally-props-identifier/warnings.json @@ -0,0 +1,26 @@ +[ + { + "code": "state_referenced_locally", + "message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?", + "start": { + "line": 3, + "column": 19 + }, + "end": { + "line": 3, + "column": 24 + } + }, + { + "code": "state_referenced_locally", + "message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?", + "start": { + "line": 4, + "column": 15 + }, + "end": { + "line": 4, + "column": 20 + } + } +] From 82265f14965fef95289bdb41baba5ffbbff1fcd6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 15 Feb 2026 11:03:21 +0100 Subject: [PATCH 05/10] Version Packages (#17711) This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## svelte@5.51.2 ### Patch Changes - fix: take async into consideration for dev delegated handlers ([#17710](https://github.com/sveltejs/svelte/pull/17710)) - fix: emit state_referenced_locally warning for non-destructured props ([#17708](https://github.com/sveltejs/svelte/pull/17708)) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/lemon-mails-guess.md | 5 ----- .changeset/loose-baboons-visit.md | 5 ----- packages/svelte/CHANGELOG.md | 8 ++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 5 files changed, 10 insertions(+), 12 deletions(-) delete mode 100644 .changeset/lemon-mails-guess.md delete mode 100644 .changeset/loose-baboons-visit.md diff --git a/.changeset/lemon-mails-guess.md b/.changeset/lemon-mails-guess.md deleted file mode 100644 index 9156bac953..0000000000 --- a/.changeset/lemon-mails-guess.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: take async into consideration for dev delegated handlers diff --git a/.changeset/loose-baboons-visit.md b/.changeset/loose-baboons-visit.md deleted file mode 100644 index 09c50deb5e..0000000000 --- a/.changeset/loose-baboons-visit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: emit state_referenced_locally warning for non-destructured props diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 489482522d..380de4b901 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,13 @@ # svelte +## 5.51.2 + +### Patch Changes + +- fix: take async into consideration for dev delegated handlers ([#17710](https://github.com/sveltejs/svelte/pull/17710)) + +- fix: emit state_referenced_locally warning for non-destructured props ([#17708](https://github.com/sveltejs/svelte/pull/17708)) + ## 5.51.1 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 0199c9263c..ee08526201 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.51.1", + "version": "5.51.2", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index f9a6708e6f..d6d42c7e97 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.51.1'; +export const VERSION = '5.51.2'; export const PUBLIC_VERSION = '5'; From ff70ab1b76f0fea051c932bb2b8eac948ac2de96 Mon Sep 17 00:00:00 2001 From: 7nik Date: Tue, 17 Feb 2026 12:11:05 +0200 Subject: [PATCH 06/10] fix: locate Rollup annontaion friendly to JS downgraders (#17724) Closes #17722 Looks like after downgrading `?.`, `/* @__PURE__ */` may happen in an invalid location. --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/icy-otters-fall.md | 5 +++++ packages/svelte/src/internal/client/constants.js | 5 ++++- packages/svelte/src/internal/client/dom/reconciler.js | 10 +++++----- 3 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 .changeset/icy-otters-fall.md diff --git a/.changeset/icy-otters-fall.md b/.changeset/icy-otters-fall.md new file mode 100644 index 0000000000..b15c282ca4 --- /dev/null +++ b/.changeset/icy-otters-fall.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: locate Rollup annontaion friendly to JS downgraders diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index 7b118a5b5f..3525539f1d 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -67,7 +67,10 @@ export const STALE_REACTION = new (class StaleReactionError extends Error { message = 'The reaction that called `getAbortSignal()` was re-run or destroyed'; })(); -export const IS_XHTML = /* @__PURE__ */ globalThis.document?.contentType?.includes('xml') ?? false; +export const IS_XHTML = + // We gotta write it like this because after downleveling the pure comment may end up in the wrong location + !!globalThis.document?.contentType && + /* @__PURE__ */ globalThis.document.contentType.includes('xml'); export const ELEMENT_NODE = 1; export const TEXT_NODE = 3; export const COMMENT_NODE = 8; diff --git a/packages/svelte/src/internal/client/dom/reconciler.js b/packages/svelte/src/internal/client/dom/reconciler.js index 1f4750ce8f..0ceebd3337 100644 --- a/packages/svelte/src/internal/client/dom/reconciler.js +++ b/packages/svelte/src/internal/client/dom/reconciler.js @@ -2,15 +2,15 @@ import { create_element } from './operations.js'; -const policy = /* @__PURE__ */ globalThis?.window?.trustedTypes?.createPolicy( - 'svelte-trusted-html', - { +const policy = + // We gotta write it like this because after downleveling the pure comment may end up in the wrong location + globalThis?.window?.trustedTypes && + /* @__PURE__ */ globalThis.window.trustedTypes.createPolicy('svelte-trusted-html', { /** @param {string} html */ createHTML: (html) => { return html; } - } -); + }); /** @param {string} html */ function create_trusted_html(html) { From 6557a0a591d7e312103de8ea5c75d988f1b8e7c9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 17 Feb 2026 08:12:09 -0500 Subject: [PATCH 07/10] fix: run effects in pending snippets (#17719) Boundaries are buggy: if the `pending` snippet contains state, changes to that state [won't cause updates](https://svelte.dev/playground/hello-world?version=5.51.2#H4sIAAAAAAAAE22SQW-DMAyF_0qUTSpoE92uFJh223Hate0hJWaNliZRYsoqxH9fQtJW6noD-33Pz4aRKnYAWtIPkFKTQVvJSQZcIPCcPtNOSHC0XI8UTyboQsHXE_VuTOGOIDHUdszBvXqrFYJCb0Mr11phsNmoDUpAYsFpeQTrSE3W25Uv-0bXqxaFVsT0bp8dmewhJ2PobNB7OSQcOrAWuKc-rT4IB8UgcP91dsvyVZRf_IvZK8tJ3VzoInXTiCuDvVVXlYkT5u50k9DtRYfZJd11XGq8FSlKAsPOre4V-uSPDhlC9hIE1fJ6GFXtekRvrlUrRftTjzF25J5q8jrN9xOqtXDwhw14RO7jc5bIzI-3-vihyp3358yeZmFlmpENTGD8CIu0GV_kU7U0TdxmfHBKGON3MqC4UN9ZPsVDBHzOe1bjuEzaad7230j_nyD8Ii3R9jBt_RsTchCK07Jj0sH0B6hNF6aqAgAA): ```svelte

{await push('resolved')}

{#snippet pending()}

{count}

{/snippet}
``` The issue is that the boundary's `this.#effect` has the `BOUNDARY_EFFECT` flag, and `this.#pending_effect` is a child thereof. Instead, `this.#main_effect` should have the flag. (It turns out `this.#failed_effect` _also_ needs the flag, because errors that occur in a `failed` snippet cause the boundary to re-render in its `failed` state, which I found somewhat confusing to be honest. Probably the right choice though.) I was able to simplify the code a bit, too. ~~(Actually now that I think about it do we need `this.#effect` at all? Will check.)~~ ### 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: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/many-dolls-argue.md | 5 + .../internal/client/dom/blocks/boundary.js | 142 +++++++----------- .../src/internal/client/reactivity/batch.js | 17 ++- .../async-boundary-pending-live/_config.js | 38 +++++ .../async-boundary-pending-live/main.svelte | 31 ++++ 5 files changed, 139 insertions(+), 94 deletions(-) create mode 100644 .changeset/many-dolls-argue.md create mode 100644 packages/svelte/tests/runtime-runes/samples/async-boundary-pending-live/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-boundary-pending-live/main.svelte diff --git a/.changeset/many-dolls-argue.md b/.changeset/many-dolls-argue.md new file mode 100644 index 0000000000..54a9cef915 --- /dev/null +++ b/.changeset/many-dolls-argue.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: run effects in pending snippets diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index ef5f0e116d..da70cbb19d 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -1,6 +1,5 @@ /** @import { Effect, Source, TemplateNode, } from '#client' */ import { - BLOCK_EFFECT, BOUNDARY_EFFECT, COMMENT_NODE, DIRTY, @@ -53,7 +52,7 @@ import { set_signal_status } from '../../reactivity/status.js'; * }} BoundaryProps */ -var flags = EFFECT_TRANSPARENT | EFFECT_PRESERVED | BOUNDARY_EFFECT; +var flags = EFFECT_TRANSPARENT | EFFECT_PRESERVED; /** * @param {TemplateNode} node @@ -98,15 +97,10 @@ export class Boundary { /** @type {DocumentFragment | null} */ #offscreen_fragment = null; - /** @type {TemplateNode | null} */ - #pending_anchor = null; - #local_pending_count = 0; #pending_count = 0; #pending_count_update_queued = false; - #is_creating_fallback = false; - /** @type {Set} */ #dirty_effects = new Set(); @@ -142,51 +136,31 @@ export class Boundary { constructor(node, props, children) { this.#anchor = node; this.#props = props; - this.#children = children; - this.parent = /** @type {Effect} */ (active_effect).b; + this.#children = (anchor) => { + var effect = /** @type {Effect} */ (active_effect); - this.is_pending = !!this.#props.pending; + effect.b = this; + effect.f |= BOUNDARY_EFFECT; - this.#effect = block(() => { - /** @type {Effect} */ (active_effect).b = this; + children(anchor); + }; + + this.parent = /** @type {Effect} */ (active_effect).b; + this.#effect = block(() => { if (hydrating) { - const comment = this.#hydrate_open; + const comment = /** @type {Comment} */ (this.#hydrate_open); hydrate_next(); - const server_rendered_pending = - /** @type {Comment} */ (comment).nodeType === COMMENT_NODE && - /** @type {Comment} */ (comment).data === HYDRATION_START_ELSE; - - if (server_rendered_pending) { + if (comment.data === HYDRATION_START_ELSE) { this.#hydrate_pending_content(); } else { this.#hydrate_resolved_content(); - - if (this.#pending_count === 0) { - this.is_pending = false; - } } } else { - var anchor = this.#get_anchor(); - - try { - this.#main_effect = branch(() => children(anchor)); - } catch (error) { - this.error(error); - } - - if (this.#pending_count > 0) { - this.#show_pending_snippet(); - } else { - this.is_pending = false; - } + this.#render(); } - - return () => { - this.#pending_anchor?.remove(); - }; }, flags); if (hydrating) { @@ -206,19 +180,24 @@ export class Boundary { const pending = this.#props.pending; if (!pending) return; + this.is_pending = true; this.#pending_effect = branch(() => pending(this.#anchor)); queue_micro_task(() => { - var anchor = this.#get_anchor(); + var fragment = (this.#offscreen_fragment = document.createDocumentFragment()); + var anchor = create_text(); + + fragment.append(anchor); this.#main_effect = this.#run(() => { Batch.ensure(); return branch(() => this.#children(anchor)); }); - if (this.#pending_count > 0) { - this.#show_pending_snippet(); - } else { + if (this.#pending_count === 0) { + this.#anchor.before(fragment); + this.#offscreen_fragment = null; + pause_effect(/** @type {Effect} */ (this.#pending_effect), () => { this.#pending_effect = null; }); @@ -228,17 +207,28 @@ export class Boundary { }); } - #get_anchor() { - var anchor = this.#anchor; + #render() { + try { + this.is_pending = this.has_pending_snippet(); + this.#pending_count = 0; + this.#local_pending_count = 0; + + this.#main_effect = branch(() => { + this.#children(this.#anchor); + }); - if (this.is_pending) { - this.#pending_anchor = create_text(); - this.#anchor.before(this.#pending_anchor); + if (this.#pending_count > 0) { + var fragment = (this.#offscreen_fragment = document.createDocumentFragment()); + move_effect(this.#main_effect, fragment); - anchor = this.#pending_anchor; + const pending = /** @type {(anchor: Node) => void} */ (this.#props.pending); + this.#pending_effect = branch(() => pending(this.#anchor)); + } else { + this.is_pending = false; + } + } catch (error) { + this.error(error); } - - return anchor; } /** @@ -262,7 +252,8 @@ export class Boundary { } /** - * @param {() => Effect | null} fn + * @template T + * @param {() => T} fn */ #run(fn) { var previous_effect = active_effect; @@ -285,20 +276,6 @@ export class Boundary { } } - #show_pending_snippet() { - const pending = /** @type {(anchor: Node) => void} */ (this.#props.pending); - - if (this.#main_effect !== null) { - this.#offscreen_fragment = document.createDocumentFragment(); - this.#offscreen_fragment.append(/** @type {TemplateNode} */ (this.#pending_anchor)); - move_effect(this.#main_effect, this.#offscreen_fragment); - } - - if (this.#pending_effect === null) { - this.#pending_effect = branch(() => pending(this.#anchor)); - } - } - /** * Updates the pending count associated with the currently visible pending snippet, * if any, such that we can replace the snippet with content once work is done @@ -383,7 +360,7 @@ export class Boundary { // If we have nothing to capture the error, or if we hit an error while // rendering the fallback, re-throw for another boundary to handle - if (this.#is_creating_fallback || (!onerror && !failed)) { + if (!onerror && !failed) { throw error; } @@ -423,31 +400,18 @@ export class Boundary { e.svelte_boundary_reset_onerror(); } - // If the failure happened while flushing effects, current_batch can be null - Batch.ensure(); - - this.#local_pending_count = 0; - if (this.#failed_effect !== null) { pause_effect(this.#failed_effect, () => { this.#failed_effect = null; }); } - // we intentionally do not try to find the nearest pending boundary. If this boundary has one, we'll render it on reset - // but it would be really weird to show the parent's boundary on a child reset. - this.is_pending = this.has_pending_snippet(); + this.#run(() => { + // If the failure happened while flushing effects, current_batch can be null + Batch.ensure(); - this.#main_effect = this.#run(() => { - this.#is_creating_fallback = false; - return branch(() => this.#children(this.#anchor)); + this.#render(); }); - - if (this.#pending_count > 0) { - this.#show_pending_snippet(); - } else { - this.is_pending = false; - } }; queue_micro_task(() => { @@ -462,10 +426,16 @@ export class Boundary { if (failed) { this.#failed_effect = this.#run(() => { Batch.ensure(); - this.#is_creating_fallback = true; try { return branch(() => { + // errors in `failed` snippets cause the boundary to error again + // TODO Svelte 6: revisit this decision, most likely better to go to parent boundary instead + var effect = /** @type {Effect} */ (active_effect); + + effect.b = this; + effect.f |= BOUNDARY_EFFECT; + failed( this.#anchor, () => error, @@ -475,8 +445,6 @@ export class Boundary { } catch (error) { invoke_error_boundary(error, /** @type {Effect} */ (this.#effect.parent)); return null; - } finally { - this.#is_creating_fallback = false; } }); } diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 82a2d4f484..b382a4e3a5 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -293,16 +293,19 @@ export class Batch { } } - var parent = effect.parent; - effect = effect.next; - - while (effect === null && parent !== null) { - if (parent === pending_boundary) { + while (effect !== null) { + if (effect === pending_boundary) { pending_boundary = null; } - effect = parent.next; - parent = parent.parent; + var next = effect.next; + + if (next !== null) { + effect = next; + break; + } + + effect = effect.parent; } } } diff --git a/packages/svelte/tests/runtime-runes/samples/async-boundary-pending-live/_config.js b/packages/svelte/tests/runtime-runes/samples/async-boundary-pending-live/_config.js new file mode 100644 index 0000000000..1d860cded5 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-boundary-pending-live/_config.js @@ -0,0 +1,38 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: ` + + +

0

+ `, + + async test({ assert, target }) { + const [increment, shift] = target.querySelectorAll('button'); + + increment.click(); + await tick(); + + assert.htmlEqual( + target.innerHTML, + ` + + +

1

+ ` + ); + + shift.click(); + await tick(); + + assert.htmlEqual( + target.innerHTML, + ` + + +

resolved

+ ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-boundary-pending-live/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-boundary-pending-live/main.svelte new file mode 100644 index 0000000000..764fd20a51 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-boundary-pending-live/main.svelte @@ -0,0 +1,31 @@ + + + + + + + +

{await push('resolved')}

+ + {#snippet pending()} +

{count}

+ {/snippet} +
From 60a425193c643665c99dfd7e0d5cea5b052127a9 Mon Sep 17 00:00:00 2001 From: Varun Chawla <34209028+veeceey@users.noreply.github.com> Date: Tue, 17 Feb 2026 06:54:15 -0800 Subject: [PATCH 08/10] fix: treat CSS attribute selectors as case-insensitive for HTML enumerated attributes (#17712) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #17207 CSS attribute selectors for HTML enumerated attributes (like `method`, `type`, `dir`, etc.) are supposed to match case-insensitively per the HTML spec. Browsers handle this correctly — `form[method="get"]` matches `
`. But Svelte's CSS pruning was doing a strict case-sensitive comparison, which meant: 1. The selector got incorrectly flagged as unused (no `css_unused_selector` warning was shown when spreads were involved, but the selector was still pruned) 2. The scoping class wasn't applied to the matching element 3. Styles silently disappeared in production builds The fix adds a set of known HTML attributes with case-insensitive enumerated values (sourced from the HTML spec) and uses it during CSS attribute selector matching. The explicit CSS `s` flag still overrides this behavior, as expected. ### Before ```svelte

Hello

``` ### After The selector correctly matches and styles are applied. ### Test plan - Added `attribute-selector-html-case-insensitive` CSS test covering `form[method]` and `input[type]` cases - All 179 existing CSS tests pass - Verified the existing `attribute-selector-case-sensitive` test (using `s` flag) still works correctly - Compiler error tests and validator tests all pass --------- Co-authored-by: Rich Harris Co-authored-by: Rich Harris --- .changeset/css-attribute-case-insensitive.md | 5 ++ .../phases/2-analyze/css/css-prune.js | 48 ++++++++++++++++++- .../expected.css | 11 +++++ .../expected.html | 1 + .../input.svelte | 23 +++++++++ 5 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 .changeset/css-attribute-case-insensitive.md create mode 100644 packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.css create mode 100644 packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.html create mode 100644 packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/input.svelte diff --git a/.changeset/css-attribute-case-insensitive.md b/.changeset/css-attribute-case-insensitive.md new file mode 100644 index 0000000000..e5b3bcea2b --- /dev/null +++ b/.changeset/css-attribute-case-insensitive.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: treat CSS attribute selectors as case-insensitive for HTML enumerated attributes diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js index 7242c69d8c..7e05d2e7d3 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js @@ -22,6 +22,50 @@ const whitelist_attribute_selector = new Map([ ['dialog', ['open']] ]); +/** + * HTML attributes whose enumerated values are case-insensitive per the HTML spec. + * CSS attribute selectors match these values case-insensitively in HTML documents. + * @see {@link https://html.spec.whatwg.org/multipage/semantics-other.html#case-sensitivity-of-selectors HTML spec} + */ +const case_insensitive_attributes = new Set([ + 'accept-charset', + 'autocapitalize', + 'autocomplete', + 'behavior', + 'charset', + 'crossorigin', + 'decoding', + 'dir', + 'direction', + 'draggable', + 'enctype', + 'enterkeyhint', + 'fetchpriority', + 'formenctype', + 'formmethod', + 'formtarget', + 'hidden', + 'http-equiv', + 'inputmode', + 'kind', + 'loading', + 'method', + 'preload', + 'referrerpolicy', + 'rel', + 'rev', + 'role', + 'rules', + 'scope', + 'shape', + 'spellcheck', + 'target', + 'translate', + 'type', + 'valign', + 'wrap' +]); + /** @type {Compiler.AST.CSS.Combinator} */ const descendant_combinator = { type: 'Combinator', @@ -523,7 +567,9 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element, selector.name, selector.value && unquote(selector.value), selector.matcher, - selector.flags?.includes('i') ?? false + (selector.flags?.includes('i') ?? false) || + (!selector.flags?.includes('s') && + case_insensitive_attributes.has(selector.name.toLowerCase())) ) ) { return false; diff --git a/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.css b/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.css new file mode 100644 index 0000000000..01ec1b2269 --- /dev/null +++ b/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.css @@ -0,0 +1,11 @@ + form[method="get"].svelte-xyz h1:where(.svelte-xyz) { + color: red; + } + + form[method="post"].svelte-xyz h1:where(.svelte-xyz) { + color: blue; + } + + input[type="text"].svelte-xyz { + color: green; + } diff --git a/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.html b/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.html new file mode 100644 index 0000000000..101c03f836 --- /dev/null +++ b/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.html @@ -0,0 +1 @@ +

Hello

World

diff --git a/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/input.svelte b/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/input.svelte new file mode 100644 index 0000000000..e11a922d75 --- /dev/null +++ b/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/input.svelte @@ -0,0 +1,23 @@ +
+

Hello

+
+ +
+

World

+
+ + + + From e47c747338a99ae9699d9f13297140f145a34aec Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 17 Feb 2026 09:57:06 -0500 Subject: [PATCH 09/10] fix: prevent event delegation logic conflicting between svelte instances (#17728) Fixes https://github.com/sveltejs/svelte.dev/issues/1793. There are actually two fixes here, and either is sufficient to fix the playground, but they are complementary. First, we only add the delegated event handler _after_ the component has successfully mounted, otherwise it will never get cleaned up if an error occurs during mount. Second, instead of storing data on `event.__root` (which leaks between instances), we reuse the existing `event_symbol` to provide the necessary encapsulation. (I'll be honest I don't totally understand what this property is for anyway and can't be bothered to figure it out right now, but I'm sure it's important.) No test because I'm not really sure how you _would_ test this; it requires a fairly esoteric setup. ### 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. - [ ] 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: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/chilly-kings-join.md | 5 ++ .../internal/client/dom/elements/events.js | 15 ++-- packages/svelte/src/internal/client/render.js | 85 ++++++++++--------- 3 files changed, 57 insertions(+), 48 deletions(-) create mode 100644 .changeset/chilly-kings-join.md diff --git a/.changeset/chilly-kings-join.md b/.changeset/chilly-kings-join.md new file mode 100644 index 0000000000..b53d7e6cb6 --- /dev/null +++ b/.changeset/chilly-kings-join.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: prevent event delegation logic conflicting between svelte instances diff --git a/packages/svelte/src/internal/client/dom/elements/events.js b/packages/svelte/src/internal/client/dom/elements/events.js index 041698eb9d..e598a78949 100644 --- a/packages/svelte/src/internal/client/dom/elements/events.js +++ b/packages/svelte/src/internal/client/dom/elements/events.js @@ -11,8 +11,11 @@ import { set_active_reaction } from '../../runtime.js'; import { without_reactive_context } from './bindings/shared.js'; -import { can_delegate_event } from '../../../../utils.js'; +/** + * Used on elements, as a map of event type -> event handler, + * and on events themselves to track which element handled an event + */ export const event_symbol = Symbol('events'); /** @type {Set} */ @@ -177,8 +180,8 @@ export function handle_event_propagation(event) { last_propagated_event = event; // composedPath contains list of nodes the event has propagated through. - // We check __root to skip all nodes below it in case this is a - // parent of the __root node, which indicates that there's nested + // We check `event_symbol` to skip all nodes below it in case this is a + // parent of the `event_symbol` node, which indicates that there's nested // mounted apps. In this case we don't want to trigger events multiple times. var path_idx = 0; @@ -186,7 +189,7 @@ export function handle_event_propagation(event) { // without it the variable will be DCE'd and things will // fail mysteriously in Firefox // @ts-expect-error is added below - var handled_at = last_propagated_event === event && event.__root; + var handled_at = last_propagated_event === event && event[event_symbol]; if (handled_at) { var at_idx = path.indexOf(handled_at); @@ -198,7 +201,7 @@ export function handle_event_propagation(event) { // -> ignore, but set handle_at to document/window so that we're resetting the event // chain in case someone manually dispatches the same event object again. // @ts-expect-error - event.__root = handler_element; + event[event_symbol] = handler_element; return; } @@ -298,7 +301,7 @@ export function handle_event_propagation(event) { } } finally { // @ts-expect-error is used above - event.__root = handler_element; + event[event_symbol] = handler_element; // @ts-ignore remove proxy on currentTarget delete event.currentTarget; set_active_reaction(previous_reaction); diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 0d5bc6cb49..76a73852d5 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -161,48 +161,6 @@ const listeners = new Map(); function _mount(Component, { target, anchor, props = {}, events, context, intro = true }) { init_operations(); - /** @type {Set} */ - var registered_events = new Set(); - - /** @param {Array} events */ - var event_handle = (events) => { - for (var i = 0; i < events.length; i++) { - var event_name = events[i]; - - if (registered_events.has(event_name)) continue; - registered_events.add(event_name); - - var passive = is_passive_event(event_name); - - // Add the event listener to both the container and the document. - // The container listener ensures we catch events from within in case - // the outer content stops propagation of the event. - // - // The document listener ensures we catch events that originate from elements that were - // manually moved outside of the container (e.g. via manual portals). - for (const node of [target, document]) { - var counts = listeners.get(node); - - if (counts === undefined) { - counts = new Map(); - listeners.set(node, counts); - } - - var count = counts.get(event_name); - - if (count === undefined) { - node.addEventListener(event_name, handle_event_propagation, { passive }); - counts.set(event_name, 1); - } else { - counts.set(event_name, count + 1); - } - } - } - }; - - event_handle(array_from(all_registered_events)); - root_event_handles.add(event_handle); - /** @type {Exports} */ // @ts-expect-error will be defined because the render effect runs synchronously var component = undefined; @@ -251,6 +209,49 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro } ); + // Setup event delegation _after_ component is mounted - if an error would happen during mount, it would otherwise not be cleaned up + /** @type {Set} */ + var registered_events = new Set(); + + /** @param {Array} events */ + var event_handle = (events) => { + for (var i = 0; i < events.length; i++) { + var event_name = events[i]; + + if (registered_events.has(event_name)) continue; + registered_events.add(event_name); + + var passive = is_passive_event(event_name); + + // Add the event listener to both the container and the document. + // The container listener ensures we catch events from within in case + // the outer content stops propagation of the event. + // + // The document listener ensures we catch events that originate from elements that were + // manually moved outside of the container (e.g. via manual portals). + for (const node of [target, document]) { + var counts = listeners.get(node); + + if (counts === undefined) { + counts = new Map(); + listeners.set(node, counts); + } + + var count = counts.get(event_name); + + if (count === undefined) { + node.addEventListener(event_name, handle_event_propagation, { passive }); + counts.set(event_name, 1); + } else { + counts.set(event_name, count + 1); + } + } + } + }; + + event_handle(array_from(all_registered_events)); + root_event_handles.add(event_handle); + return () => { for (var event_name of registered_events) { for (const node of [target, document]) { From 04c0368aa8d8dff31cfe34bc134c02bfa6f05bf9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 10:04:16 -0500 Subject: [PATCH 10/10] Version Packages (#17725) This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## svelte@5.51.3 ### Patch Changes - fix: prevent event delegation logic conflicting between svelte instances ([#17728](https://github.com/sveltejs/svelte/pull/17728)) - fix: treat CSS attribute selectors as case-insensitive for HTML enumerated attributes ([#17712](https://github.com/sveltejs/svelte/pull/17712)) - fix: locate Rollup annontaion friendly to JS downgraders ([#17724](https://github.com/sveltejs/svelte/pull/17724)) - fix: run effects in pending snippets ([#17719](https://github.com/sveltejs/svelte/pull/17719)) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/chilly-kings-join.md | 5 ----- .changeset/css-attribute-case-insensitive.md | 5 ----- .changeset/icy-otters-fall.md | 5 ----- .changeset/many-dolls-argue.md | 5 ----- packages/svelte/CHANGELOG.md | 12 ++++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 7 files changed, 14 insertions(+), 22 deletions(-) delete mode 100644 .changeset/chilly-kings-join.md delete mode 100644 .changeset/css-attribute-case-insensitive.md delete mode 100644 .changeset/icy-otters-fall.md delete mode 100644 .changeset/many-dolls-argue.md diff --git a/.changeset/chilly-kings-join.md b/.changeset/chilly-kings-join.md deleted file mode 100644 index b53d7e6cb6..0000000000 --- a/.changeset/chilly-kings-join.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: prevent event delegation logic conflicting between svelte instances diff --git a/.changeset/css-attribute-case-insensitive.md b/.changeset/css-attribute-case-insensitive.md deleted file mode 100644 index e5b3bcea2b..0000000000 --- a/.changeset/css-attribute-case-insensitive.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: treat CSS attribute selectors as case-insensitive for HTML enumerated attributes diff --git a/.changeset/icy-otters-fall.md b/.changeset/icy-otters-fall.md deleted file mode 100644 index b15c282ca4..0000000000 --- a/.changeset/icy-otters-fall.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: locate Rollup annontaion friendly to JS downgraders diff --git a/.changeset/many-dolls-argue.md b/.changeset/many-dolls-argue.md deleted file mode 100644 index 54a9cef915..0000000000 --- a/.changeset/many-dolls-argue.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: run effects in pending snippets diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 380de4b901..4e4f4f6088 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,17 @@ # svelte +## 5.51.3 + +### Patch Changes + +- fix: prevent event delegation logic conflicting between svelte instances ([#17728](https://github.com/sveltejs/svelte/pull/17728)) + +- fix: treat CSS attribute selectors as case-insensitive for HTML enumerated attributes ([#17712](https://github.com/sveltejs/svelte/pull/17712)) + +- fix: locate Rollup annontaion friendly to JS downgraders ([#17724](https://github.com/sveltejs/svelte/pull/17724)) + +- fix: run effects in pending snippets ([#17719](https://github.com/sveltejs/svelte/pull/17719)) + ## 5.51.2 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index ee08526201..e9f4713d1c 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.51.2", + "version": "5.51.3", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index d6d42c7e97..c27b247dba 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.51.2'; +export const VERSION = '5.51.3'; export const PUBLIC_VERSION = '5';