hoist-unmodified-var
Ben McCann 8 months ago
commit 8e17fa741f

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: handle delegated events of elements moved outside the container

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: support TypeScript's `satisfies` operator

@ -25,6 +25,8 @@
"cool-ants-leave", "cool-ants-leave",
"cuddly-pianos-drop", "cuddly-pianos-drop",
"curly-lizards-dream", "curly-lizards-dream",
"curvy-ties-shout",
"dirty-bats-punch",
"dirty-garlics-design", "dirty-garlics-design",
"dirty-tips-add", "dirty-tips-add",
"dry-clocks-grow", "dry-clocks-grow",
@ -54,6 +56,7 @@
"happy-suits-film", "happy-suits-film",
"healthy-planes-vanish", "healthy-planes-vanish",
"heavy-ears-rule", "heavy-ears-rule",
"hip-balloons-begin",
"honest-icons-change", "honest-icons-change",
"hungry-dots-fry", "hungry-dots-fry",
"hungry-tips-unite", "hungry-tips-unite",
@ -80,6 +83,7 @@
"lucky-schools-hang", "lucky-schools-hang",
"moody-frogs-exist", "moody-frogs-exist",
"moody-owls-cry", "moody-owls-cry",
"nasty-lions-double",
"neat-dingos-clap", "neat-dingos-clap",
"new-boats-wait", "new-boats-wait",
"ninety-dingos-walk", "ninety-dingos-walk",
@ -88,12 +92,14 @@
"odd-shoes-cheat", "odd-shoes-cheat",
"old-flies-jog", "old-flies-jog",
"old-mails-sneeze", "old-mails-sneeze",
"orange-dingos-poke",
"polite-dolphins-care", "polite-dolphins-care",
"polite-pumpkins-guess", "polite-pumpkins-guess",
"polite-ravens-study", "polite-ravens-study",
"poor-eggs-enjoy", "poor-eggs-enjoy",
"poor-seahorses-flash", "poor-seahorses-flash",
"popular-mangos-rest", "popular-mangos-rest",
"pretty-ties-help",
"purple-dragons-peel", "purple-dragons-peel",
"quiet-camels-mate", "quiet-camels-mate",
"rare-pears-whisper", "rare-pears-whisper",
@ -125,10 +131,12 @@
"strong-gifts-smoke", "strong-gifts-smoke",
"strong-lemons-provide", "strong-lemons-provide",
"sweet-mangos-beg", "sweet-mangos-beg",
"sweet-pens-sniff",
"swift-donkeys-perform", "swift-donkeys-perform",
"swift-ravens-hunt", "swift-ravens-hunt",
"swift-seahorses-deliver", "swift-seahorses-deliver",
"tall-books-grin", "tall-books-grin",
"tall-garlics-try",
"tall-shrimps-worry", "tall-shrimps-worry",
"tall-tigers-wait", "tall-tigers-wait",
"tasty-numbers-perform", "tasty-numbers-perform",
@ -140,12 +148,14 @@
"thirty-ghosts-fix", "thirty-ghosts-fix",
"thirty-impalas-repair", "thirty-impalas-repair",
"thirty-wombats-relax", "thirty-wombats-relax",
"three-suits-grin",
"tiny-kings-whisper", "tiny-kings-whisper",
"twelve-dragons-join", "twelve-dragons-join",
"twelve-onions-juggle", "twelve-onions-juggle",
"two-dragons-yell", "two-dragons-yell",
"two-falcons-buy", "two-falcons-buy",
"unlucky-boxes-obey", "unlucky-boxes-obey",
"unlucky-trees-lick",
"wet-games-fly", "wet-games-fly",
"wicked-clouds-exercise", "wicked-clouds-exercise",
"wicked-doors-train", "wicked-doors-train",

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: bail-out event handler referencing each index

