Skip to content

Commit de70d89

Browse files
committed
Merge branch 'main' into teardown-effects
2 parents adce47f + aadf629 commit de70d89

File tree

24 files changed

+200
-42
lines changed

24 files changed

+200
-42
lines changed

.changeset/flat-ghosts-fly.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: make `legacy.componentApi` option more visible

.changeset/pre.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"chilled-pumas-invite",
5858
"chilled-seas-jog",
5959
"chilly-dolphins-lick",
60+
"chilly-laws-juggle",
6061
"chilly-pans-raise",
6162
"chilly-rocks-hug",
6263
"chilly-snakes-scream",
@@ -69,6 +70,7 @@
6970
"cold-beans-tease",
7071
"cold-birds-own",
7172
"cold-cheetahs-judge",
73+
"cold-lamps-accept",
7274
"cold-masks-learn",
7375
"cool-actors-tan",
7476
"cool-ants-leave",
@@ -219,6 +221,7 @@
219221
"honest-pans-kick",
220222
"hot-cooks-repair",
221223
"hot-jobs-tap",
224+
"hot-sloths-clap",
222225
"hungry-boxes-relate",
223226
"hungry-dots-fry",
224227
"hungry-pants-push",
@@ -337,6 +340,7 @@
337340
"olive-seals-sell",
338341
"olive-shirts-complain",
339342
"olive-socks-kick",
343+
"orange-comics-prove",
340344
"orange-crews-rescue",
341345
"orange-dingos-poke",
342346
"orange-masks-exercise",
@@ -511,6 +515,7 @@
511515
"tall-tigers-wait",
512516
"tame-cycles-kneel",
513517
"tame-dots-battle",
518+
"tame-goats-bow",
514519
"tame-spies-drum",
515520
"tasty-cheetahs-appear",
516521
"tasty-numbers-perform",

.changeset/spicy-peas-vanish.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
feat: add svelte/events package and export `on` function

packages/svelte/CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# svelte
22

