Skip to content

Commit 67183ce

Browse files
committed
Merge branch 'main' into templateless-template-generation
2 parents 2e24fdc + 4e72b7d commit 67183ce

File tree

209 files changed

+4263
-1094
lines changed

Some content is hidden

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

209 files changed

+4263
-1094
lines changed

.changeset/short-ads-rhyme.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: import untrack directly from client in `svelte/attachments`

.changeset/wild-actors-retire.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

benchmarking/compare/index.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,23 +67,37 @@ for (let i = 0; i < results[0].length; i += 1) {
6767
for (const metric of ['time', 'gc_time']) {
6868
const times = results.map((result) => +result[i][metric]);
6969
let min = Infinity;
70+
let max = -Infinity;
7071
let min_index = -1;
7172

7273
for (let b = 0; b < times.length; b += 1) {
73-
if (times[b] < min) {
74-
min = times[b];
74+
const time = times[b];
75+
76+
if (time < min) {
77+
min = time;
7578
min_index = b;
7679
}
80+
81+
if (time > max) {
82+
max = time;
83+
}
7784
}
7885

7986
if (min !== 0) {
80-
console.group(`${metric}: fastest is ${branches[min_index]}`);
87+
console.group(`${metric}: fastest is ${char(min_index)} (${branches[min_index]})`);
8188
times.forEach((time, b) => {
82-
console.log(`${branches[b]}: ${time.toFixed(2)}ms (${((time / min) * 100).toFixed(2)}%)`);
89+
const SIZE = 20;
90+
const n = Math.round(SIZE * (time / max));
91+
92+
console.log(`${char(b)}: ${'◼'.repeat(n)}${' '.repeat(SIZE - n)} ${time.toFixed(2)}ms`);
8393
});
8494
console.groupEnd();
8595
}
8696
}
8797

8898
console.groupEnd();
8999
}
100+
101+
function char(i) {
102+
return String.fromCharCode(97 + i);
103+
}

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +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] Classes like `Set` and `Map` will not be proxied, but Svelte provides reactive implementations for various built-ins like these that can be imported from [`svelte/reactivity`](./svelte-reactivity).
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).
2424
2525
State is proxified recursively until Svelte finds something other than an array or simple object. In a case like this...
2626

@@ -67,16 +67,15 @@ todos[0].done = !todos[0].done;
6767

6868
### Classes
6969

70-
You can also use `$state` in class fields (whether public or private):
70+
You can also use `$state` in class fields (whether public or private), or as the first assignment to a property immediately inside the `constructor`:
7171

7272
```js
7373
// @errors: 7006 2554
7474
class Todo {
7575
done = $state(false);
76-
text = $state();
7776

7877
constructor(text) {
79-
this.text = text;
78+
this.text = $state(text);
8079
}
8180

8281
reset() {
@@ -110,10 +109,9 @@ You can either use an inline function...
110109
// @errors: 7006 2554
111110
class Todo {
112111
done = $state(false);
113-
text = $state();
114112

115113
constructor(text) {
116-
this.text = text;
114+
this.text = $state(text);
117115
}
118116

119117
+++reset = () => {+++

documentation/docs/02-runes/07-$inspect.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ This rune, added in 5.14, causes the surrounding function to be _traced_ in deve
5252
import { doSomeWork } from './elsewhere';
5353
5454
$effect(() => {
55+
+++// $inspect.trace must be the first statement of a function body+++
5556
+++$inspect.trace();+++
5657
doSomeWork();
5758
});

documentation/docs/03-template-syntax/01-basic-markup.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,14 @@ As with elements, `name={name}` can be replaced with the `{name}` shorthand.
8282
<Widget foo={bar} answer={42} text="hello" />
8383
```
8484

85+
## Spread attributes
86+
8587
_Spread attributes_ allow many attributes or properties to be passed to an element or component at once.
8688

87-
An element or component can have multiple spread attributes, interspersed with regular ones.
89+
An element or component can have multiple spread attributes, interspersed with regular ones. Order matters — if `things.a` exists it will take precedence over `a="b"`, while `c="d"` would take precedence over `things.c`:
8890

8991
```svelte
90-
<Widget {...things} />
92+
<Widget a="b" {...things} c="d" />
9193
```
9294