@ -1,5 +1,29 @@
# svelte # svelte
## 5.0.0-next.28
### Patch Changes
- fix: deeply unstate objects passed to inspect ([#10056](https://github.com/sveltejs/svelte/pull/10056))
- fix: handle delegated events of elements moved outside the container ([#10060](https://github.com/sveltejs/svelte/pull/10060))
- fix: improve script `lang` attribute detection ([#10046](https://github.com/sveltejs/svelte/pull/10046))
- fix: improve pseudo class parsing ([#10055](https://github.com/sveltejs/svelte/pull/10055))
- fix: add types for popover attributes and events ([#10041](https://github.com/sveltejs/svelte/pull/10041))
- fix: skip generating $.proxy() calls for unary and binary expressions ([#9979](https://github.com/sveltejs/svelte/pull/9979))
- fix: allow pseudo classes after `:global(..)` ([#10055](https://github.com/sveltejs/svelte/pull/10055))
- fix: bail-out event handler referencing each index ([#10063](https://github.com/sveltejs/svelte/pull/10063))
- fix: parse `:nth-of-type(xn+y)` correctly ([#9970](https://github.com/sveltejs/svelte/pull/9970))
- fix: ensure if block is executed in correct order ([#10053](https://github.com/sveltejs/svelte/pull/10053))
## 5.0.0-next.27 ## 5.0.0-next.27
### Patch Changes ### Patch Changes

@ -2,7 +2,7 @@
"name": "svelte", "name": "svelte",
"description": "Cybernetically enhanced web apps", "description": "Cybernetically enhanced web apps",
"license": "MIT", "license": "MIT",
"version": "5.0.0-next.27", "version": "5.0.0-next.28",
"type": "module", "type": "module",
"types": "./types/index.d.ts", "types": "./types/index.d.ts",
"engines": { "engines": {
@ -117,7 +117,7 @@
"@ampproject/remapping": "^2.2.1", "@ampproject/remapping": "^2.2.1",
"@jridgewell/sourcemap-codec": "^1.4.15", "@jridgewell/sourcemap-codec": "^1.4.15",
"acorn": "^8.10.0", "acorn": "^8.10.0",
"acorn-typescript": "^1.4.11", "acorn-typescript": "^1.4.13",
"aria-query": "^5.3.0", "aria-query": "^5.3.0",
"axobject-query": "^4.0.0", "axobject-query": "^4.0.0",
"esm-env": "^1.0.0", "esm-env": "^1.0.0",

@ -2,7 +2,7 @@ import * as acorn from 'acorn';
import { walk } from 'zimmerframe'; import { walk } from 'zimmerframe';
import { tsPlugin } from 'acorn-typescript'; import { tsPlugin } from 'acorn-typescript';
const ParserWithTS = acorn.Parser.extend(tsPlugin()); const ParserWithTS = acorn.Parser.extend(tsPlugin({ allowSatisfies: true }));
/** /**
* @param {string} source * @param {string} source

@ -183,6 +183,10 @@ function get_delegated_event(node, context) {
) { ) {
return non_hoistable; return non_hoistable;
} }
// If we referebnce the index within an each block, then bail-out.
if (binding !== null && binding.initial?.type === 'EachBlock') {
return non_hoistable;
}
if ( if (
binding !== null && binding !== null &&

@ -41,13 +41,16 @@ export const remove_types = {
TSAsExpression(node, context) { TSAsExpression(node, context) {
return context.visit(node.expression); return context.visit(node.expression);
}, },
TSSatisfiesExpression(node, context) {
return context.visit(node.expression);
},
TSNonNullExpression(node, context) { TSNonNullExpression(node, context) {
return context.visit(node.expression); return context.visit(node.expression);
}, },
TSInterfaceDeclaration(node, context) { TSInterfaceDeclaration() {
return b.empty; return b.empty;
}, },
TSTypeAliasDeclaration(node, context) { TSTypeAliasDeclaration() {
return b.empty; return b.empty;
} }
}; };

@ -73,7 +73,7 @@ export class Scope {
* @param {import('estree').Identifier} node * @param {import('estree').Identifier} node
* @param {import('#compiler').Binding['kind']} kind * @param {import('#compiler').Binding['kind']} kind
* @param {import('#compiler').DeclarationKind} declaration_kind * @param {import('#compiler').DeclarationKind} declaration_kind
* @param {null | import('estree').Expression | import('estree').FunctionDeclaration | import('estree').ClassDeclaration | import('estree').ImportDeclaration} initial * @param {null | import('estree').Expression | import('estree').FunctionDeclaration | import('estree').ClassDeclaration | import('estree').ImportDeclaration | import('../types/template.js').EachBlock} initial
* @returns {import('#compiler').Binding} * @returns {import('#compiler').Binding}
*/ */
declare(node, kind, declaration_kind, initial = null) { declare(node, kind, declaration_kind, initial = null) {
@ -551,7 +551,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
const is_keyed = const is_keyed =
node.key && node.key &&
(node.key.type !== 'Identifier' || !node.index || node.key.name !== node.index); (node.key.type !== 'Identifier' || !node.index || node.key.name !== node.index);
scope.declare(b.id(node.index), is_keyed ? 'derived' : 'normal', 'const'); scope.declare(b.id(node.index), is_keyed ? 'derived' : 'normal', 'const', node);
} }
if (node.key) visit(node.key, { scope }); if (node.key) visit(node.key, { scope });
@ -708,7 +708,7 @@ export function set_scope(scopes) {
/** /**
* Returns the name of the rune if the given expression is a `CallExpression` using a rune. * Returns the name of the rune if the given expression is a `CallExpression` using a rune.
* @param {import('estree').Node | null | undefined} node * @param {import('estree').Node | import('../types/template.js').EachBlock | null | undefined} node
* @param {Scope} scope * @param {Scope} scope
* @returns {Runes[number] | null} * @returns {Runes[number] | null}
*/ */

@ -11,7 +11,7 @@ import type { SourceMap } from 'magic-string';
import type { Context } from 'zimmerframe'; import type { Context } from 'zimmerframe';
import type { Scope } from '../phases/scope.js'; import type { Scope } from '../phases/scope.js';
import * as Css from './css.js'; import * as Css from './css.js';
import type { Namespace, SvelteNode } from './template.js'; import type { EachBlock, Namespace, SvelteNode } from './template.js';
/** The return value of `compile` from `svelte/compiler` */ /** The return value of `compile` from `svelte/compiler` */
export interface CompileResult { export interface CompileResult {
@ -269,7 +269,13 @@ export interface Binding {
* What the value was initialized with. * What the value was initialized with.
* For destructured props such as `let { foo = 'bar' } = $props()` this is `'bar'` and not `$props()` * For destructured props such as `let { foo = 'bar' } = $props()` this is `'bar'` and not `$props()`
*/ */
initial: null | Expression | FunctionDeclaration | ClassDeclaration | ImportDeclaration; initial:
| null
| Expression
| FunctionDeclaration
| ClassDeclaration
| ImportDeclaration
| EachBlock;
is_called: boolean; is_called: boolean;
references: { node: Identifier; path: SvelteNode[] }[]; references: { node: Identifier; path: SvelteNode[] }[];
mutated: boolean; mutated: boolean;

@ -1280,6 +1280,10 @@ function handle_event_propagation(root_element, event) {
const handled_at = event.__root; const handled_at = event.__root;
if (handled_at) { if (handled_at) {
const at_idx = path.indexOf(handled_at); const at_idx = path.indexOf(handled_at);
if (at_idx !== -1 && root_element === document) {
// This is the fallback document listener but the event was already handled -> ignore
return;
}
if (at_idx < path.indexOf(root_element)) { if (at_idx < path.indexOf(root_element)) {
path_idx = at_idx; path_idx = at_idx;
} }
@ -2778,6 +2782,7 @@ export function mount(component, options) {
set_current_hydration_fragment(previous_hydration_fragment); set_current_hydration_fragment(previous_hydration_fragment);
} }
const bound_event_listener = handle_event_propagation.bind(null, container); const bound_event_listener = handle_event_propagation.bind(null, container);
const bound_document_event_listener = handle_event_propagation.bind(null, document);
/** @param {Array<string>} events */ /** @param {Array<string>} events */
const event_handle = (events) => { const event_handle = (events) => {
@ -2785,6 +2790,9 @@ export function mount(component, options) {
const event_name = events[i]; const event_name = events[i];
if (!registered_events.has(event_name)) { if (!registered_events.has(event_name)) {
registered_events.add(event_name); registered_events.add(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.
container.addEventListener( container.addEventListener(
event_name, event_name,
bound_event_listener, bound_event_listener,
@ -2794,6 +2802,17 @@ export function mount(component, options) {
} }
: undefined : undefined
); );
// The document listener ensures we catch events that originate from elements that were
// manually moved outside of the container (e.g. via manual portals).
document.addEventListener(
event_name,
bound_document_event_listener,
PassiveDelegatedEvents.includes(event_name)
? {
passive: true
}
: undefined
);
} }
} }
}; };

@ -6,5 +6,5 @@
* https://svelte.dev/docs/svelte-compiler#svelte-version * https://svelte.dev/docs/svelte-compiler#svelte-version
* @type {string} * @type {string}
*/ */
export const VERSION = '5.0.0-next.27'; export const VERSION = '5.0.0-next.28';
export const PUBLIC_VERSION = '5'; export const PUBLIC_VERSION = '5';

@ -0,0 +1,23 @@
import { test } from '../../test';
// Tests that event delegation still works when the element with the event listener is moved outside the container
export default test({
async test({ assert, target }) {
const btn1 = target.parentElement?.querySelector('button');
const btn2 = target.querySelector('button');
btn1?.click();
await Promise.resolve();
assert.htmlEqual(
target.parentElement?.innerHTML ?? '',
'<main><div><button>clicks: 1</button></div></main><button>clicks: 1</button>'
);
btn2?.click();
await Promise.resolve();
assert.htmlEqual(
target.parentElement?.innerHTML ?? '',
'<main><div><button>clicks: 2</button></div></main><button>clicks: 2</button>'
);
}
});

@ -0,0 +1,12 @@
<script>
let count = $state(0);
let el;
$effect(() => {
document.getElementsByTagName('body')[0].appendChild(el);
})
</script>
<div>
<button bind:this={el} onclick={() => count++}>clicks: {count}</button>
<button onclick={() => count++}>clicks: {count}</button>
</div>

@ -10,6 +10,9 @@
import { type Bar, type Baz } from './types'; import { type Bar, type Baz } from './types';
let count = $state(0); let count = $state(0);
const person = {
message: 'goodbye'
} satisfies Goodbye;
</script> </script>
<button <button

@ -746,7 +746,13 @@ declare module 'svelte/compiler' {
* What the value was initialized with. * What the value was initialized with.
* For destructured props such as `let { foo = 'bar' } = $props()` this is `'bar'` and not `$props()` * For destructured props such as `let { foo = 'bar' } = $props()` this is `'bar'` and not `$props()`
*/ */
initial: null | Expression | FunctionDeclaration | ClassDeclaration | ImportDeclaration; initial:
| null
| Expression
| FunctionDeclaration
| ClassDeclaration
| ImportDeclaration
| EachBlock;
is_called: boolean; is_called: boolean;
references: { node: Identifier; path: SvelteNode[] }[]; references: { node: Identifier; path: SvelteNode[] }[];
mutated: boolean; mutated: boolean;
@ -1026,7 +1032,7 @@ declare module 'svelte/compiler' {
*/ */
function_depth: number; function_depth: number;
declare(node: import('estree').Identifier, kind: Binding['kind'], declaration_kind: DeclarationKind, initial?: null | import('estree').Expression | import('estree').FunctionDeclaration | import('estree').ClassDeclaration | import('estree').ImportDeclaration): Binding; declare(node: import('estree').Identifier, kind: Binding['kind'], declaration_kind: DeclarationKind, initial?: null | import('estree').Expression | import('estree').FunctionDeclaration | import('estree').ClassDeclaration | import('estree').ImportDeclaration | EachBlock): Binding;
has_parent(): boolean; has_parent(): boolean;

@ -69,8 +69,8 @@ importers:
specifier: ^8.10.0 specifier: ^8.10.0
version: 8.11.2 version: 8.11.2
acorn-typescript: acorn-typescript:
specifier: ^1.4.11 specifier: ^1.4.13
version: 1.4.11(acorn@8.11.2) version: 1.4.13(acorn@8.11.2)
aria-query: aria-query:
specifier: ^5.3.0 specifier: ^5.3.0
version: 5.3.0 version: 5.3.0
@ -2748,8 +2748,8 @@ packages:
acorn: 8.11.2 acorn: 8.11.2
dev: true dev: true
/acorn-typescript@1.4.11(acorn@8.11.2): /acorn-typescript@1.4.13(acorn@8.11.2):
resolution: {integrity: sha512-cRGgp+4HMxMZAiMS61ZmQ3iuU/+A4g4ZYZsyLZdmvrEVN/TOwfJ40rPWcLqi3H5ut75SYAdOOJj6QGCcrkK57w==} resolution: {integrity: sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==}
peerDependencies: peerDependencies:
acorn: '>=8.9.0' acorn: '>=8.9.0'
dependencies: dependencies:

Loading…
Cancel
Save