3+
## 5.0.0-next.151
4+
5+
### Patch Changes
6+
7+
- fix: relax `Component` type ([#11929](https://github.com/sveltejs/svelte/pull/11929))
8+
9+
- fix: sort `{@const ...}` tags topologically in legacy mode ([#11908](https://github.com/sveltejs/svelte/pull/11908))
10+
11+
- chore: deprecate html in favour of body for render() ([#11927](https://github.com/sveltejs/svelte/pull/11927))
12+
13+
- fix: append start/end info to `AssignmentPattern` and `VariableDeclarator` ([#11930](https://github.com/sveltejs/svelte/pull/11930))
14+
15+
- fix: relax slot prop validation on components ([#11923](https://github.com/sveltejs/svelte/pull/11923))
16+
317
## 5.0.0-next.150
418

519
### Patch Changes

packages/svelte/messages/client-errors/errors.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414

1515
> %parent% called `%method%` on an instance of %component%, which is no longer valid in Svelte 5. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information
1616
17+
## component_api_invalid_new
18+
19+
> Attempted to instantiate %component% with `new %name%`, which is no longer valid in Svelte 5. If this component is not under your control, set the `legacy.componentApi` compiler option to keep it working. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information
20+
1721
## each_key_duplicate
1822

1923
> Keyed each block has duplicate key at indexes %a% and %b%

packages/svelte/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "svelte",
33
"description": "Cybernetically enhanced web apps",
44
"license": "MIT",
5-
"version": "5.0.0-next.150",
5+
"version": "5.0.0-next.151",
66
"type": "module",
77
"types": "./types/index.d.ts",
88
"engines": {
@@ -81,6 +81,10 @@
8181
"./transition": {
8282
"types": "./types/index.d.ts",
8383
"default": "./src/transition/index.js"
84+
},
85+
"./events": {
86+
"types": "./types/index.d.ts",
87+
"default": "./src/events/index.js"
8488
}
8589
},
8690
"repository": {

packages/svelte/scripts/generate-types.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ await createBundle({
3333
[`${pkg.name}/server`]: `${dir}/src/server/index.js`,
3434
[`${pkg.name}/store`]: `${dir}/src/store/public.d.ts`,
3535
[`${pkg.name}/transition`]: `${dir}/src/transition/public.d.ts`,
36+
[`${pkg.name}/events`]: `${dir}/src/events/index.js`,
3637
// TODO remove in Svelte 6
3738
[`${pkg.name}/types/compiler/preprocess`]: `${dir}/src/compiler/preprocess/legacy-public.d.ts`,
3839
[`${pkg.name}/types/compiler/interfaces`]: `${dir}/src/compiler/types/legacy-interfaces.d.ts`

packages/svelte/src/compiler/phases/3-transform/client/transform-client.js

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ export function client_component(source, analysis, options) {
452452
body.unshift(b.imports([['createClassComponent', '$$_createClassComponent']], 'svelte/legacy'));
453453
component_block.body.unshift(
454454
b.if(
455-
b.binary('===', b.id('new.target'), b.id(analysis.name)),
455+
b.id('new.target'),
456456
b.return(
457457
b.call(
458458
'$$_createClassComponent',
@@ -463,15 +463,7 @@ export function client_component(source, analysis, options) {
463463
)
464464
);
465465
} else if (options.dev) {
466-
component_block.body.unshift(
467-
b.if(
468-
b.binary('===', b.id('new.target'), b.id(analysis.name)),
469-
b.throw_error(
470-
`Instantiating a component with \`new\` is no longer valid in Svelte 5. ` +
471-
'See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information'
472-
)
473-
)
474-
);
466+
component_block.body.unshift(b.stmt(b.call('$.check_target', b.id('new.target'))));
475467
}
476468

477469
if (state.events.size > 0) {

packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2747,10 +2747,9 @@ export const template_visitors = {
27472747
'$.bind_property',
27482748
b.literal(node.name),
27492749
b.literal(property.event),
2750-
b.literal(property.type ?? 'get'),
27512750
state.node,
2752-
getter,
2753-
setter
2751+
setter,
2752+
property.bidirectional && getter
27542753
);
27552754
} else {
27562755
// special cases

packages/svelte/src/compiler/phases/bindings.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* @typedef BindingProperty
33
* @property {string} [event] This is set if the binding corresponds to the property name on the dom element it's bound to
44
* and there's an event that notifies of a change to that property
5-
* @property {string} [type] Set this to `set` if updates are written to the dom property
5+
* @property {boolean} [bidirectional] Set this to `true` if updates are written to the dom property
66
* @property {boolean} [omit_in_ssr] Set this to true if the binding should not be included in SSR
77
* @property {string[]} [valid_elements] If this is set, the binding is only valid on the given elements
88
* @property {string[]} [invalid_elements] If this is set, the binding is invalid on the given elements
@@ -175,7 +175,7 @@ export const binding_properties = {
175175
// checkbox/radio
176176
indeterminate: {
177177
event: 'change',
178-
type: 'set',
178+
bidirectional: true,
179179
valid_elements: ['input'],
180180
omit_in_ssr: true // no corresponding attribute
181181
},
@@ -200,7 +200,7 @@ export const binding_properties = {
200200
},
201201
open: {
202202
event: 'toggle',
203-
type: 'set',
203+
bidirectional: true,
204204
valid_elements: ['details']
205205
},
206206
value: {

packages/svelte/src/events/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { on } from '../internal/client/dom/elements/events.js';

packages/svelte/src/internal/client/dev/hmr.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { block, branch, destroy_effect } from '../reactivity/effects.js';
22
import { set_should_intro } from '../render.js';
33
import { get } from '../runtime.js';
4+
import { check_target } from './legacy.js';
45

56
/**
67
* @template {(anchor: Comment, props: any) => any} Component
@@ -11,7 +12,7 @@ export function hmr(source) {
1112
* @param {Comment} anchor
1213
* @param {any} props
1314
*/
14-
return (anchor, props) => {
15+
return function (anchor, props) {
1516
let instance = {};
1617

1718
/** @type {import("#client").Effect} */
@@ -31,7 +32,10 @@ export function hmr(source) {
3132
// preserve getters/setters
3233
Object.defineProperties(
3334
instance,
34-
Object.getOwnPropertyDescriptors(component(anchor, props))
35+
Object.getOwnPropertyDescriptors(
36+
// @ts-expect-error
37+
new.target ? new component(anchor, props) : component(anchor, props)
38+
)
3539
);
3640
set_should_intro(true);
3741
});

packages/svelte/src/internal/client/dev/legacy.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ import * as e from '../errors.js';
22
import { current_component_context } from '../runtime.js';
33
import { get_component } from './ownership.js';
44

5+
/** @param {Function & { filename: string }} target */
6+
export function check_target(target) {
7+
if (target) {
8+
e.component_api_invalid_new(target.filename ?? 'a component', target.name);
9+
}
10+
}
11+
512
export function legacy_api() {
613
const component = current_component_context?.function;
714

packages/svelte/src/internal/client/dom/elements/bindings/universal.js

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,40 +33,36 @@ export function bind_content_editable(property, element, get_value, update) {
3333
/**
3434
* @param {string} property
3535
* @param {string} event_name
36-
* @param {'get' | 'set'} type
3736
* @param {Element} element
38-
* @param {() => unknown} get_value
39-
* @param {(value: unknown) => void} update
37+
* @param {(value: unknown) => void} set
38+
* @param {() => unknown} [get]
4039
* @returns {void}
4140
*/
42-
export function bind_property(property, event_name, type, element, get_value, update) {
43-
var target_handler = () => {
41+
export function bind_property(property, event_name, element, set, get) {
42+
var handler = () => {
4443
// @ts-ignore
45-
update(element[property]);
44+
set(element[property]);
4645
};
4746

48-
element.addEventListener(event_name, target_handler);
47+
element.addEventListener(event_name, handler);
4948

50-
if (type === 'set') {
49+
if (get) {
5150
render_effect(() => {
5251
// @ts-ignore
53-
element[property] = get_value();
52+
element[property] = get();
5453
});
54+
} else {
55+
handler();
5556
}
5657

57-
if (type === 'get') {
58-
// @ts-ignore
59-
update(element[property]);
60-
}
61-
62-
render_effect(() => {
63-
// @ts-ignore
64-
if (element === document.body || element === window || element === document) {
58+
// @ts-ignore
59+
if (element === document.body || element === window || element === document) {
60+
render_effect(() => {
6561
return () => {
66-
element.removeEventListener(event_name, target_handler);
62+
element.removeEventListener(event_name, handler);
6763
};
68-
}
69-
});
64+
});
65+
}
7066
}
7167

7268
/**

packages/svelte/src/internal/client/dom/elements/events.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,24 @@ export function create_event(event_name, dom, handler, options) {
6666
return target_handler;
6767
}
6868

69+
/**
70+
* Attaches an event handler to an element and returns a function that removes the handler. Using this
71+
* rather than `addEventListener` will preserve the correct order relative to handlers added declaratively
72+
* (with attributes like `onclick`), which use event delegation for performance reasons
73+
*
74+
* @param {Element} element
75+
* @param {string} type
76+
* @param {EventListener} handler
77+
* @param {AddEventListenerOptions} [options]
78+
*/
79+
export function on(element, type, handler, options = {}) {
80+
var target_handler = create_event(type, element, handler, options);
81+
82+
return () => {
83+
element.removeEventListener(type, target_handler, options);
84+
};
85+
}
86+
6987
/**
7088
* @param {string} event_name
7189
* @param {Element} dom

packages/svelte/src/internal/client/errors.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,24 @@ export function component_api_changed(parent, method, component) {
7575
}
7676
}
7777

78+
/**
79+
* Attempted to instantiate %component% with `new %name%`, which is no longer valid in Svelte 5. If this component is not under your control, set the `legacy.componentApi` compiler option to keep it working. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information
80+
* @param {string} component
81+
* @param {string} name
82+
* @returns {never}
83+
*/
84+
export function component_api_invalid_new(component, name) {
85+
if (DEV) {
86+
const error = new Error(`${"component_api_invalid_new"}\n${`Attempted to instantiate ${component} with \`new ${name}\`, which is no longer valid in Svelte 5. If this component is not under your control, set the \`legacy.componentApi\` compiler option to keep it working. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information`}`);
87+
88+
error.name = 'Svelte error';
89+
throw error;
90+
} else {
91+
// TODO print a link to the documentation
92+
throw new Error("component_api_invalid_new");
93+
}
94+
}
95+
7896
/**
7997
* Keyed each block has duplicate key `%value%` at indexes %a% and %b%
8098
* @param {string} a

packages/svelte/src/internal/client/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export {
77
mark_module_end,
88
add_owner_effect
99
} from './dev/ownership.js';
10-
export { legacy_api } from './dev/legacy.js';
10+
export { check_target, legacy_api } from './dev/legacy.js';
1111
export { inspect } from './dev/inspect.js';
1212
export { await_block as await } from './dom/blocks/await.js';
1313
export { if_block as if } from './dom/blocks/if.js';

packages/svelte/src/version.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
* https://svelte.dev/docs/svelte-compiler#svelte-version
77
* @type {string}
88
*/
9-
export const VERSION = '5.0.0-next.150';
9+
export const VERSION = '5.0.0-next.151';
1010
export const PUBLIC_VERSION = '5';
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
mode: ['client'],
6+
7+
test({ assert, target, logs }) {
8+
const [b1] = target.querySelectorAll('button');
9+
10+
b1?.click();
11+
b1?.click();
12+
b1?.click();
13+
flushSync();
14+
assert.htmlEqual(target.innerHTML, '<section><button>clicks: 3</button></section>');
15+
assert.deepEqual(logs, []);
16+
}
17+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script>
2+
import { on } from 'svelte/events';
3+
4+
let count = $state(0);
5+
6+
function increment(e) {
7+
e.stopPropagation();
8+
count += 1;
9+
}
10+
11+
let sectionEl
12+
$effect(() => {
13+
return on(sectionEl, 'click', () => {
14+
console.log('logged from addEventListener');
15+
});
16+
});
17+
</script>
18+
19+
<section bind:this={sectionEl} onclick={() => console.log('logged from onclick')}>
20+
<button onclick={increment}>
21+
clicks: {count}
22+
</button>
23+
</section>

0 commit comments

Comments
 (0)