9395
## Events

documentation/docs/03-template-syntax/03-each.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ An each block can also specify an _index_, equivalent to the second argument in
4343
{#each expression as name, index (key)}...{/each}
4444
```
4545

46-
If a _key_ expression is provided — which must uniquely identify each list item — Svelte will use it to diff the list when data changes, rather than adding or removing items at the end. The key can be any object, but strings and numbers are recommended since they allow identity to persist when the objects themselves change.
46+
If a _key_ expression is provided — which must uniquely identify each list item — Svelte will use it to intelligently update the list when data changes by inserting, moving and deleting items, rather than adding or removing items at the end and updating the state in the middle.
47+
48+
The key can be any object, but strings and numbers are recommended since they allow identity to persist when the objects themselves change.
4749

4850
```svelte
4951
{#each items as item (item.id)}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
---
2+
title: {@attach ...}
3+
---
4+
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.
8+
9+
> [!NOTE]
10+
> Attachments are available in Svelte 5.29 and newer.
11+
12+
```svelte
13+
<!--- file: App.svelte --->
14+
<script>
15+
/** @type {import('svelte/attachments').Attachment} */
16+
function myAttachment(element) {
17+
console.log(element.nodeName); // 'DIV'
18+
19+
return () => {
20+
console.log('cleaning up');
21+
};
22+
}
23+
</script>
24+
25+
<div {@attach myAttachment}>...</div>
26+
```
27+
28+
An element can have any number of attachments.
29+
30+
## Attachment factories
31+
32+
A useful pattern is for a function, such as `tooltip` in this example, to _return_ an attachment ([demo](/playground/untitled#H4sIAAAAAAAAE3VT0XLaMBD8lavbDiaNCUlbHhTItG_5h5AH2T5ArdBppDOEMv73SkbGJGnH47F9t3un3TsfMyO3mInsh2SW1Sa7zlZKo8_E0zHjg42pGAjxBPxp7cTvUHOMldLjv-IVGUbDoUw295VTlh-WZslqa8kxsLL2ACtHWxh175NffnQfAAGikSGxYQGfPEvGfPSIWtOH0TiBVo2pWJEBJtKhQp4YYzjG9JIdcuMM5IZqHMPioY8vOSA997zQoevf4a7heO7cdp34olRiTGr07OhwH1IdoO2A7dLMbwahZq6MbRhKZWqxk7rBxTGVbuHmhCgb5qDgmIx_J6XtHHukHTrYYqx_YpzYng8aO4RYayql7hU-1ZJl0akqHBE_D9KLolwL-Dibzc7iSln9XjtqTF1UpMkJ2EmXR-BgQErsN4pxIJKr0RVO1qrxAqaTO4fbc9bKulZm3cfDY3aZDgvFGErWjmzhN7KmfX5rXyDeX8Pt1mU-hXjdBOrtuB97vK4GPUtmJ41XcRMEGDLD8do0nJ73zhUhSlyRw0t3vPqD8cjfLs-axiFgNBrkUd9Ulp50c-GLxlXAVlJX-ffpZyiSn7H0eLCUySZQcQdXlxj4El0Yv_FZvIKElqqGTruVLhzu7VRKCh22_5toOyxsWqLwwzK-cCbYNdg-hy-p9D7sbiZWUnts_wLUOF3CJgQAAA==)):
33+
34+
```svelte
35+
<!--- file: App.svelte --->
36+
<script>
37+
import tippy from 'tippy.js';
38+
39+
let content = $state('Hello!');
40+
41+
/**
42+
* @param {string} content
43+
* @returns {import('svelte/attachments').Attachment}
44+
*/
45+
function tooltip(content) {
46+
return (element) => {
47+
const tooltip = tippy(element, { content });
48+
return tooltip.destroy;
49+
};
50+
}
51+
</script>
52+
53+
<input bind:value={content} />
54+
55+
<button {@attach tooltip(content)}>
56+
Hover me
57+
</button>
58+
```
59+
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).)
61+
62+
## Inline attachments
63+
64+
Attachments can also be created inline ([demo](/playground/untitled#H4sIAAAAAAAAE71Wf3OaWBT9KoyTTnW3MS-I3dYmnWXVtnRAazRJzbozRSQEApiRhwKO333vuY8m225m_9yZGOT9OPfcc84D943UTfxGr_G7K6Xr3TVeNW7D2M8avT_3DVk-YAoDNF4vNB8e2tnWjyXGlm7mPzfurVPpp5JgGmeZtwkf5PtFupCxLzVvHa832rl2lElX-s2Xm2DZFNqp_hs-rZetd4v07ORpT3qmQHu7MF2td0BZp8k6z_xkvfXP902_pZ2_1_aYWEiqm0kN8I4r79qbdZ6umnq3q_2iNf22F4dE6qt2oimwdpim_uY6XMm7Fuo-IQT_iTD_CeGTHwZ38ieIJUFQRxirR1Xf39Dw0X5z0I72Af4tD61vvPNwWKQnqmfPTbduhsEd2J3vO_oBd3dc6fF2X7umNdWGf0vBRhSS6qoV7cCXfTXWfKmvWG61_si_vfU92Wz-E4RhsLhNIYinsox9QKGVd8-tuACCeKXRX12P-T_eKf7fhTq0Hvt-f3ailtSeoxJHRo1-58NoPe1UiBc1hkL8Yeh45y_vQ3mcuNl9T8s3cXPRWLnS7YWJG_gn2Tb4tUjid8jua-PVl08j_ab8I14mH8Llx0s5Tz5Err4ql52r_GYg0mVy1bEGZuD0ze64b5TWYFiM-16wSuJ4JT5vfVpDcztrcG_YkRU4s6HxufzDWF4XuVeJ1P10IbzBemt3Vp1V2e04ZXfrJd7Wicyd039brRIv_RIVu_nXi7X1cfL2sy66ztToUp1TO7qJ7NlwZ0f30pld5qNSVE5o6PbMojFHjgZB7oSicPpGteyLclQap7SvY0dXtM_LR1NT2JFHey3aaxa0VxCeYJ7RMHemoiCcgPZV9pR7o7kgcOjeGliYk9hjDZx8FAq6enwlTPSZj_vYPw9Il64dXdIY8ZmapzwfEd8-1ZyaxWhqkIZOibXUd-6Upqi1pD4uMicCV1GA_7zi73UN8BaF4sC8peJtMjfmjbHZBFwq5ov50qRaE0l96NZggnW4KqypYRAW-uhSz9ADvklwJF2J-5W0Z5fQPBhDX92R6I_0IFxRgDftge4l4dP-gH1hjD7uqU6fsOEZ9UNrCdPB-nys6uXgY6O3ZMd9sy5T9PghqrWHdjo4jB51CgLiKJaDYYA-7WgYONf1FbjkI-mE3EAfUY_rijfuJ_CVPaR50oe9JF7Q0pI8Dw3osxxYHdYPGbp2CnwHF8KvwJv2wEv0Z3ilQI6U9uwbZxbYJXvEmjjQjjCHkvNLvNg3yhzXQd1olamsT4IRrZmX0MUDpwL7R8zzHj7pSh9hPHFSHjLezKqAST51uC5zmtQ87skDUaneLokT5RbXkPWSYz53Abgjc8_o4KFGUZ-Hgv2Z1l5OTYM9D-HfUD0L-EwxH5wRnIG61gS-khfgY1bq7IAP_DA4l5xRuh9xlm8yGjutc8t-wHtkhWv3hc7aqGwiK5KzgvM5xRkZYn193uEln-su55j1GaIv7oM4iPrsVHiG0Dx7TR9-1lBfqFdwfvSd5LNL5xyZVp5NoHFZ57FkfiF6vKs4k5zvIfrX5xX6MXmt0gM5MTu8DjnhukrHHzTRd3jm0dma0_f_x5cxP9f4jBdqHvmbq2fUjzqcKh2Cp-yWj9ntcHanXmBXxhu7Q--eyjhfNFpaV7zgz4nWEUb7zUOhpevjjf_gu_KZ99pxFlZ-T3sttkmYqrco_26q35v0Ewzv5EZPbnL_8BfduWGMnyyN3q0bZ_7hb_7KG_L4CQAA)):
65+
66+
```svelte
67+
<!--- file: App.svelte --->
68+
<canvas
69+
width={32}
70+
height={32}
71+
{@attach (canvas) => {
72+
const context = canvas.getContext('2d');
73+
74+
$effect(() => {
75+
context.fillStyle = color;
76+
context.fillRect(0, 0, canvas.width, canvas.height);
77+
});
78+
}}
79+
></canvas>
80+
```
81+
82+
> [!NOTE]
83+
> The nested effect runs whenever `color` changes, while the outer effect (where `canvas.getContext(...)` is called) only runs once, since it doesn't read any reactive state.
84+
85+
## Passing attachments to components
86+
87+
When used on a component, `{@attach ...}` will create a prop whose key is a [`Symbol`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol). If the component then [spreads](/tutorial/svelte/spread-props) props onto an element, the element will receive those attachments.
88+
89+
This allows you to create _wrapper components_ that augment elements ([demo](/playground/untitled#H4sIAAAAAAAAE3VUS3ObMBD-KxvajnFqsJM2PhA7TXrKob31FjITAbKtRkiMtDhJPfz3LiAMdpxhGJvdb1_fPnaeYjn3Iu-WIbJ04028lZDcetHDzsO3olbVApI74F1RhHbLJdayhFl-Sp5qhVwhufEWNjWiwJtYxSjyQhsEFEXxBiujcxg1_8O_dnQ9APwsEbVyiHDafjrvDZCgkiO4MLCEzxYZcn90z6XUZ6OxA61KlaIgV6i1pFC-sxjDrlbHaDiWRoGvdMbHsLzp5DES0mJnRxGaRBvcBHb7yFUTCQeunEWYcYtGv12TqgFUDbCK1WLaM6IWQhUlQiJUFm2ZLPly51xXMG0Rjoyd69C7UqqG2nu95QZyXvtvLVpri2-SN4hoLXXCZFfhQ8aQBU1VgdEaH_vSgyBZR_BpPp_vi0tY-rw2ulRZkGqpTQRbZvwa2BPgFC8bgbw31CbjJjAsE6WNYBZeGp7vtQXLMqHWnZx-5kM1TR5ycpkZXQR2wzL94l8Ur1C_3-g168SfQf1MyfRi3LW9fs77emJEw5QV9SREoLTq06tcczq7d6xEUcJX2vAhO1b843XK34e5unZEMBr15ekuKEusluWAF8lXhE2ZTP2r2RcIHJ-163FPKerCgYJLOB9i4GvNwviI5-gAQiFFBk3tBTOU3HFXEk0R8o86WvUD64aINhv5K3oRmpJXkw8uxMG6Hh6JY9X7OwGSqfUy9tDG3sHNoEi0d_d_fv9qndxRU0VClFqo3KVo3U655Hnt1PXB3Qra2Y2QGdEwgTAMCxopsoxOe6SD0gD8movDhT0LAnhqlE8gVCpLWnRoV7OJCkFAwEXitrYL1W7p7pbiE_P7XH6E_rihODm5s52XtiH9Ekaw0VgI9exadWL1uoEYjPtg2672k5szsxbKyWB2fdT0w5Y_0hcT8oXOlRetmLS8-g-6TLXXQgYAAA==)):
90+
91+
```svelte
92+
<!--- file: Button.svelte --->
93+
<script>
94+
/** @type {import('svelte/elements').HTMLButtonAttributes} */
95+
let { children, ...props } = $props();
96+
</script>
97+
98+
<!-- `props` includes attachments -->
99+
<button {...props}>
100+
{@render children?.()}
101+
</button>
102+
```
103+
104+
```svelte
105+
<!--- file: App.svelte --->
106+
<script>
107+
import tippy from 'tippy.js';
108+
import Button from './Button.svelte';
109+
110+
let content = $state('Hello!');
111+
112+
/**
113+
* @param {string} content
114+
* @returns {import('svelte/attachments').Attachment}
115+
*/
116+
function tooltip(content) {
117+
return (element) => {
118+
const tooltip = tippy(element, { content });
119+
return tooltip.destroy;
120+
};
121+
}
122+
</script>
123+
124+
<input bind:value={content} />
125+
126+
<Button {@attach tooltip(content)}>
127+
Hover me
128+
</Button>
129+
```
130+
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+
160+
## Creating attachments programmatically
161+
162+
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.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)