Skip to content

Commit a0dfeea

Browse files
authored
Merge branch 'main' into $state-invalidate
2 parents a45b405 + f0497b1 commit a0dfeea

File tree

326 files changed

+5983
-2179
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

326 files changed

+5983
-2179
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ jobs:
2828
os: ubuntu-latest
2929
- node-version: 22
3030
os: ubuntu-latest
31+
- node-version: 24
32+
os: ubuntu-latest
3133

3234
steps:
3335
- uses: actions/checkout@v4

documentation/docs/02-runes/02-$state.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ Unlike other frameworks you may have encountered, there is no API for interactin
2020

2121
If `$state` is used with an array or a simple object, the result is a deeply reactive _state proxy_. [Proxies](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) allow Svelte to run code when you read or write properties, including via methods like `array.push(...)`, triggering granular updates.
2222

23-
> [!NOTE] Class instances are not proxied. You can create [reactive state fields](#Classes) on classes that you define. Svelte provides reactive implementations of built-ins like `Set` and `Map` that can be imported from [`svelte/reactivity`](svelte-reactivity).
24-
25-
State is proxified recursively until Svelte finds something other than an array or simple object. In a case like this...
23+
State is proxified recursively until Svelte finds something other than an array or simple object (like a class or an object created with `Object.create`). In a case like this...
2624

2725
```js
2826
let todos = $state([
@@ -67,16 +65,15 @@ todos[0].done = !todos[0].done;
6765

6866
### Classes
6967

70-
You can also use `$state` in class fields (whether public or private):
68+
Class instances are not proxied. Instead, you can use `$state` in class fields (whether public or private), or as the first assignment to a property immediately inside the `constructor`:
7169

7270
```js
7371
// @errors: 7006 2554
7472
class Todo {
7573
done = $state(false);
76-
text = $state();
7774

7875
constructor(text) {
79-
this.text = text;
76+
this.text = $state(text);
8077
}
8178

8279
reset() {
@@ -110,10 +107,9 @@ You can either use an inline function...
110107
// @errors: 7006 2554
111108
class Todo {
112109
done = $state(false);
113-
text = $state();
114110

115111
constructor(text) {
116-
this.text = text;
112+
this.text = $state(text);
117113
}
118114

119115
+++reset = () => {+++
@@ -123,6 +119,8 @@ class Todo {
123119
}
124120
```
125121

122+
> Svelte provides reactive implementations of built-in classes like `Set` and `Map` that can be imported from [`svelte/reactivity`](svelte-reactivity).
123+
126124
## `$state.raw`
127125

128126
In cases where you don't want objects and arrays to be deeply reactive you can use `$state.raw`.
@@ -147,6 +145,8 @@ person = {
147145

148146
This can improve performance with large arrays and objects that you weren't planning to mutate anyway, since it avoids the cost of making them reactive. Note that raw state can _contain_ reactive state (for example, a raw array of reactive objects).
149147

148+
As with `$state`, you can declare class fields using `$state.raw`.
149+
150150
## `$state.snapshot`
151151

152152
To take a static snapshot of a deeply reactive `$state` proxy, use `$state.snapshot`:

documentation/docs/02-runes/04-$effect.md

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -269,11 +269,11 @@ In general, `$effect` is best considered something of an escape hatch — useful
269269
270270
If you're using an effect because you want to be able to reassign the derived value (to build an optimistic UI, for example) note that [deriveds can be directly overridden]($derived#Overriding-derived-values) as of Svelte 5.25.
271271

272-
You might be tempted to do something convoluted with effects to link one value to another. The following example shows two inputs for "money spent" and "money left" that are connected to each other. If you update one, the other should update accordingly. Don't use effects for this ([demo](/playground/untitled#H4sIAAAAAAAACpVRy26DMBD8FcvKgUhtoIdeHBwp31F6MGSJkBbHwksEQvx77aWQqooq9bgzOzP7mGTdIHipPiZJowOpGJAv0po2VmfnDv4OSBErjYdneHWzBJaCjcx91TWOToUtCIEE3cig0OIty44r5l1oDtjOkyFIsv3GINQ_CNYyGegd1DVUlCR7oU9iilDUcP8S8roYs9n8p2wdYNVFm4csTx872BxNCcjr5I11fdgonEkXsjP2CoUUZWMv6m6wBz2x7yxaM-iJvWeRsvSbSVeUy5i0uf8vKA78NIeJLSZWv1I8jQjLdyK4XuTSeIdmVKJGGI4LdjVOiezwDu1yG74My8PLCQaSiroe5s_5C2PHrkVGAgAA)):
272+
You might be tempted to do something convoluted with effects to link one value to another. The following example shows two inputs for "money spent" and "money left" that are connected to each other. If you update one, the other should update accordingly. Don't use effects for this ([demo](/playground/untitled#H4sIAAAAAAAAE5WRTWrDMBCFryKGLBJoY3fRjWIHeoiu6i6UZBwEY0VE49TB-O6VxrFTSih0qe_Ne_OjHpxpEDS8O7ZMeIAnqC1hAP3RA1990hKI_Fb55v06XJA4sZ0J-IjvT47RcYyBIuzP1vO2chVHHFjxiQ2pUr3k-SZRQlbBx_LIFoEN4zJfzQph_UMQr4hRXmBd456Xy5Uqt6pPKHmkfmzyPAZL2PCnbRpg8qWYu63I7lu4gswOSRYqrPNt3CgeqqzgbNwRK1A76w76YqjFspfcQTWmK3vJHlQm1puSTVSeqdOc_r9GaeCHfUSY26TXry6Br4RSK3C6yMEGT-aqVU3YbUZ2NF6rfP2KzXgbuYzY46czdgyazy0On_FlLH3F-UDXhgIO35UGlA1rAgAA)):
273273

274274
```svelte
275275
<script>
276-
let total = 100;
276+
const total = 100;
277277
let spent = $state(0);
278278
let left = $state(total);
279279
@@ -297,32 +297,26 @@ You might be tempted to do something convoluted with effects to link one value t
297297
</label>
298298
```
299299

300-
Instead, use `oninput` callbacks or — better still — [function bindings](bind#Function-bindings) where possible ([demo](/playground/untitled#H4sIAAAAAAAAE51SsW6DMBT8FcvqABINdOhCIFKXTt06lg4GHpElYyz8iECIf69tcIIipo6-u3f3fPZMJWuBpvRzkBXyTpKSy5rLq6YRbbgATdOfmeKkrMgCBt9GPpQ66RsItFjJNBzhVScRJBobmumq5wovhSxQABLskAmSk7ckOXtMKyM22ItGhhAk4Z0R0OwIN-tIQzd-90HVhvy2HsGNiQFCMltBgd7XoecV2xzXNV7XaEcth7ZfRv7kujnsTX2Qd7USb5rFjwZkJlgJwpWRcakG04cpOS9oz-QVCuoeInXW-RyEJL-sG0b7Wy6kZWM-u7CFxM5tdrIl9qg72vB74H-y7T2iXROHyVb0CLanp1yNk4D1A1jQ91hzrQSbUtIIGLcir0ylJDm9Q7urz42bX4UwIk2xH2D5Xf4A7SeMcMQCAAA=)):
300+
Instead, use `oninput` callbacks or — better still — [function bindings](bind#Function-bindings) where possible ([demo](/playground/untitled#H4sIAAAAAAAAE5VRvW7CMBB-FcvqECQK6dDFJEgsnfoGTQdDLsjSxVjxhYKivHvPBwFUsXS8774_nwftbQva6I_e78gdvNo6Xzu_j3quG4cQtfkaNJ1DIiWA8atkE8IiHgEpYVsb4Rm-O3gCT2yji7jrXKB15StiOJKiA1lUpXrL81VCEUjFwHTGXiJZgiyf3TYIjSxq6NwR6uyifr0ohMbEZnpHH2rWf7ImS8KZGtK6osl_UqelRIyVL5b3ir5AuwWUtoXzoee6fIWy0p31e6i0XMocLfZQDuI6qtaeykGcR7UU6XWznFAZU9LN_X9B2UyVayk9f3ji0-REugen6U9upDOCcAWcLlS7GNCejWoQTqsLtrfBqHzxDu3DrUTOf0xwIm2o62H85sk6_OHG2jQWI4y_3byXXGMCAAA=)):
301301

302302
```svelte
303303
<script>
304-
let total = 100;
304+
const total = 100;
305305
let spent = $state(0);
306-
let left = $state(total);
307-
308-
function updateSpent(value) {
309-
spent = value;
310-
left = total - spent;
311-
}
306+
let left = $derived(total - spent);
312307
313-
function updateLeft(value) {
314-
left = value;
308+
+++ function updateLeft(left) {
315309
spent = total - left;
316-
}
310+
}+++
317311
</script>
318312
319313
<label>
320-
<input type="range" bind:value={() => spent, updateSpent} max={total} />
314+
<input type="range" bind:value={spent} max={total} />
321315
{spent}/{total} spent
322316
</label>
323317
324318
<label>
325-
<input type="range" bind:value={() => left, updateLeft} max={total} />
319+
<input type="range" +++bind:value={() => left, updateLeft}+++ max={total} />
326320
{left}/{total} left
327321
</label>
328322
```

documentation/docs/03-template-syntax/[email protected]

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
title: {@attach ...}
33
---
44

5-
Attachments are functions that run when an element is mounted to the DOM. Optionally, they can return a function that is called when the element is later removed from the DOM.
5+
Attachments are functions that run in an [effect]($effect) when an element is mounted to the DOM or when [state]($state) read inside the function updates.
6+
7+
Optionally, they can return a function that is called before the attachment re-runs, or after the element is later removed from the DOM.
68

79
> [!NOTE]
810
> Attachments are available in Svelte 5.29 and newer.
@@ -55,7 +57,7 @@ A useful pattern is for a function, such as `tooltip` in this example, to _retur
5557
</button>
5658
```
5759

58-
Since the `tooltip(content)` expression runs inside an [effect]($effect), the attachment will be destroyed and recreated whenever `content` changes.
60+
Since the `tooltip(content)` expression runs inside an [effect]($effect), the attachment will be destroyed and recreated whenever `content` changes. The same thing would happen for any state read _inside_ the attachment function when it first runs. (If this isn't what you want, see [Controlling when attachments re-run](#Controlling-when-attachments-re-run).)
5961

6062
## Inline attachments
6163

@@ -126,6 +128,39 @@ This allows you to create _wrapper components_ that augment elements ([demo](/pl
126128
</Button>
127129
```
128130

131+
## Controlling when attachments re-run
132+
133+
Attachments, unlike [actions](use), are fully reactive: `{@attach foo(bar)}` will re-run on changes to `foo` _or_ `bar` (or any state read inside `foo`):
134+
135+
```js
136+
// @errors: 7006 2304 2552
137+
function foo(bar) {
138+
return (node) => {
139+
veryExpensiveSetupWork(node);
140+
update(node, bar);
141+
};
142+
}
143+
```
144+
145+
In the rare case that this is a problem (for example, if `foo` does expensive and unavoidable setup work) consider passing the data inside a function and reading it in a child effect:
146+
147+
```js
148+
// @errors: 7006 2304 2552
149+
function foo(+++getBar+++) {
150+
return (node) => {
151+
veryExpensiveSetupWork(node);
152+
153+
+++ $effect(() => {
154+
update(node, getBar());
155+
});+++
156+
}
157+
}
158+
```
159+
129160
## Creating attachments programmatically
130161

131162
To add attachments to an object that will be spread onto a component or element, use [`createAttachmentKey`](svelte-attachments#createAttachmentKey).
163+
164+
## Converting actions to attachments
165+
166+
If you're using a library that only provides actions, you can convert them to attachments with [`fromAction`](svelte-attachments#fromAction), allowing you to (for example) use them with components.

documentation/docs/03-template-syntax/12-bind.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ title: bind:
44

55
Data ordinarily flows down, from parent to child. The `bind:` directive allows data to flow the other way, from child to parent.
66

7-
The general syntax is `bind:property={expression}`, where `expression` is an _lvalue_ (i.e. a variable or an object property). When the expression is an identifier with the same name as the property, we can omit the expression — in other words these are equivalent:
7+
The general syntax is `bind:property={expression}`, where `expression` is an [_lvalue_](https://press.rebus.community/programmingfundamentals/chapter/lvalue-and-rvalue/) (i.e. a variable or an object property). When the expression is an identifier with the same name as the property, we can omit the expression — in other words these are equivalent:
88

99
<!-- prettier-ignore -->
1010
```svelte
@@ -142,26 +142,27 @@ Checkboxes can be in an [indeterminate](https://developer.mozilla.org/en-US/docs
142142

143143
## `<input bind:group>`
144144

145-
Inputs that work together can use `bind:group`.
145+
Inputs that work together can use `bind:group` ([demo](/playground/untitled#H4sIAAAAAAAAE62T32_TMBDH_5XDQkpbrct7SCMGEvCEECDxsO7BSW6L2c227EvbKOv_jp0f6jYhQKJv5_P3PvdL1wstH1Bk4hMSGdgbRzUssFaM9VJciFtF6EV23QvubNRFR_BPUVfWXvodEkdfKT3-zl8Zzag5YETuK6csF1u9ZUIGNo4VkYQNvPYsGRfJF5JKJ8s3QRJE6WoFb2Nq6K-ck13u2Sl9Vxxhlc6QUBIFnz9Brm9ifJ6esun81XoNd860FmtwslYGlLYte5AO4aHlVhJ1gIeKWq92COt1iMtJlkhFPkgh1rHZiiF6K6BUus4G5KafGznCTlIbVUMfQZUWMJh5OrL-C_qjMYSwb1DyiH7iOEuCb1ZpWTUjfHqcwC_GWDVY3ZfmME_SGttSmD9IHaYatvWHIc6xLyqad3mq6KuqcCwnWn9p8p-p71BqP2IH81zc9w2in-od7XORP7ayCpd5YCeXI_-p59mObPF9WmwGpx3nqS2Gzw8TO3zOaS5_GqUXyQUkS3h8hOSz0ZhMESHGc0c4Hm3MAn00t1wrb0l2GZRkqvt4sXwczm6Qh8vnUJzI2LV4vAkvqWgfehTZrSSPx19WiVfFfAQAAA==)):
146146

147147
```svelte
148+
<!--- file: BurritoChooser.svelte --->
148149
<script>
149150
let tortilla = $state('Plain');
150151
151-
/** @type {Array<string>} */
152+
/** @type {string[]} */
152153
let fillings = $state([]);
153154
</script>
154155
155156
<!-- grouped radio inputs are mutually exclusive -->
156-
<input type="radio" bind:group={tortilla} value="Plain" />
157-
<input type="radio" bind:group={tortilla} value="Whole wheat" />
158-
<input type="radio" bind:group={tortilla} value="Spinach" />
157+
<label><input type="radio" bind:group={tortilla} value="Plain" /> Plain</label>
158+
<label><input type="radio" bind:group={tortilla} value="Whole wheat" /> Whole wheat</label>
159+
<label><input type="radio" bind:group={tortilla} value="Spinach" /> Spinach</label>
159160
160161
<!-- grouped checkbox inputs populate an array -->
161-
<input type="checkbox" bind:group={fillings} value="Rice" />
162-
<input type="checkbox" bind:group={fillings} value="Beans" />
163-
<input type="checkbox" bind:group={fillings} value="Cheese" />
164-
<input type="checkbox" bind:group={fillings} value="Guac (extra)" />
162+
<label><input type="checkbox" bind:group={fillings} value="Rice" /> Rice</label>
163+
<label><input type="checkbox" bind:group={fillings} value="Beans" /> Beans</label>
164+
<label><input type="checkbox" bind:group={fillings} value="Cheese" /> Cheese</label>
165+
<label><input type="checkbox" bind:group={fillings} value="Guac (extra)" /> Guac (extra)</label>
165166
```
166167

167168
> [!NOTE] `bind:group` only works if the inputs are in the same Svelte component.

documentation/docs/05-special-elements/04-svelte-body.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ title: <svelte:body>
88

99
Similarly to `<svelte:window>`, this element allows you to add listeners to events on `document.body`, such as `mouseenter` and `mouseleave`, which don't fire on `window`. It also lets you use [actions](use) on the `<body>` element.
1010

11-
As with `<svelte:window>` and `<svelte:document>`, this element may only appear the top level of your component and must never be inside a block or element.
11+
As with `<svelte:window>` and `<svelte:document>`, this element may only appear at the top level of your component and must never be inside a block or element.
1212

1313
```svelte
1414
<svelte:body onmouseenter={handleMouseenter} onmouseleave={handleMouseleave} use:someAction />

documentation/docs/06-runtime/02-context.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ In many cases this is perfectly fine, but there is a risk: if you mutate the sta
125125
```svelte
126126
<!--- file: App.svelte ---->
127127
<script>
128-
import { myGlobalState } from 'svelte';
128+
import { myGlobalState } from './state.svelte.js';
129129
130130
let { data } = $props();
131131

documentation/docs/07-misc/04-custom-elements.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ When constructing a custom element, you can tailor several aspects by defining `
114114
...
115115
```
116116

117+
> [!NOTE] While Typescript is supported in the `extend` function, it is subject to limitations: you need to set `lang="ts"` on one of the scripts AND you can only use [erasable syntax](https://www.typescriptlang.org/tsconfig/#erasableSyntaxOnly) in it. They are not processed by script preprocessors.
118+
117119
## Caveats and limitations
118120

119121
Custom elements can be a useful way to package components for consumption in a non-Svelte app, as they will work with vanilla HTML and JavaScript as well as [most frameworks](https://custom-elements-everywhere.com/). There are, however, some important differences to be aware of:

documentation/docs/07-misc/07-v5-migration-guide.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -833,9 +833,9 @@ Svelte 5 is more strict about the HTML structure and will throw a compiler error
833833

834834
Assignments to destructured parts of a `@const` declaration are no longer allowed. It was an oversight that this was ever allowed.
835835

836-
### :is(...) and :where(...) are scoped
836+
### :is(...), :has(...), and :where(...) are scoped
837837

838-
Previously, Svelte did not analyse selectors inside `:is(...)` and `:where(...)`, effectively treating them as global. Svelte 5 analyses them in the context of the current component. As such, some selectors may now be treated as unused if they were relying on this treatment. To fix this, use `:global(...)` inside the `:is(...)/:where(...)` selectors.
838+
Previously, Svelte did not analyse selectors inside `:is(...)`, `:has(...)`, and `:where(...)`, effectively treating them as global. Svelte 5 analyses them in the context of the current component. As such, some selectors may now be treated as unused if they were relying on this treatment. To fix this, use `:global(...)` inside the `:is(...)/:has(...)/:where(...)` selectors.
839839

840840
When using Tailwind's `@apply` directive, add a `:global` selector to preserve rules that use Tailwind-generated `:is(...)` selectors:
841841

documentation/docs/98-reference/.generated/client-warnings.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,19 @@ Consider the following code:
200200
201201
To fix it, either create callback props to communicate changes, or mark `person` as [`$bindable`]($bindable).
202202
203+
### select_multiple_invalid_value
204+
205+
```
206+
The `value` property of a `<select multiple>` element should be an array, but it received a non-array value. The selection will be kept as is.
207+
```
208+
209+
When using `<select multiple value={...}>`, Svelte will mark all selected `<option>` elements as selected by iterating over the array passed to `value`. If `value` is not an array, Svelte will emit this warning and keep the selected options as they are.
210+
211+
To silence the warning, ensure that `value`:
212+
213+
- is an array for an explicit selection
214+
- is `null` or `undefined` to keep the selection as is
215+
203216
### state_proxy_equality_mismatch
204217
205218
```

0 commit comments

Comments
 (0)