mirror of https://github.com/sveltejs/svelte
commit
4a19fd18c0
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: handle more hydration mismatches
|
@ -0,0 +1,131 @@
|
||||
---
|
||||
title: {@attach ...}
|
||||
---
|
||||
|
||||
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.
|
||||
|
||||
> [!NOTE]
|
||||
> Attachments are available in Svelte 5.29 and newer.
|
||||
|
||||
```svelte
|
||||
<!--- file: App.svelte --->
|
||||
<script>
|
||||
/** @type {import('svelte/attachments').Attachment} */
|
||||
function myAttachment(element) {
|
||||
console.log(element.nodeName); // 'DIV'
|
||||
|
||||
return () => {
|
||||
console.log('cleaning up');
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<div {@attach myAttachment}>...</div>
|
||||
```
|
||||
|
||||
An element can have any number of attachments.
|
||||
|
||||
## Attachment factories
|
||||
|
||||
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==)):
|
||||
|
||||
```svelte
|
||||
<!--- file: App.svelte --->
|
||||
<script>
|
||||
import tippy from 'tippy.js';
|
||||
|
||||
let content = $state('Hello!');
|
||||
|
||||
/**
|
||||
* @param {string} content
|
||||
* @returns {import('svelte/attachments').Attachment}
|
||||
*/
|
||||
function tooltip(content) {
|
||||
return (element) => {
|
||||
const tooltip = tippy(element, { content });
|
||||
return tooltip.destroy;
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<input bind:value={content} />
|
||||
|
||||
<button {@attach tooltip(content)}>
|
||||
Hover me
|
||||
</button>
|
||||
```
|
||||
|
||||
Since the `tooltip(content)` expression runs inside an [effect]($effect), the attachment will be destroyed and recreated whenever `content` changes.
|
||||
|
||||
## Inline attachments
|
||||
|
||||
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)):
|
||||
|
||||
```svelte
|
||||
<!--- file: App.svelte --->
|
||||
<canvas
|
||||
width={32}
|
||||
height={32}
|
||||
{@attach (canvas) => {
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
$effect(() => {
|
||||
context.fillStyle = color;
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
});
|
||||
}}
|
||||
></canvas>
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> 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.
|
||||
|
||||
## Passing attachments to components
|
||||
|
||||
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.
|
||||
|
||||
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==)):
|
||||
|
||||
```svelte
|
||||
<!--- file: Button.svelte --->
|
||||
<script>
|
||||
/** @type {import('svelte/elements').HTMLButtonAttributes} */
|
||||
let { children, ...props } = $props();
|
||||
</script>
|
||||
|
||||
<!-- `props` includes attachments -->
|
||||
<button {...props}>
|
||||
{@render children?.()}
|
||||
</button>
|
||||
```
|
||||
|
||||
```svelte
|
||||
<!--- file: App.svelte --->
|
||||
<script>
|
||||
import tippy from 'tippy.js';
|
||||
import Button from './Button.svelte';
|
||||
|
||||
let content = $state('Hello!');
|
||||
|
||||
/**
|
||||
* @param {string} content
|
||||
* @returns {import('svelte/attachments').Attachment}
|
||||
*/
|
||||
function tooltip(content) {
|
||||
return (element) => {
|
||||
const tooltip = tippy(element, { content });
|
||||
return tooltip.destroy;
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<input bind:value={content} />
|
||||
|
||||
<Button {@attach tooltip(content)}>
|
||||
Hover me
|
||||
</Button>
|
||||
```
|
||||
|
||||
## Creating attachments programmatically
|
||||
|
||||
To add attachments to an object that will be spread onto a component or element, use [`createAttachmentKey`](svelte-attachments#createAttachmentKey).
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
title: svelte/attachments
|
||||
---
|
||||
|
||||
> MODULE: svelte/attachments
|
@ -0,0 +1,27 @@
|
||||
import { ATTACHMENT_KEY } from '../constants.js';
|
||||
|
||||
/**
|
||||
* Creates an object key that will be recognised as an attachment when the object is spread onto an element,
|
||||
* as a programmatic alternative to using `{@attach ...}`. This can be useful for library authors, though
|
||||
* is generally not needed when building an app.
|
||||
*
|
||||
* ```svelte
|
||||
* <script>
|
||||
* import { createAttachmentKey } from 'svelte/attachments';
|
||||
*
|
||||
* const props = {
|
||||
* class: 'cool',
|
||||
* onclick: () => alert('clicked'),
|
||||
* [createAttachmentKey()]: (node) => {
|
||||
* node.textContent = 'attached!';
|
||||
* }
|
||||
* };
|
||||
* </script>
|
||||
*
|
||||
* <button {...props}>click me</button>
|
||||
* ```
|
||||
* @since 5.29
|
||||
*/
|
||||
export function createAttachmentKey() {
|
||||
return Symbol(ATTACHMENT_KEY);
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* An [attachment](https://svelte.dev/docs/svelte/@attach) is a function that runs when an element is mounted
|
||||
* to the DOM, and optionally returns a function that is called when the element is later removed.
|
||||
*
|
||||
* It can be attached to an element with an `{@attach ...}` tag, or by spreading an object containing
|
||||
* a property created with [`createAttachmentKey`](https://svelte.dev/docs/svelte/svelte-attachments#createAttachmentKey).
|
||||
*/
|
||||
export interface Attachment<T extends EventTarget = Element> {
|
||||
(element: T): void | (() => void);
|
||||
}
|
||||
|
||||
export * from './index.js';
|
@ -0,0 +1,13 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
|
||||
import { mark_subtree_dynamic } from './shared/fragment.js';
|
||||
|
||||
/**
|
||||
* @param {AST.AttachTag} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function AttachTag(node, context) {
|
||||
mark_subtree_dynamic(context.path);
|
||||
context.next({ ...context.state, expression: node.metadata.expression });
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
/** @import { Literal } from 'estree' */
|
||||
import * as w from '../../../warnings.js';
|
||||
import { regex_bidirectional_control_characters } from '../../patterns.js';
|
||||
|
||||
/**
|
||||
* @param {Literal} node
|
||||
*/
|
||||
export function Literal(node) {
|
||||
if (typeof node.value === 'string') {
|
||||
if (regex_bidirectional_control_characters.test(node.value)) {
|
||||
w.bidirectional_control_characters(node);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
/** @import { TemplateElement } from 'estree' */
|
||||
import * as w from '../../../warnings.js';
|
||||
import { regex_bidirectional_control_characters } from '../../patterns.js';
|
||||
|
||||
/**
|
||||
* @param {TemplateElement} node
|
||||
*/
|
||||
export function TemplateElement(node) {
|
||||
if (regex_bidirectional_control_characters.test(node.value.cooked ?? '')) {
|
||||
w.bidirectional_control_characters(node);
|
||||
}
|
||||
}
|
@ -1,20 +1,52 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { is_tag_valid_with_parent } from '../../../../html-tree-validation.js';
|
||||
import { regex_not_whitespace } from '../../patterns.js';
|
||||
import { regex_bidirectional_control_characters, regex_not_whitespace } from '../../patterns.js';
|
||||
import * as e from '../../../errors.js';
|
||||
import * as w from '../../../warnings.js';
|
||||
import { extract_svelte_ignore } from '../../../utils/extract_svelte_ignore.js';
|
||||
|
||||
/**
|
||||
* @param {AST.Text} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function Text(node, context) {
|
||||
const in_template = context.path.at(-1)?.type === 'Fragment';
|
||||
const parent = /** @type {AST.SvelteNode} */ (context.path.at(-1));
|
||||
|
||||
if (in_template && context.state.parent_element && regex_not_whitespace.test(node.data)) {
|
||||
if (
|
||||
parent.type === 'Fragment' &&
|
||||
context.state.parent_element &&
|
||||
regex_not_whitespace.test(node.data)
|
||||
) {
|
||||
const message = is_tag_valid_with_parent('#text', context.state.parent_element);
|
||||
if (message) {
|
||||
e.node_invalid_placement(node, message);
|
||||
}
|
||||
}
|
||||
|
||||
regex_bidirectional_control_characters.lastIndex = 0;
|
||||
for (const match of node.data.matchAll(regex_bidirectional_control_characters)) {
|
||||
let is_ignored = false;
|
||||
|
||||
// if we have a svelte-ignore comment earlier in the text, bail
|
||||
// (otherwise we can only use svelte-ignore on parent elements/blocks)
|
||||
if (parent.type === 'Fragment') {
|
||||
for (const child of parent.nodes) {
|
||||
if (child === node) break;
|
||||
|
||||
if (child.type === 'Comment') {
|
||||
is_ignored ||= extract_svelte_ignore(
|
||||
child.start + 4,
|
||||
child.data,
|
||||
context.state.analysis.runes
|
||||
).includes('bidirectional_control_characters');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_ignored) {
|
||||
let start = match.index + node.start;
|
||||
w.bidirectional_control_characters({ start, end: start + match[0].length });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
/** @import { Expression } from 'estree' */
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { ComponentContext } from '../types' */
|
||||
import * as b from '../../../../utils/builders.js';
|
||||
|
||||
/**
|
||||
* @param {AST.AttachTag} node
|
||||
* @param {ComponentContext} context
|
||||
*/
|
||||
export function AttachTag(node, context) {
|
||||
context.state.init.push(
|
||||
b.stmt(
|
||||
b.call(
|
||||
'$.attach',
|
||||
context.state.node,
|
||||
b.thunk(/** @type {Expression} */ (context.visit(node.expression)))
|
||||
)
|
||||
)
|
||||
);
|
||||
context.next();
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
import { effect } from '../../reactivity/effects.js';
|
||||
|
||||
/**
|
||||
* @param {Element} node
|
||||
* @param {() => (node: Element) => void} get_fn
|
||||
*/
|
||||
export function attach(node, get_fn) {
|
||||
effect(() => {
|
||||
const fn = get_fn();
|
||||
|
||||
// we use `&&` rather than `?.` so that things like
|
||||
// `{@attach DEV && something_dev_only()}` work
|
||||
return fn && fn(node);
|
||||
});
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
error: {
|
||||
code: 'rune_invalid_name',
|
||||
message: '`$state.foo` is not a valid rune'
|
||||
}
|
||||
});
|
@ -0,0 +1,5 @@
|
||||
class State {
|
||||
value = $state.foo();
|
||||
}
|
||||
|
||||
export const state = new State();
|
@ -0,0 +1,6 @@
|
||||
import { test } from '../../test';
|
||||
|
||||
// https://github.com/sveltejs/svelte/issues/15819
|
||||
export default test({
|
||||
expect_hydration_error: true
|
||||
});
|
@ -0,0 +1 @@
|
||||
<p>start</p><p>cond</p><!---->
|
@ -0,0 +1 @@
|
||||
<!--[--> <p>start</p><!--[--><p>cond</p><!--]--><!--]-->
|
@ -0,0 +1,5 @@
|
||||
<script>
|
||||
const cond = true;
|
||||
</script>
|
||||
|
||||
<p>start</p>{#if cond}<p>cond</p>{/if}
|
@ -0,0 +1,6 @@
|
||||
import { test } from '../../test';
|
||||
|
||||
// https://github.com/sveltejs/svelte/issues/15819
|
||||
export default test({
|
||||
expect_hydration_error: true
|
||||
});
|
@ -0,0 +1 @@
|
||||
<p>start</p> pre123 mid<!---->
|
@ -0,0 +1 @@
|
||||
<!--[--><p>start</p>pre<a>123</a> <!--[-->mid<!--]--><!--]-->
|
@ -0,0 +1,9 @@
|
||||
<script>
|
||||
let cond = true;
|
||||
</script>
|
||||
|
||||
<p>start</p>
|
||||
pre123
|
||||
{#if cond}
|
||||
mid
|
||||
{/if}
|
@ -1,2 +1,2 @@
|
||||
<!-- @migration-task Error while migrating Svelte code: This migration would change the name of a slot making the component unusable -->
|
||||
<!-- @migration-task Error while migrating Svelte code: This migration would change the name of a slot (dashed-name to dashed_name) making the component unusable -->
|
||||
<slot name="dashed-name"></slot>
|
@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
</script>
|
||||
|
||||
{#snippet generic<T extends string>(val: T)}
|
||||
{val}
|
||||
{/snippet}
|
||||
|
||||
{#snippet complex_generic<T extends { bracket: "<" } | "<" | Set<"<>">>(val: T)}
|
||||
{val}
|
||||
{/snippet}
|
@ -0,0 +1,244 @@
|
||||
{
|
||||
"html": {
|
||||
"type": "Fragment",
|
||||
"start": 30,
|
||||
"end": 192,
|
||||
"children": [
|
||||
{
|
||||
"type": "Text",
|
||||
"start": 28,
|
||||
"end": 30,
|
||||
"raw": "\n\n",
|
||||
"data": "\n\n"
|
||||
},
|
||||
{
|
||||
"type": "SnippetBlock",
|
||||
"start": 30,
|
||||
"end": 92,
|
||||
"expression": {
|
||||
"type": "Identifier",
|
||||
"start": 40,
|
||||
"end": 47,
|
||||
"name": "generic"
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"type": "Identifier",
|
||||
"start": 66,
|
||||
"end": 72,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 4,
|
||||
"column": 36
|
||||
},
|
||||
"end": {
|
||||
"line": 4,
|
||||
"column": 42
|
||||
}
|
||||
},
|
||||
"name": "val",
|
||||
"typeAnnotation": {
|
||||
"type": "TSTypeAnnotation",
|
||||
"start": 69,
|
||||
"end": 72,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 4,
|
||||
"column": 39
|
||||
},
|
||||
"end": {
|
||||
"line": 4,
|
||||
"column": 42
|
||||
}
|
||||
},
|
||||
"typeAnnotation": {
|
||||
"type": "TSTypeReference",
|
||||
"start": 71,
|
||||
"end": 72,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 4,
|
||||
"column": 41
|
||||
},
|
||||
"end": {
|
||||
"line": 4,
|
||||
"column": 42
|
||||
}
|
||||
},
|
||||
"typeName": {
|
||||
"type": "Identifier",
|
||||
"start": 71,
|
||||
"end": 72,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 4,
|
||||
"column": 41
|
||||
},
|
||||
"end": {
|
||||
"line": 4,
|
||||
"column": 42
|
||||
}
|
||||
},
|
||||
"name": "T"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"children": [
|
||||
{
|
||||
"type": "MustacheTag",
|
||||
"start": 76,
|
||||
"end": 81,
|
||||
"expression": {
|
||||
"type": "Identifier",
|
||||
"start": 77,
|
||||
"end": 80,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 5,
|
||||
"column": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 5,
|
||||
"column": 5
|
||||
}
|
||||
},
|
||||
"name": "val"
|
||||
}
|
||||
}
|
||||
],
|
||||
"typeParams": "T extends string"
|
||||
},
|
||||
{
|
||||
"type": "Text",
|
||||
"start": 92,
|
||||
"end": 94,
|
||||
"raw": "\n\n",
|
||||
"data": "\n\n"
|
||||
},
|
||||
{
|
||||
"type": "SnippetBlock",
|
||||
"start": 94,
|
||||
"end": 192,
|
||||
"expression": {
|
||||
"type": "Identifier",
|
||||
"start": 104,
|
||||
"end": 119,
|
||||
"name": "complex_generic"
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"type": "Identifier",
|
||||
"start": 166,
|
||||
"end": 172,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 8,
|
||||
"column": 72
|
||||
},
|
||||
"end": {
|
||||
"line": 8,
|
||||
"column": 78
|
||||
}
|
||||
},
|
||||
"name": "val",
|
||||
"typeAnnotation": {
|
||||
"type": "TSTypeAnnotation",
|
||||
"start": 169,
|
||||
"end": 172,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 8,
|
||||
"column": 75
|
||||
},
|
||||
"end": {
|
||||
"line": 8,
|
||||
"column": 78
|
||||
}
|
||||
},
|
||||
"typeAnnotation": {
|
||||
"type": "TSTypeReference",
|
||||
"start": 171,
|
||||
"end": 172,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 8,
|
||||
"column": 77
|
||||
},
|
||||
"end": {
|
||||
"line": 8,
|
||||
"column": 78
|
||||
}
|
||||
},
|
||||
"typeName": {
|
||||
"type": "Identifier",
|
||||
"start": 171,
|
||||
"end": 172,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 8,
|
||||
"column": 77
|
||||
},
|
||||
"end": {
|
||||
"line": 8,
|
||||
"column": 78
|
||||
}
|
||||
},
|
||||
"name": "T"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"children": [
|
||||
{
|
||||
"type": "MustacheTag",
|
||||
"start": 176,
|
||||
"end": 181,
|
||||
"expression": {
|
||||
"type": "Identifier",
|
||||
"start": 177,
|
||||
"end": 180,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 9,
|
||||
"column": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 9,
|
||||
"column": 5
|
||||
}
|
||||
},
|
||||
"name": "val"
|
||||
}
|
||||
}
|
||||
],
|
||||
"typeParams": "T extends { bracket: \"<\" } | \"<\" | Set<\"<>\">"
|
||||
}
|
||||
]
|
||||
},
|
||||
"instance": {
|
||||
"type": "Script",
|
||||
"start": 0,
|
||||
"end": 28,
|
||||
"context": "default",
|
||||
"content": {
|
||||
"type": "Program",
|
||||
"start": 18,
|
||||
"end": 19,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 2,
|
||||
"column": 0
|
||||
}
|
||||
},
|
||||
"body": [],
|
||||
"sourceType": "module"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
<div {@attach (node) => {}} {@attach (node) => {}}></div>
|
@ -0,0 +1,141 @@
|
||||
{
|
||||
"css": null,
|
||||
"js": [],
|
||||
"start": 0,
|
||||
"end": 57,
|
||||
"type": "Root",
|
||||
"fragment": {
|
||||
"type": "Fragment",
|
||||
"nodes": [
|
||||
{
|
||||
"type": "RegularElement",
|
||||
"start": 0,
|
||||
"end": 57,
|
||||
"name": "div",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "AttachTag",
|
||||
"start": 5,
|
||||
"end": 27,
|
||||
"expression": {
|
||||
"type": "ArrowFunctionExpression",
|
||||
"start": 14,
|
||||
"end": 26,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 14
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 26
|
||||
}
|
||||
},
|
||||
"id": null,
|
||||
"expression": false,
|
||||
"generator": false,
|
||||
"async": false,
|
||||
"params": [
|
||||
{
|
||||
"type": "Identifier",
|
||||
"start": 15,
|
||||
"end": 19,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 15
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 19
|
||||
}
|
||||
},
|
||||
"name": "node"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"type": "BlockStatement",
|
||||
"start": 24,
|
||||
"end": 26,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 24
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 26
|
||||
}
|
||||
},
|
||||
"body": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "AttachTag",
|
||||
"start": 28,
|
||||
"end": 50,
|
||||
"expression": {
|
||||
"type": "ArrowFunctionExpression",
|
||||
"start": 37,
|
||||
"end": 49,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 37
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 49
|
||||
}
|
||||
},
|
||||
"id": null,
|
||||
"expression": false,
|
||||
"generator": false,
|
||||
"async": false,
|
||||
"params": [
|
||||
{
|
||||
"type": "Identifier",
|
||||
"start": 38,
|
||||
"end": 42,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 38
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 42
|
||||
}
|
||||
},
|
||||
"name": "node"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"type": "BlockStatement",
|
||||
"start": 47,
|
||||
"end": 49,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 47
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"column": 49
|
||||
}
|
||||
},
|
||||
"body": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"fragment": {
|
||||
"type": "Fragment",
|
||||
"nodes": []
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": null
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
</script>
|
||||
|
||||
{#snippet generic<T extends string>(val: T)}
|
||||
{val}
|
||||
{/snippet}
|
||||
|
||||
{#snippet complex_generic<T extends { bracket: "<" } | "<" | Set<"<>">>(val: T)}
|
||||
{val}
|
||||
{/snippet}
|
@ -0,0 +1,299 @@
|
||||
{
|
||||
"css": null,
|
||||
"js": [],
|
||||
"start": 30,
|
||||
"end": 192,
|
||||
"type": "Root",
|
||||
"fragment": {
|
||||
"type": "Fragment",
|
||||
"nodes": [
|
||||
{
|
||||
"type": "Text",
|
||||
"start": 28,
|
||||
"end": 30,
|
||||
"raw": "\n\n",
|
||||
"data": "\n\n"
|
||||
},
|
||||
{
|
||||
"type": "SnippetBlock",
|
||||
"start": 30,
|
||||
"end": 92,
|
||||
"expression": {
|
||||
"type": "Identifier",
|
||||
"start": 40,
|
||||
"end": 47,
|
||||
"name": "generic"
|
||||
},
|
||||
"typeParams": "T extends string",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "Identifier",
|
||||
"start": 66,
|
||||
"end": 72,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 4,
|
||||
"column": 36
|
||||
},
|
||||
"end": {
|
||||
"line": 4,
|
||||
"column": 42
|
||||
}
|
||||
},
|
||||
"name": "val",
|
||||
"typeAnnotation": {
|
||||
"type": "TSTypeAnnotation",
|
||||
"start": 69,
|
||||
"end": 72,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 4,
|
||||
"column": 39
|
||||
},
|
||||
"end": {
|
||||
"line": 4,
|
||||
"column": 42
|
||||
}
|
||||
},
|
||||
"typeAnnotation": {
|
||||
"type": "TSTypeReference",
|
||||
"start": 71,
|
||||
"end": 72,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 4,
|
||||
"column": 41
|
||||
},
|
||||
"end": {
|
||||
"line": 4,
|
||||
"column": 42
|
||||
}
|
||||
},
|
||||
"typeName": {
|
||||
"type": "Identifier",
|
||||
"start": 71,
|
||||
"end": 72,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 4,
|
||||
"column": 41
|
||||
},
|
||||
"end": {
|
||||
"line": 4,
|
||||
"column": 42
|
||||
}
|
||||
},
|
||||
"name": "T"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"type": "Fragment",
|
||||
"nodes": [
|
||||
{
|
||||
"type": "Text",
|
||||
"start": 74,
|
||||
"end": 76,
|
||||
"raw": "\n\t",
|
||||
"data": "\n\t"
|
||||
},
|
||||
{
|
||||
"type": "ExpressionTag",
|
||||
"start": 76,
|
||||
"end": 81,
|
||||
"expression": {
|
||||
"type": "Identifier",
|
||||
"start": 77,
|
||||
"end": 80,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 5,
|
||||
"column": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 5,
|
||||
"column": 5
|
||||
}
|
||||
},
|
||||
"name": "val"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Text",
|
||||
"start": 81,
|
||||
"end": 82,
|
||||
"raw": "\n",
|
||||
"data": "\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Text",
|
||||
"start": 92,
|
||||
"end": 94,
|
||||
"raw": "\n\n",
|
||||
"data": "\n\n"
|
||||
},
|
||||
{
|
||||
"type": "SnippetBlock",
|
||||
"start": 94,
|
||||
"end": 192,
|
||||
"expression": {
|
||||
"type": "Identifier",
|
||||
"start": 104,
|
||||
"end": 119,
|
||||
"name": "complex_generic"
|
||||
},
|
||||
"typeParams": "T extends { bracket: \"<\" } | \"<\" | Set<\"<>\">",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "Identifier",
|
||||
"start": 166,
|
||||
"end": 172,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 8,
|
||||
"column": 72
|
||||
},
|
||||
"end": {
|
||||
"line": 8,
|
||||
"column": 78
|
||||
}
|
||||
},
|
||||
"name": "val",
|
||||
"typeAnnotation": {
|
||||
"type": "TSTypeAnnotation",
|
||||
"start": 169,
|
||||
"end": 172,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 8,
|
||||
"column": 75
|
||||
},
|
||||
"end": {
|
||||
"line": 8,
|
||||
"column": 78
|
||||
}
|
||||
},
|
||||
"typeAnnotation": {
|
||||
"type": "TSTypeReference",
|
||||
"start": 171,
|
||||
"end": 172,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 8,
|
||||
"column": 77
|
||||
},
|
||||
"end": {
|
||||
"line": 8,
|
||||
"column": 78
|
||||
}
|
||||
},
|
||||
"typeName": {
|
||||
"type": "Identifier",
|
||||
"start": 171,
|
||||
"end": 172,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 8,
|
||||
"column": 77
|
||||
},
|
||||
"end": {
|
||||
"line": 8,
|
||||
"column": 78
|
||||
}
|
||||
},
|
||||
"name": "T"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"type": "Fragment",
|
||||
"nodes": [
|
||||
{
|
||||
"type": "Text",
|
||||
"start": 174,
|
||||
"end": 176,
|
||||
"raw": "\n\t",
|
||||
"data": "\n\t"
|
||||
},
|
||||
{
|
||||
"type": "ExpressionTag",
|
||||
"start": 176,
|
||||
"end": 181,
|
||||
"expression": {
|
||||
"type": "Identifier",
|
||||
"start": 177,
|
||||
"end": 180,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 9,
|
||||
"column": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 9,
|
||||
"column": 5
|
||||
}
|
||||
},
|
||||
"name": "val"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Text",
|
||||
"start": 181,
|
||||
"end": 182,
|
||||
"raw": "\n",
|
||||
"data": "\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": null,
|
||||
"instance": {
|
||||
"type": "Script",
|
||||
"start": 0,
|
||||
"end": 28,
|
||||
"context": "default",
|
||||
"content": {
|
||||
"type": "Program",
|
||||
"start": 18,
|
||||
"end": 19,
|
||||
"loc": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"column": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 2,
|
||||
"column": 0
|
||||
}
|
||||
},
|
||||
"body": [],
|
||||
"sourceType": "module"
|
||||
},
|
||||
"attributes": [
|
||||
{
|
||||
"type": "Attribute",
|
||||
"start": 8,
|
||||
"end": 17,
|
||||
"name": "lang",
|
||||
"value": [
|
||||
{
|
||||
"start": 14,
|
||||
"end": 16,
|
||||
"type": "Text",
|
||||
"raw": "ts",
|
||||
"data": "ts"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
ssrHtml: `<div></div>`,
|
||||
html: `<div>DIV</div>`
|
||||
});
|
@ -0,0 +1 @@
|
||||
<div {@attach (node) => node.textContent = node.nodeName}></div>
|
@ -0,0 +1,5 @@
|
||||
<script>
|
||||
let props = $props();
|
||||
</script>
|
||||
|
||||
<div {...props}></div>
|
@ -0,0 +1,15 @@
|
||||
import { flushSync } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
html: `<button>update</button><div></div>`,
|
||||
|
||||
test({ target, assert, logs }) {
|
||||
const button = target.querySelector('button');
|
||||
|
||||
assert.deepEqual(logs, ['one DIV']);
|
||||
|
||||
flushSync(() => button?.click());
|
||||
assert.deepEqual(logs, ['one DIV', 'cleanup one', 'two DIV']);
|
||||
}
|
||||
});
|
@ -0,0 +1,30 @@
|
||||
<script>
|
||||
import { createAttachmentKey } from 'svelte/attachments';
|
||||
import Child from './Child.svelte';
|
||||
|
||||
let stuff = $state({
|
||||
[createAttachmentKey()]: (node) => {
|
||||
console.log(`one ${node.nodeName}`);
|
||||
|
||||
return () => {
|
||||
console.log('cleanup one');
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
function update() {
|
||||
stuff = {
|
||||
[createAttachmentKey()]: (node) => {
|
||||
console.log(`two ${node.nodeName}`);
|
||||
|
||||
return () => {
|
||||
console.log('cleanup two');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onclick={update}>update</button>
|
||||
|
||||
<Child {...stuff} />
|
@ -0,0 +1,5 @@
|
||||
<script>
|
||||
let props = $props();
|
||||
</script>
|
||||
|
||||
<div {...props}></div>
|
@ -0,0 +1,14 @@
|
||||
import { flushSync } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
ssrHtml: `<button>update</button><div></div>`,
|
||||
html: `<button>update</button><div>one</div>`,
|
||||
|
||||
test({ target, assert }) {
|
||||
const button = target.querySelector('button');
|
||||
|
||||
flushSync(() => button?.click());
|
||||
assert.htmlEqual(target.innerHTML, '<button>update</button><div>two</div>');
|
||||
}
|
||||
});
|
@ -0,0 +1,15 @@
|
||||
<script>
|
||||
import Child from './Child.svelte';
|
||||
|
||||
let message = $state('one');
|
||||
|
||||
function attachment(message) {
|
||||
return (node) => {
|
||||
node.textContent = message;
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onclick={() => message = 'two'}>update</button>
|
||||
|
||||
<Child {@attach attachment(message)} />
|
@ -0,0 +1,14 @@
|
||||
import { flushSync } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
ssrHtml: `<div></div><button>increment</button>`,
|
||||
html: `<div>1</div><button>increment</button>`,
|
||||
|
||||
test: ({ assert, target }) => {
|
||||
const btn = target.querySelector('button');
|
||||
|
||||
flushSync(() => btn?.click());
|
||||
assert.htmlEqual(target.innerHTML, `<div>2</div><button>increment</button>`);
|
||||
}
|
||||
});
|
@ -0,0 +1,6 @@
|
||||
<script>
|
||||
let value = $state(1);
|
||||
</script>
|
||||
|
||||
<div {@attach (node) => node.textContent = value}></div>
|
||||
<button onclick={() => value += 1}>increment</button>
|
@ -0,0 +1,8 @@
|
||||
import { flushSync } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
test({ assert, logs, target }) {
|
||||
assert.deepEqual(logs, ['hello']);
|
||||
}
|
||||
});
|
@ -0,0 +1,9 @@
|
||||
<script>
|
||||
import { createAttachmentKey } from 'svelte/attachments';
|
||||
|
||||
let stuff = $state({
|
||||
[createAttachmentKey()]: () => console.log('hello')
|
||||
});
|
||||
</script>
|
||||
|
||||
<div {...stuff}></div>
|
@ -0,0 +1,6 @@
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
ssrHtml: `<div></div>`,
|
||||
html: `<div>DIV</div>`
|
||||
});
|
@ -0,0 +1 @@
|
||||
<svelte:element this={'div'} {@attach (node) => node.textContent = node.nodeName}></svelte:element>
|
@ -0,0 +1,7 @@
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
// check that this is a runtime error, not a compile time error
|
||||
// caused by over-eager partial-evaluation
|
||||
error: 'Cannot convert invalid to a BigInt'
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue