Merge branch 'main' into remove-readonly-check

remove-readonly-check
Rich Harris 5 months ago committed by GitHub
commit 643e4ebcaf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: align `beforeUpdate`/`afterUpdate` behavior better with that in Svelte 4

@ -0,0 +1,5 @@
---
"svelte": patch
---
chore: bump zimmerframe to fix bugs introduced in previous version

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: properly analyze group expressions

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: allow `let:` directives on slot elements

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: use hybrid scoping strategy for consistent specificity increase

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: handle nested script tags

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: throw validation error when binding to each argument in runes mode

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: disallow exporting props, derived and reassigned state from within components

@ -0,0 +1,5 @@
---
"svelte": patch
---
chore: bump zimmerframe to resolve AST-traversal-related bugs

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: repair each block length mismatches during hydration

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: make CSS animation declaration transformation more robust

@ -9,6 +9,7 @@
"svelte.dev": "1.0.0"
},
"changesets": [
"afraid-dogs-matter",
"afraid-moose-matter",
"angry-books-jam",
"angry-plums-punch",
@ -56,6 +57,7 @@
"fair-pianos-talk",
"famous-falcons-melt",
"famous-knives-sneeze",
"famous-pants-pay",
"fast-weeks-clean",
"few-mugs-fail",
"fifty-rice-wait",
@ -65,8 +67,10 @@
"forty-comics-invent",
"forty-dolls-wave",
"forty-peaches-unite",
"forty-suns-smile",
"four-flies-hammer",
"fresh-weeks-trade",
"friendly-candles-relate",
"friendly-lies-camp",
"funny-wombats-argue",
"gentle-sheep-hug",
@ -112,13 +116,16 @@
"light-pens-watch",
"long-buckets-lay",
"long-crews-return",
"long-lobsters-mate",
"loud-cheetahs-flow",
"loud-ravens-drop",
"lovely-carpets-lick",
"lovely-items-turn",
"lovely-rules-eat",
"lucky-schools-hang",
"lucky-toes-begin",
"many-trees-fix",
"mighty-files-hammer",
"moody-frogs-exist",
"moody-owls-cry",
"nasty-lions-double",
@ -131,13 +138,17 @@
"odd-needles-joke",
"odd-schools-wait",
"odd-shoes-cheat",
"odd-taxis-retire",
"old-flies-jog",
"old-houses-drum",
"old-mails-sneeze",
"old-oranges-compete",
"olive-kangaroos-brake",
"olive-seals-sell",
"olive-shirts-complain",
"olive-socks-kick",
"orange-dingos-poke",
"pink-mayflies-tie",
"polite-dolphins-care",
"polite-pumpkins-guess",
"polite-ravens-study",
@ -150,6 +161,7 @@
"quiet-crabs-nail",
"quiet-timers-speak",
"rare-pears-whisper",
"rare-worms-hunt",
"real-guests-do",
"real-items-suffer",
"red-doors-own",
@ -174,6 +186,8 @@
"shiny-baboons-play",
"shiny-shrimps-march",
"short-buses-camp",
"silent-apes-report",
"silver-points-approve",
"sixty-items-crash",
"slimy-clouds-talk",
"slimy-laws-explode",
@ -196,6 +210,7 @@
"spotty-spiders-compare",
"stale-books-perform",
"stale-comics-look",
"strange-apricots-happen",
"strong-gifts-smoke",
"strong-lemons-provide",
"strong-pans-doubt",
@ -203,6 +218,7 @@
"sweet-pens-sniff",
"swift-donkeys-perform",
"swift-fans-stare",
"swift-feet-juggle",
"swift-ravens-hunt",
"swift-seahorses-deliver",
"tall-books-grin",
@ -226,6 +242,7 @@
"thirty-pears-hug",
"thirty-wombats-relax",
"three-camels-sell",
"three-icons-trade",
"three-suits-grin",
"tidy-buses-whisper",
"tidy-starfishes-allow",
@ -236,6 +253,7 @@
"two-falcons-buy",
"unlucky-boxes-obey",
"unlucky-trees-lick",
"violet-pigs-jam",
"wet-games-fly",
"wicked-clouds-exercise",
"wicked-doors-train",

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: only update lazy properties that have actually changed

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: handle sole empty expression tags

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: improve indexed each array reconcilation

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: set `open` binding value in `<details>`

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: correctly determine binding scope of `let:` directives

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: run `onDestroy` callbacks during SSR

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: add compiler error for each block mutations in runes mode

@ -220,4 +220,4 @@ This is just the beginning though. We have a long list of ideas for subsequent r
You can't use Svelte 5 in production yet. We're in the thick of it at the moment and can't tell you when it'll be ready to use in your apps.
But we didn't want to leave you hanging. We've created a [preview site](https://svelte-5-preview.vercel.app) with detailed explanations of the new features and an interactive playground. You can also visit the `#svelte-5-runes` channel of the [Svelte Discord](/chat) to learn more. We'd love to have your feedback!
But we didn't want to leave you hanging. We've created a [preview site](https://svelte-5-preview.vercel.app) with detailed explanations of the new features and an interactive playground. You can also visit the `#svelte-5-alpha` channel of the [Svelte Discord](/chat) to learn more. We'd love to have your feedback!

@ -1,5 +1,69 @@
# svelte
## 5.0.0-next.52
### Patch Changes
- fix: use hybrid scoping strategy for consistent specificity increase ([#10443](https://github.com/sveltejs/svelte/pull/10443))
- fix: throw validation error when binding to each argument in runes mode ([#10441](https://github.com/sveltejs/svelte/pull/10441))
- fix: make CSS animation declaration transformation more robust ([#10432](https://github.com/sveltejs/svelte/pull/10432))
- fix: handle sole empty expression tags ([#10433](https://github.com/sveltejs/svelte/pull/10433))
## 5.0.0-next.51
### Patch Changes
- fix: align `beforeUpdate`/`afterUpdate` behavior better with that in Svelte 4 ([#10408](https://github.com/sveltejs/svelte/pull/10408))
- fix: disallow exporting props, derived and reassigned state from within components ([#10430](https://github.com/sveltejs/svelte/pull/10430))
- fix: improve indexed each array reconcilation ([#10422](https://github.com/sveltejs/svelte/pull/10422))
- fix: add compiler error for each block mutations in runes mode ([#10428](https://github.com/sveltejs/svelte/pull/10428))
## 5.0.0-next.50
### Patch Changes
- fix: set `open` binding value in `<details>` ([#10413](https://github.com/sveltejs/svelte/pull/10413))
## 5.0.0-next.49
### Patch Changes
- fix: properly analyze group expressions ([#10410](https://github.com/sveltejs/svelte/pull/10410))
- fix: handle nested script tags ([#10416](https://github.com/sveltejs/svelte/pull/10416))
- fix: only update lazy properties that have actually changed ([#10415](https://github.com/sveltejs/svelte/pull/10415))
- fix: correctly determine binding scope of `let:` directives ([#10395](https://github.com/sveltejs/svelte/pull/10395))
- fix: run `onDestroy` callbacks during SSR ([#10297](https://github.com/sveltejs/svelte/pull/10297))
## 5.0.0-next.48
### Patch Changes
- chore: bump zimmerframe to fix bugs introduced in previous version ([#10405](https://github.com/sveltejs/svelte/pull/10405))
## 5.0.0-next.47
### Patch Changes
- chore: bump zimmerframe to resolve AST-traversal-related bugs ([`b63ab91c7b92ecec6e7e939d6d509fc3008cf048`](https://github.com/sveltejs/svelte/commit/b63ab91c7b92ecec6e7e939d6d509fc3008cf048))
## 5.0.0-next.46
### Patch Changes
- fix: allow `let:` directives on slot elements ([#10391](https://github.com/sveltejs/svelte/pull/10391))
- fix: repair each block length mismatches during hydration ([#10398](https://github.com/sveltejs/svelte/pull/10398))
## 5.0.0-next.45
### Patch Changes

@ -978,6 +978,7 @@ export interface HTMLImgAttributes extends HTMLAttributes<HTMLImageElement> {
alt?: string | undefined | null;
crossorigin?: 'anonymous' | 'use-credentials' | '' | undefined | null;
decoding?: 'async' | 'auto' | 'sync' | undefined | null;
fetchpriority?: 'auto' | 'high' | 'low' | undefined | null;
height?: number | string | undefined | null;
ismap?: boolean | undefined | null;
loading?: 'eager' | 'lazy' | undefined | null;
@ -1098,6 +1099,7 @@ export interface HTMLLinkAttributes extends HTMLAttributes<HTMLLinkElement> {
sizes?: string | undefined | null;
type?: string | undefined | null;
charset?: string | undefined | null;
fetchpriority?: 'auto' | 'high' | 'low' | undefined | null;
}
export interface HTMLMapAttributes extends HTMLAttributes<HTMLMapElement> {
@ -1232,6 +1234,7 @@ export interface HTMLScriptAttributes extends HTMLAttributes<HTMLScriptElement>
charset?: string | undefined | null;
crossorigin?: string | undefined | null;
defer?: boolean | undefined | null;
fetchpriority?: 'auto' | 'high' | 'low' | undefined | null;
integrity?: string | undefined | null;
nomodule?: boolean | undefined | null;
nonce?: string | undefined | null;

@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
"version": "5.0.0-next.45",
"version": "5.0.0-next.52",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
@ -128,7 +128,7 @@
"is-reference": "^3.0.2",
"locate-character": "^3.0.0",
"magic-string": "^0.30.5",
"zimmerframe": "^1.1.0"
"zimmerframe": "^1.1.2"
},
"knip": {
"entry": [

@ -169,8 +169,12 @@ const runes = {
'invalid-legacy-export': () => `Cannot use \`export let\` in runes mode — use $props instead`,
/** @param {string} rune */
'invalid-rune-usage': (rune) => `Cannot use ${rune} rune in non-runes mode`,
'invalid-state-export': () => `Cannot export state if it is reassigned`,
'invalid-derived-export': () => `Cannot export derived state`,
'invalid-state-export': () =>
`Cannot export state if it is reassigned. Either export a function returning the state value or only mutate the state value's properties`,
'invalid-derived-export': () =>
`Cannot export derived state. To expose the current derived value, export a function returning its value`,
'invalid-prop-export': () =>
`Cannot export properties. To expose the current value of a property, export a function returning its value`,
'invalid-props-id': () => `$props() can only be used with an object destructuring pattern`,
'invalid-props-pattern': () =>
`$props() assignment must not contain nested properties or computed keys`,
@ -200,7 +204,11 @@ const runes = {
`${rune} can only be called with ${list(args, 'or')} ${
args.length === 1 && args[0] === 1 ? 'argument' : 'arguments'
}`,
'duplicate-props-rune': () => `Cannot use $props() more than once`
/** @param {string} name */
'invalid-runes-mode-import': (name) => `${name} cannot be used in runes mode`,
'duplicate-props-rune': () => `Cannot use $props() more than once`,
'invalid-each-assignment': () =>
`Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. 'array[i] = value' instead of 'entry = value')`
};
/** @satisfies {Errors} */
@ -273,7 +281,8 @@ const attributes = {
/** @satisfies {Errors} */
const slots = {
'invalid-slot-element-attribute': () => `<slot> can only receive attributes, not directives`,
'invalid-slot-element-attribute': () =>
`<slot> can only receive attributes and (optionally) let directives`,
'invalid-slot-attribute': () => `slot attribute must be a static value`,
/** @param {boolean} is_default */
'invalid-slot-name': (is_default) =>

@ -13,9 +13,9 @@ import { error } from '../../../errors.js';
/**
* @param {import('../index.js').Parser} parser
* @returns {any}
* @returns {import('estree').Pattern}
*/
export default function read_context(parser) {
export default function read_pattern(parser) {
const start = parser.index;
let i = parser.index;

@ -65,6 +65,7 @@ export default function tag(parser) {
const data = parser.read_until(regex_closing_comment);
parser.eat('-->', true);
/** @type {ReturnType<typeof parser.append<import('#compiler').Comment>>} */
parser.append({
type: 'Comment',
start,

@ -1,9 +1,8 @@
import read_context from '../read/context.js';
import read_pattern from '../read/context.js';
import read_expression from '../read/expression.js';
import { error } from '../../../errors.js';
import { create_fragment } from '../utils/create.js';
import { walk } from 'zimmerframe';
import { parse } from '../acorn.js';
const regex_whitespace_with_closing_curly_brace = /^\s*}/;
@ -24,18 +23,17 @@ export default function mustache(parser) {
parser.allow_whitespace();
parser.eat('}', true);
parser.append(
/** @type {import('#compiler').ExpressionTag} */ ({
type: 'ExpressionTag',
start,
end: parser.index,
expression,
metadata: {
contains_call_expression: false,
dynamic: false
}
})
);
/** @type {ReturnType<typeof parser.append<import('#compiler').ExpressionTag>>} */
parser.append({
type: 'ExpressionTag',
start,
end: parser.index,
expression,
metadata: {
contains_call_expression: false,
dynamic: false
}
});
}
/** @param {import('../index.js').Parser} parser */
@ -45,17 +43,16 @@ function open(parser) {
if (parser.eat('if')) {
parser.require_whitespace();
const block = parser.append(
/** @type {import('#compiler').IfBlock} */ ({
type: 'IfBlock',
elseif: false,
start,
end: -1,
test: read_expression(parser),
consequent: create_fragment(),
alternate: null
})
);
/** @type {ReturnType<typeof parser.append<import('#compiler').IfBlock>>} */
const block = parser.append({
type: 'IfBlock',
elseif: false,
start,
end: -1,
test: read_expression(parser),
consequent: create_fragment(),
alternate: null
});
parser.allow_whitespace();
parser.eat('}', true);
@ -139,7 +136,7 @@ function open(parser) {
parser.eat('as', true);
parser.require_whitespace();
const context = read_context(parser);
const context = read_pattern(parser);
parser.allow_whitespace();
@ -167,19 +164,18 @@ function open(parser) {
parser.eat('}', true);
const block = parser.append(
/** @type {Omit<import('#compiler').EachBlock, 'parent'>} */ ({
type: 'EachBlock',
start,
end: -1,
expression,
body: create_fragment(),
context,
index,
key,
metadata: /** @type {any} */ (null) // filled in later
})
);
/** @type {ReturnType<typeof parser.append<import('#compiler').EachBlock>>} */
const block = parser.append({
type: 'EachBlock',
start,
end: -1,
expression,
body: create_fragment(),
context,
index,
key,
metadata: /** @type {any} */ (null) // filled in later
});
parser.stack.push(block);
parser.fragments.push(block.body);
@ -192,26 +188,25 @@ function open(parser) {
const expression = read_expression(parser);
parser.allow_whitespace();
const block = parser.append(
/** @type {import('#compiler').AwaitBlock} */ ({
type: 'AwaitBlock',
start,
end: -1,
expression,
value: null,
error: null,
pending: null,
then: null,
catch: null
})
);
/** @type {ReturnType<typeof parser.append<import('#compiler').AwaitBlock>>} */
const block = parser.append({
type: 'AwaitBlock',
start,
end: -1,
expression,
value: null,
error: null,
pending: null,
then: null,
catch: null
});
if (parser.eat('then')) {
if (parser.match_regex(regex_whitespace_with_closing_curly_brace)) {
parser.allow_whitespace();
} else {
parser.require_whitespace();
block.value = read_context(parser);
block.value = read_pattern(parser);
parser.allow_whitespace();
}
@ -222,7 +217,7 @@ function open(parser) {
parser.allow_whitespace();
} else {
parser.require_whitespace();
block.error = read_context(parser);
block.error = read_pattern(parser);
parser.allow_whitespace();
}
@ -247,15 +242,14 @@ function open(parser) {
parser.eat('}', true);
const block = parser.append(
/** @type {import('#compiler').KeyBlock} */ ({
type: 'KeyBlock',
start,
end: -1,
expression,
fragment: create_fragment()
})
);
/** @type {ReturnType<typeof parser.append<import('#compiler').KeyBlock>>} */
const block = parser.append({
type: 'KeyBlock',
start,
end: -1,
expression,
fragment: create_fragment()
});
parser.stack.push(block);
parser.fragments.push(block.fragment);
@ -270,6 +264,10 @@ function open(parser) {
const name = parser.read_identifier();
const name_end = parser.index;
if (name === null) {
error(parser.index, 'expected-identifier');
}
parser.eat('(', true);
parser.allow_whitespace();
@ -278,7 +276,7 @@ function open(parser) {
const parameters = [];
while (!parser.match(')')) {
let pattern = read_context(parser);
let pattern = read_pattern(parser);
parser.allow_whitespace();
if (parser.eat('=')) {
@ -301,22 +299,20 @@ function open(parser) {
parser.allow_whitespace();
parser.eat('}', true);
const block = parser.append(
/** @type {Omit<import('#compiler').SnippetBlock, 'parent'>} */
({
type: 'SnippetBlock',
start,
end: -1,
expression: {
type: 'Identifier',
start: name_start,
end: name_end,
name
},
parameters,
body: create_fragment()
})
);
/** @type {ReturnType<typeof parser.append<import('#compiler').SnippetBlock>>} */
const block = parser.append({
type: 'SnippetBlock',
start,
end: -1,
expression: {
type: 'Identifier',
start: name_start,
end: name_end,
name
},
parameters,
body: create_fragment()
});
parser.stack.push(block);
parser.fragments.push(block.body);
@ -353,17 +349,16 @@ function next(parser) {
parser.allow_whitespace();
parser.eat('}', true);
const child = parser.append(
/** @type {import('#compiler').IfBlock} */ ({
start: parser.index,
end: -1,
type: 'IfBlock',
elseif: true,
test: expression,
consequent: create_fragment(),
alternate: null
})
);
/** @type {ReturnType<typeof parser.append<import('#compiler').IfBlock>>} */
const child = parser.append({
start: parser.index,
end: -1,
type: 'IfBlock',
elseif: true,
test: expression,
consequent: create_fragment(),
alternate: null
});
parser.stack.push(child);
parser.fragments.pop();
@ -399,7 +394,7 @@ function next(parser) {
if (!parser.eat('}')) {
parser.require_whitespace();
block.value = read_context(parser);
block.value = read_pattern(parser);
parser.allow_whitespace();
parser.eat('}', true);
}
@ -418,7 +413,7 @@ function next(parser) {
if (!parser.eat('}')) {
parser.require_whitespace();
block.error = read_context(parser);
block.error = read_pattern(parser);
parser.allow_whitespace();
parser.eat('}', true);
}
@ -498,14 +493,13 @@ function special(parser) {
parser.allow_whitespace();
parser.eat('}', true);
parser.append(
/** @type {import('#compiler').HtmlTag} */ ({
type: 'HtmlTag',
start,
end: parser.index,
expression
})
);
/** @type {ReturnType<typeof parser.append<import('#compiler').HtmlTag>>} */
parser.append({
type: 'HtmlTag',
start,
end: parser.index,
expression
});
return;
}
@ -537,65 +531,44 @@ function special(parser) {
parser.eat('}', true);
}
parser.append(
/** @type {import('#compiler').DebugTag} */ ({
type: 'DebugTag',
start,
end: parser.index,
identifiers
})
);
/** @type {ReturnType<typeof parser.append<import('#compiler').DebugTag>>} */
parser.append({
type: 'DebugTag',
start,
end: parser.index,
identifiers
});
return;
}
if (parser.eat('const')) {
// {@const a = b}
const start_index = parser.index - 5;
parser.require_whitespace();
parser.allow_whitespace();
let end_index = parser.index;
/** @type {import('estree').VariableDeclaration | undefined} */
let declaration = undefined;
const id = read_pattern(parser);
parser.allow_whitespace();
// Can't use parse_expression_at here, so we try to parse until we find the correct range
const dummy_spaces = parser.template.substring(0, start_index).replace(/[^\n]/g, ' ');
while (true) {
end_index = parser.template.indexOf('}', end_index + 1);
if (end_index === -1) break;
try {
const node = parse(
dummy_spaces + parser.template.substring(start_index, end_index),
parser.ts
).body[0];
if (node?.type === 'VariableDeclaration') {
declaration = node;
break;
}
} catch (e) {
continue;
}
}
parser.eat('=', true);
parser.allow_whitespace();
if (
declaration === undefined ||
declaration.declarations.length !== 1 ||
declaration.declarations[0].init === undefined
) {
error(start, 'invalid-const');
}
const init = read_expression(parser);
parser.allow_whitespace();
parser.index = end_index;
parser.eat('}', true);
parser.append(
/** @type {import('#compiler').ConstTag} */ ({
type: 'ConstTag',
start,
end: parser.index,
declaration
})
);
/** @type {ReturnType<typeof parser.append<import('#compiler').ConstTag>>} */
parser.append({
type: 'ConstTag',
start,
end: parser.index,
declaration: {
type: 'VariableDeclaration',
kind: 'const',
declarations: [{ type: 'VariableDeclarator', id, init }],
start: start + 1,
end: parser.index - 1
}
});
}
if (parser.eat('render')) {
@ -611,14 +584,13 @@ function special(parser) {
parser.allow_whitespace();
parser.eat('}', true);
parser.append(
/** @type {import('#compiler').RenderTag} */ ({
type: 'RenderTag',
start,
end: parser.index,
expression: expression.callee,
arguments: expression.arguments
})
);
/** @type {ReturnType<typeof parser.append<import('#compiler').RenderTag>>} */
parser.append({
type: 'RenderTag',
start,
end: parser.index,
expression: expression.callee,
arguments: expression.arguments
});
}
}

@ -10,13 +10,12 @@ export default function text(parser) {
data += parser.template[parser.index++];
}
parser.append(
/** @type {import('#compiler').Text} */ ({
type: 'Text',
start,
end: parser.index,
raw: data,
data: decode_character_references(data, false)
})
);
/** @type {ReturnType<typeof parser.append<import('#compiler').Text>>} */
parser.append({
type: 'Text',
start,
end: parser.index,
raw: data,
data: decode_character_references(data, false)
});
}

@ -70,14 +70,9 @@ export default class Selector {
/**
* @param {import('magic-string').default} code
* @param {string} attr
* @param {number} max_amount_class_specificity_increased
* @param {string} modifier
*/
transform(code, attr, max_amount_class_specificity_increased) {
const amount_class_specificity_to_increase =
max_amount_class_specificity_increased -
this.blocks.filter((block) => block.should_encapsulate).length;
transform(code, modifier) {
/** @param {import('#compiler').Css.SimpleSelector} selector */
function remove_global_pseudo_class(selector) {
code
@ -87,43 +82,50 @@ export default class Selector {
/**
* @param {Block} block
* @param {string} attr
* @param {string} modifier
*/
function encapsulate_block(block, attr) {
function encapsulate_block(block, modifier) {
for (const selector of block.selectors) {
if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
remove_global_pseudo_class(selector);
}
}
let i = block.selectors.length;
while (i--) {
const selector = block.selectors[i];
if (selector.type === 'PseudoElementSelector' || selector.type === 'PseudoClassSelector') {
if (selector.name !== 'root' && selector.name !== 'host') {
if (i === 0) code.prependRight(selector.start, attr);
if (i === 0) code.prependRight(selector.start, modifier);
}
continue;
}
if (selector.type === 'TypeSelector' && selector.name === '*') {
code.update(selector.start, selector.end, attr);
code.update(selector.start, selector.end, modifier);
} else {
code.appendLeft(selector.end, attr);
code.appendLeft(selector.end, modifier);
}
break;
}
}
this.blocks.forEach((block, index) => {
let first = true;
for (const block of this.blocks) {
if (block.global) {
remove_global_pseudo_class(block.selectors[0]);
}
if (block.should_encapsulate)
encapsulate_block(
block,
index === this.blocks.length - 1
? attr.repeat(amount_class_specificity_to_increase + 1)
: attr
);
});
if (block.should_encapsulate) {
// for the first occurrence, we use a classname selector, so that every
// encapsulated selector gets a +0-1-0 specificity bump. thereafter,
// we use a `:where` selector, which does not affect specificity
encapsulate_block(block, first ? modifier : `:where(${modifier})`);
first = false;
}
}
}
/** @param {import('../../types.js').ComponentAnalysis} analysis */
@ -200,10 +202,6 @@ export default class Selector {
}
}
}
get_amount_class_specificity_increased() {
return this.blocks.filter((block) => block.should_encapsulate).length;
}
}
/**

@ -8,7 +8,7 @@ import { push_array } from '../utils/push_array.js';
import { create_attribute } from '../../nodes.js';
const regex_css_browser_prefix = /^-((webkit)|(moz)|(o)|(ms))-/;
const regex_name_boundary = /^[\s,;}]$/;
/**
* @param {string} name
* @returns {string}
@ -97,17 +97,14 @@ class Rule {
* @param {import('magic-string').default} code
* @param {string} id
* @param {Map<string, string>} keyframes
* @param {number} max_amount_class_specificity_increased
*/
transform(code, id, keyframes, max_amount_class_specificity_increased) {
transform(code, id, keyframes) {
if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node)) {
return;
}
const attr = `.${id}`;
this.selectors.forEach((selector) =>
selector.transform(code, attr, max_amount_class_specificity_increased)
);
const modifier = `.${id}`;
this.selectors.forEach((selector) => selector.transform(code, modifier));
this.declarations.forEach((declaration) => declaration.transform(code, keyframes));
}
@ -125,13 +122,6 @@ class Rule {
});
}
/** @returns number */
get_max_amount_class_specificity_increased() {
return Math.max(
...this.selectors.map((selector) => selector.get_amount_class_specificity_increased())
);
}
/**
* @param {MagicString} code
* @param {boolean} dev
@ -213,20 +203,29 @@ class Declaration {
transform(code, keyframes) {
const property = this.node.property && remove_css_prefix(this.node.property.toLowerCase());
if (property === 'animation' || property === 'animation-name') {
const name = /** @type {string} */ (this.node.value)
.split(' ')
.find((name) => keyframes.has(name));
if (name) {
const start = code.original.indexOf(
name,
code.original.indexOf(this.node.property, this.node.start)
);
let index = this.node.start + this.node.property.length + 1;
let name = '';
const end = start + name.length;
while (index < code.original.length) {
const character = code.original[index];
const keyframe = /** @type {string} */ (keyframes.get(name));
code.update(start, end, keyframe);
if (regex_name_boundary.test(character)) {
const keyframe = keyframes.get(name);
if (keyframe) {
code.update(index - name.length, index, keyframe);
}
if (character === ';' || character === '}') {
break;
}
name = '';
} else {
name += character;
}
index++;
}
}
}
@ -278,9 +277,8 @@ class Atrule {
* @param {import('magic-string').default} code
* @param {string} id
* @param {Map<string, string>} keyframes
* @param {number} max_amount_class_specificity_increased
*/
transform(code, id, keyframes, max_amount_class_specificity_increased) {
transform(code, id, keyframes) {
if (is_keyframes_node(this.node)) {
let start = this.node.start + this.node.name.length + 1;
while (code.original[start] === ' ') start += 1;
@ -300,7 +298,7 @@ class Atrule {
}
}
this.children.forEach((child) => {
child.transform(code, id, keyframes, max_amount_class_specificity_increased);
child.transform(code, id, keyframes);
});
}
@ -319,13 +317,6 @@ class Atrule {
});
}
/** @returns {number} */
get_max_amount_class_specificity_increased() {
return Math.max(
...this.children.map((rule) => rule.get_max_amount_class_specificity_increased())
);
}
/**
* @param {MagicString} code
* @param {boolean} dev
@ -508,12 +499,8 @@ export default class Stylesheet {
}
});
const max = Math.max(
...this.children.map((rule) => rule.get_max_amount_class_specificity_increased())
);
for (const child of this.children) {
child.transform(code, this.id, this.keyframes, max);
child.transform(code, this.id, this.keyframes);
}
code.remove(0, this.ast.content.start);

@ -1014,7 +1014,7 @@ const common_visitors = {
// entries as keys.
i = context.path.length;
const each_blocks = [];
const expression_ids = extract_all_identifiers_from_expression(node.expression);
const [keypath, expression_ids] = extract_all_identifiers_from_expression(node.expression);
let ids = expression_ids;
while (i--) {
const parent = context.path[i];
@ -1027,7 +1027,7 @@ const common_visitors = {
}
each_blocks.push(parent);
ids = ids.filter((id) => !references.includes(id));
ids.push(...extract_all_identifiers_from_expression(parent.expression));
ids.push(...extract_all_identifiers_from_expression(parent.expression)[1]);
}
}
}
@ -1038,8 +1038,8 @@ const common_visitors = {
// but this is a limitation of the current static analysis we do; it also never worked in Svelte 4)
const bindings = expression_ids.map((id) => context.state.scope.get(id.name));
let group_name;
outer: for (const [b, group] of context.state.analysis.binding_groups) {
if (b.length !== bindings.length) continue;
outer: for (const [[key, b], group] of context.state.analysis.binding_groups) {
if (b.length !== bindings.length || key !== keypath) continue;
for (let i = 0; i < bindings.length; i++) {
if (bindings[i] !== b[i]) continue outer;
}
@ -1048,7 +1048,7 @@ const common_visitors = {
if (!group_name) {
group_name = context.state.scope.root.unique('binding_group');
context.state.analysis.binding_groups.set(bindings, group_name);
context.state.analysis.binding_groups.set([keypath, bindings], group_name);
}
node.metadata = {

@ -1,5 +1,5 @@
import { error } from '../../errors.js';
import { extract_identifiers, get_parent, is_text_attribute } from '../../utils/ast.js';
import { extract_identifiers, get_parent, is_text_attribute, object } from '../../utils/ast.js';
import { warn } from '../../warnings.js';
import fuzzymatch from '../1-parse/utils/fuzzymatch.js';
import { disallowed_parapgraph_contents, interactive_elements } from '../1-parse/utils/html.js';
@ -37,6 +37,10 @@ function validate_component(node, context) {
) {
error(attribute, 'invalid-event-modifier');
}
if (attribute.type === 'Attribute' && attribute.name === 'slot') {
validate_slot_attribute(context, attribute);
}
}
context.next({
@ -330,17 +334,16 @@ function is_tag_valid_with_parent(tag, parent_tag) {
* @type {import('zimmerframe').Visitors<import('#compiler').SvelteNode, import('./types.js').AnalysisState>}
*/
const validation = {
AssignmentExpression(node, context) {
validate_assignment(node, node.left, context.state);
},
BindDirective(node, context) {
validate_no_const_assignment(node, node.expression, context.state.scope, true);
const assignee = node.expression;
let left = assignee;
while (left.type === 'MemberExpression') {
left = /** @type {import('estree').MemberExpression} */ (left.object);
}
const left = object(assignee);
if (left.type !== 'Identifier') {
if (left === null) {
error(node, 'invalid-binding-expression');
}
@ -366,6 +369,10 @@ const validation = {
error(node.expression, 'invalid-derived-binding');
}
if (context.state.analysis.runes && binding.kind === 'each') {
error(node, 'invalid-each-assignment');
}
// TODO handle mutations of non-state/props in runes mode
}
@ -488,12 +495,27 @@ const validation = {
error(node, 'invalid-const-placement');
}
},
ImportDeclaration(node, context) {
if (node.source.value === 'svelte' && context.state.analysis.runes) {
for (const specifier of node.specifiers) {
if (specifier.type === 'ImportSpecifier') {
if (
specifier.imported.name === 'beforeUpdate' ||
specifier.imported.name === 'afterUpdate'
) {
error(specifier, 'invalid-runes-mode-import', specifier.imported.name);
}
}
}
}
},
LetDirective(node, context) {
const parent = context.path.at(-1);
if (
parent === undefined ||
(parent.type !== 'Component' &&
parent.type !== 'RegularElement' &&
parent.type !== 'SlotElement' &&
parent.type !== 'SvelteElement' &&
parent.type !== 'SvelteComponent' &&
parent.type !== 'SvelteSelf' &&
@ -609,7 +631,7 @@ const validation = {
error(attribute, 'invalid-slot-name', true);
}
}
} else if (attribute.type !== 'SpreadAttribute') {
} else if (attribute.type !== 'SpreadAttribute' && attribute.type !== 'LetDirective') {
error(attribute, 'invalid-slot-element-attribute');
}
}
@ -636,6 +658,9 @@ const validation = {
error(child, 'invalid-title-content');
}
},
UpdateExpression(node, context) {
validate_assignment(node, node.argument, context.state);
},
ExpressionTag(node, context) {
if (!node.parent) return;
if (context.state.parent_element) {
@ -691,6 +716,10 @@ function validate_export(node, scope, name) {
const binding = scope.get(name);
if (!binding) return;
if (binding.kind === 'prop') {
error(node, 'invalid-prop-export');
}
if (binding.kind === 'derived') {
error(node, 'invalid-derived-export');
}
@ -885,24 +914,28 @@ function validate_no_const_assignment(node, argument, scope, is_binding) {
function validate_assignment(node, argument, state) {
validate_no_const_assignment(node, argument, state.scope, false);
let left = /** @type {import('estree').Expression | import('estree').Super} */ (argument);
if (left.type === 'Identifier') {
const binding = state.scope.get(left.name);
if (state.analysis.runes && argument.type === 'Identifier') {
const binding = state.scope.get(argument.name);
if (binding?.kind === 'derived') {
error(node, 'invalid-derived-assignment');
}
if (binding?.kind === 'each') {
error(node, 'invalid-each-assignment');
}
}
let object = /** @type {import('estree').Expression | import('estree').Super} */ (argument);
/** @type {import('estree').Expression | import('estree').PrivateIdentifier | null} */
let property = null;
while (left.type === 'MemberExpression') {
property = left.property;
left = left.object;
while (object.type === 'MemberExpression') {
property = object.property;
object = object.object;
}
if (left.type === 'ThisExpression' && property?.type === 'PrivateIdentifier') {
if (object.type === 'ThisExpression' && property?.type === 'PrivateIdentifier') {
if (state.private_derived_state.includes(property.name)) {
error(node, 'invalid-derived-assignment');
}
@ -910,22 +943,24 @@ function validate_assignment(node, argument, state) {
}
export const validation_runes = merge(validation, a11y_validators, {
AssignmentExpression(node, { state, path }) {
const parent = path.at(-1);
if (parent && parent.type === 'ConstTag') return;
validate_assignment(node, node.left, state);
},
UpdateExpression(node, { state }) {
validate_assignment(node, node.argument, state);
},
LabeledStatement(node, { path }) {
if (node.label.name !== '$' || path.at(-1)?.type !== 'Program') return;
error(node, 'invalid-legacy-reactive-statement');
},
ExportNamedDeclaration(node, { state }) {
ExportNamedDeclaration(node, { state, next }) {
if (node.declaration?.type !== 'VariableDeclaration') return;
if (node.declaration.kind !== 'let') return;
// visit children, so bindings are correctly initialised
next();
for (const declarator of node.declaration.declarations) {
for (const id of extract_identifiers(declarator.id)) {
validate_export(node, state.scope, id.name);
}
}
if (state.analysis.instance.scope !== state.scope) return;
if (node.declaration.kind !== 'let') return;
error(node, 'invalid-legacy-export');
},
ExportSpecifier(node, { state }) {

@ -84,7 +84,10 @@ export function client_component(source, analysis, options) {
},
legacy_reactive_statements: new Map(),
metadata: {
template_needs_import_node: false,
context: {
template_needs_import_node: false,
template_contains_script_tag: false
},
namespace: options.namespace,
bound_contenteditable: false
},
@ -262,6 +265,7 @@ export function client_component(source, analysis, options) {
...legacy_reactive_declarations,
...group_binding_declarations,
.../** @type {import('estree').Statement[]} */ (instance.body),
analysis.runes ? b.empty : b.stmt(b.call('$.init')),
.../** @type {import('estree').Statement[]} */ (template.body),
...static_bindings
]);

@ -47,9 +47,22 @@ export interface ComponentClientTransformState extends ClientTransformState {
readonly template: string[];
readonly metadata: {
namespace: Namespace;
/** `true` if the HTML template needs to be instantiated with `importNode` */
template_needs_import_node: boolean;
bound_contenteditable: boolean;
/**
* Stuff that is set within the children of one `create_block` that is relevant
* to said `create_block`. Shouldn't be destructured or otherwise spread unless
* inside `create_block` to keep the object reference intact (it's also nested
* within `metadata` for this reason).
*/
context: {
/** `true` if the HTML template needs to be instantiated with `importNode` */
template_needs_import_node: boolean;
/**
* `true` if HTML template contains a `<script>` tag. In this case we need to invoke a special
* template instantiation function (see `create_fragment_with_script_from_html` for more info)
*/
template_contains_script_tag: boolean;
};
};
readonly preserve_whitespace: boolean;

@ -749,7 +749,7 @@ function serialize_inline_component(node, component_name, context) {
const props_and_spreads = [];
/** @type {import('estree').ExpressionStatement[]} */
const default_lets = [];
const lets = [];
/** @type {Record<string, import('#compiler').TemplateNode[]>} */
const children = {};
@ -763,6 +763,12 @@ function serialize_inline_component(node, component_name, context) {
/** @type {import('estree').Identifier | import('estree').MemberExpression | null} */
let bind_this = null;
/**
* If this component has a slot property, it is a named slot within another component. In this case
* the slot scope applies to the component itself, too, and not just its children.
*/
let slot_scope_applies_to_itself = false;
/**
* @param {import('estree').Property} prop
*/
@ -775,12 +781,9 @@ function serialize_inline_component(node, component_name, context) {
props_and_spreads.push(props);
}
}
for (const attribute of node.attributes) {
if (attribute.type === 'LetDirective') {
default_lets.push(
/** @type {import('estree').ExpressionStatement} */ (context.visit(attribute))
);
lets.push(/** @type {import('estree').ExpressionStatement} */ (context.visit(attribute)));
} else if (attribute.type === 'OnDirective') {
events[attribute.name] ||= [];
let handler = serialize_event_handler(attribute, context);
@ -811,6 +814,10 @@ function serialize_inline_component(node, component_name, context) {
continue;
}
if (attribute.name === 'slot') {
slot_scope_applies_to_itself = true;
}
const [, value] = serialize_attribute_value(attribute.value, context);
if (attribute.metadata.dynamic) {
@ -861,6 +868,10 @@ function serialize_inline_component(node, component_name, context) {
}
}
if (slot_scope_applies_to_itself) {
context.state.init.push(...lets);
}
if (Object.keys(events).length > 0) {
const events_expression = b.object(
Object.keys(events).map((name) =>
@ -916,7 +927,7 @@ function serialize_inline_component(node, component_name, context) {
const slot_fn = b.arrow(
[b.id('$$anchor'), b.id('$$slotProps')],
b.block([...(slot_name === 'default' ? default_lets : []), ...body])
b.block([...(slot_name === 'default' && !slot_scope_applies_to_itself ? lets : []), ...body])
);
if (slot_name === 'default') {
@ -1052,7 +1063,10 @@ function create_block(parent, name, nodes, context) {
after_update: [],
template: [],
metadata: {
template_needs_import_node: false,
context: {
template_needs_import_node: false,
template_contains_script_tag: false
},
namespace,
bound_contenteditable: context.state.metadata.bound_contenteditable
}
@ -1072,10 +1086,14 @@ function create_block(parent, name, nodes, context) {
node: id
});
const callee = namespace === 'svg' ? '$.svg_template' : '$.template';
context.state.hoisted.push(
b.var(template_name, b.call(callee, b.template([b.quasi(state.template.join(''), true)], [])))
b.var(
template_name,
b.call(
get_template_function(namespace, state),
b.template([b.quasi(state.template.join(''), true)], [])
)
)
);
body.push(
@ -1084,7 +1102,7 @@ function create_block(parent, name, nodes, context) {
b.call(
'$.open',
b.id('$$anchor'),
b.literal(!state.metadata.template_needs_import_node),
b.literal(!state.metadata.context.template_needs_import_node),
template_name
)
),
@ -1125,12 +1143,14 @@ function create_block(parent, name, nodes, context) {
// special case — we can use `$.comment` instead of creating a unique template
body.push(b.var(id, b.call('$.comment', b.id('$$anchor'))));
} else {
const callee = namespace === 'svg' ? '$.svg_template' : '$.template';
state.hoisted.push(
b.var(
template_name,
b.call(callee, b.template([b.quasi(state.template.join(''), true)], []), b.true)
b.call(
get_template_function(namespace, state),
b.template([b.quasi(state.template.join(''), true)], []),
b.true
)
)
);
@ -1140,7 +1160,7 @@ function create_block(parent, name, nodes, context) {
b.call(
'$.open_frag',
b.id('$$anchor'),
b.literal(!state.metadata.template_needs_import_node),
b.literal(!state.metadata.context.template_needs_import_node),
template_name
)
)
@ -1204,6 +1224,23 @@ function create_block(parent, name, nodes, context) {
return body;
}
/**
*
* @param {import('#compiler').Namespace} namespace
* @param {import('../types.js').ComponentClientTransformState} state
* @returns
*/
function get_template_function(namespace, state) {
const contains_script_tag = state.metadata.context.template_contains_script_tag;
return namespace === 'svg'
? contains_script_tag
? '$.svg_template_with_script'
: '$.svg_template'
: contains_script_tag
? '$.template_with_script'
: '$.template';
}
/**
*
* @param {import('../types.js').ComponentClientTransformState} state
@ -1834,6 +1871,9 @@ export const template_visitors = {
context.state.template.push('<!>');
return;
}
if (node.name === 'script') {
context.state.metadata.context.template_contains_script_tag = true;
}
const metadata = context.state.metadata;
const child_metadata = {
@ -1872,7 +1912,7 @@ export const template_visitors = {
// custom element until the template is connected to the dom, which would
// cause problems when setting properties on the custom element.
// Therefore we need to use importNode instead, which doesn't have this caveat.
metadata.template_needs_import_node = true;
metadata.context.template_needs_import_node = true;
}
for (const attribute of node.attributes) {
@ -2913,6 +2953,9 @@ export const template_visitors = {
/** @type {import('estree').Expression[]} */
const spreads = [];
/** @type {import('estree').ExpressionStatement[]} */
const lets = [];
let is_default = true;
/** @type {import('estree').Expression} */
@ -2928,16 +2971,21 @@ export const template_visitors = {
if (attribute.name === 'name') {
name = value;
is_default = false;
} else {
} else if (attribute.name !== 'slot') {
if (attribute.metadata.dynamic) {
props.push(b.get(attribute.name, [b.return(value)]));
} else {
props.push(b.init(attribute.name, value));
}
}
} else if (attribute.type === 'LetDirective') {
lets.push(/** @type {import('estree').ExpressionStatement} */ (context.visit(attribute)));
}
}
// Let bindings first, they can be used on attributes
context.state.init.push(...lets);
const props_expression =
spreads.length === 0
? b.object(props)

@ -808,11 +808,17 @@ function serialize_inline_component(node, component_name, context) {
const custom_css_props = [];
/** @type {import('estree').ExpressionStatement[]} */
const default_lets = [];
const lets = [];
/** @type {Record<string, import('#compiler').TemplateNode[]>} */
const children = {};
/**
* If this component has a slot property, it is a named slot within another component. In this case
* the slot scope applies to the component itself, too, and not just its children.
*/
let slot_scope_applies_to_itself = false;
/**
* @param {import('estree').Property} prop
*/
@ -825,12 +831,9 @@ function serialize_inline_component(node, component_name, context) {
props_and_spreads.push(props);
}
}
for (const attribute of node.attributes) {
if (attribute.type === 'LetDirective') {
default_lets.push(
/** @type {import('estree').ExpressionStatement} */ (context.visit(attribute))
);
lets.push(/** @type {import('estree').ExpressionStatement} */ (context.visit(attribute)));
} else if (attribute.type === 'SpreadAttribute') {
props_and_spreads.push(/** @type {import('estree').Expression} */ (context.visit(attribute)));
} else if (attribute.type === 'Attribute') {
@ -840,6 +843,10 @@ function serialize_inline_component(node, component_name, context) {
continue;
}
if (attribute.name === 'slot') {
slot_scope_applies_to_itself = true;
}
const value = serialize_attribute_value(attribute.value, context, false, true);
push_prop(b.prop('init', b.key(attribute.name), value));
} else if (attribute.type === 'BindDirective') {
@ -862,6 +869,10 @@ function serialize_inline_component(node, component_name, context) {
}
}
if (slot_scope_applies_to_itself) {
context.state.init.push(...lets);
}
/** @type {import('estree').Statement[]} */
const snippet_declarations = [];
@ -907,7 +918,7 @@ function serialize_inline_component(node, component_name, context) {
const slot_fn = b.arrow(
[b.id('$$payload'), b.id('$$slotProps')],
b.block([...(slot_name === 'default' ? default_lets : []), ...body])
b.block([...(slot_name === 'default' && !slot_scope_applies_to_itself ? lets : []), ...body])
);
if (slot_name === 'default') {
@ -1340,12 +1351,16 @@ const template_visitors = {
b.block(each)
);
if (node.fallback) {
const fallback_stmts = create_block(node, node.fallback.nodes, context);
fallback_stmts.unshift(
b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal('<!--ssr:each_else-->')))
);
state.template.push(
t_statement(
b.if(
b.binary('!==', b.member(array_id, b.id('length')), b.literal(0)),
for_loop,
b.block(create_block(node, node.fallback.nodes, context))
b.block(fallback_stmts)
)
)
);
@ -1572,6 +1587,9 @@ const template_visitors = {
/** @type {import('estree').Expression[]} */
const spreads = [];
/** @type {import('estree').ExpressionStatement[]} */
const lets = [];
/** @type {import('estree').Expression} */
let expression = b.member_id('$$props.children');
@ -1582,16 +1600,21 @@ const template_visitors = {
const value = serialize_attribute_value(attribute.value, context);
if (attribute.name === 'name') {
expression = b.member(b.member_id('$$props.$$slots'), value, true, true);
} else {
} else if (attribute.name !== 'slot') {
if (attribute.metadata.dynamic) {
props.push(b.get(attribute.name, [b.return(value)]));
} else {
props.push(b.init(attribute.name, value));
}
}
} else if (attribute.type === 'LetDirective') {
lets.push(/** @type {import('estree').ExpressionStatement} */ (context.visit(attribute)));
}
}
// Let bindings first, they can be used on attributes
context.state.init.push(...lets);
const props_expression =
spreads.length === 0
? b.object(props)

@ -175,6 +175,7 @@ export const binding_properties = {
textContent: {},
open: {
event: 'toggle',
type: 'set',
valid_elements: ['details']
},
value: {

@ -282,7 +282,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
* @type {import('zimmerframe').Visitor<import('#compiler').ElementLike, State, import('#compiler').SvelteNode>}
*/
const SvelteFragment = (node, { state, next }) => {
const scope = analyze_let_directives(node, state.scope);
const [scope] = analyze_let_directives(node, state.scope);
scopes.set(node, scope);
next({ scope });
};
@ -293,36 +293,40 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
*/
function analyze_let_directives(node, parent) {
const scope = parent.child();
let is_default_slot = true;
for (const attribute of node.attributes) {
if (attribute.type !== 'LetDirective') continue;
/** @type {import('#compiler').Binding[]} */
const bindings = [];
scope.declarators.set(attribute, bindings);
if (attribute.type === 'LetDirective') {
/** @type {import('#compiler').Binding[]} */
const bindings = [];
scope.declarators.set(attribute, bindings);
// attach the scope to the directive itself, as well as the
// contents to which it applies
scopes.set(attribute, scope);
// attach the scope to the directive itself, as well as the
// contents to which it applies
scopes.set(attribute, scope);
if (attribute.expression) {
for (const id of extract_identifiers_from_destructuring(attribute.expression)) {
if (attribute.expression) {
for (const id of extract_identifiers_from_destructuring(attribute.expression)) {
const binding = scope.declare(id, 'derived', 'const');
bindings.push(binding);
}
} else {
/** @type {import('estree').Identifier} */
const id = {
name: attribute.name,
type: 'Identifier',
start: attribute.start,
end: attribute.end
};
const binding = scope.declare(id, 'derived', 'const');
bindings.push(binding);
}
} else {
/** @type {import('estree').Identifier} */
const id = {
name: attribute.name,
type: 'Identifier',
start: attribute.start,
end: attribute.end
};
const binding = scope.declare(id, 'derived', 'const');
bindings.push(binding);
} else if (attribute.type === 'Attribute' && attribute.name === 'slot') {
is_default_slot = false;
}
}
return scope;
return /** @type {const} */ ([scope, is_default_slot]);
}
walk(ast, state, {
@ -357,19 +361,24 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
},
SvelteFragment,
SlotElement: SvelteFragment,
SvelteElement: SvelteFragment,
RegularElement: SvelteFragment,
Component(node, { state, visit, path }) {
state.scope.reference(b.id(node.name), path);
// let:x from the default slot is a weird one:
// Its scope only applies to children that are not slots themselves.
for (const attribute of node.attributes) {
visit(attribute);
}
const scope = analyze_let_directives(node, state.scope);
// let:x is super weird:
// - for the default slot, its scope only applies to children that are not slots themselves
// - for named slots, its scope applies to the component itself, too
const [scope, is_default_slot] = analyze_let_directives(node, state.scope);
if (!is_default_slot) {
scopes.set(node, scope);
}
for (const child of node.fragment.nodes) {
if (

@ -1,5 +1,4 @@
import type {
BindDirective,
Binding,
Fragment,
RegularElement,
@ -68,7 +67,7 @@ export interface ComponentAnalysis extends Analysis {
inject_styles: boolean;
reactive_statements: Map<LabeledStatement, ReactiveStatement>;
/** Identifiers that make up the `bind:group` expression -> internal group binding name */
binding_groups: Map<Array<Binding | null>, Identifier>;
binding_groups: Map<[key: string, bindings: Array<Binding | null>], Identifier>;
slot_names: Set<string>;
}

@ -485,5 +485,7 @@ declare module 'estree' {
start?: number;
/** Added by the Svelte parser */
end?: number;
/** Added by acorn-typescript */
typeAnnotation?: any;
}
}

@ -96,13 +96,15 @@ export function extract_identifiers(param, nodes = []) {
}
/**
* Extracts all identifiers from an expression.
* Extracts all identifiers and a stringified keypath from an expression.
* @param {import('estree').Expression} expr
* @returns {import('estree').Identifier[]}
* @returns {[keypath: string, ids: import('estree').Identifier[]]}
*/
export function extract_all_identifiers_from_expression(expr) {
/** @type {import('estree').Identifier[]} */
let nodes = [];
/** @type {string[]} */
let keypath = [];
walk(
expr,
@ -113,11 +115,30 @@ export function extract_all_identifiers_from_expression(expr) {
if (parent?.type !== 'MemberExpression' || parent.property !== node || parent.computed) {
nodes.push(node);
}
if (parent?.type === 'MemberExpression' && parent.computed && parent.property === node) {
keypath.push(`[${node.name}]`);
} else {
keypath.push(node.name);
}
},
Literal(node, { path }) {
const value = typeof node.value === 'string' ? `"${node.value}"` : String(node.value);
const parent = path.at(-1);
if (parent?.type === 'MemberExpression' && parent.computed && parent.property === node) {
keypath.push(`[${value}]`);
} else {
keypath.push(value);
}
},
ThisExpression(_, { next }) {
keypath.push('this');
next();
}
}
);
return nodes;
return [keypath.join('.'), nodes];
}
/**

@ -13,15 +13,11 @@ import {
hydrate_block_anchor,
set_current_hydration_fragment
} from './hydration.js';
import { clear_text_content, map_get, map_set } from './operations.js';
import { STATE_SYMBOL } from './proxy.js';
import { clear_text_content, empty, map_get, map_set } from './operations.js';
import { insert, remove } from './reconciler.js';
import { empty } from './render.js';
import {
destroy_signal,
execute_effect,
is_lazy_property,
lazy_property,
mutable_source,
push_destroy_fn,
render_effect,
@ -64,6 +60,10 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
/** @type {null | import('./types.js').EffectSignal} */
let render = null;
/** Whether or not there was a "rendered fallback but want to render items" (or vice versa) hydration mismatch */
let mismatch = false;
block.r =
/** @param {import('./types.js').Transition} transition */
(transition) => {
@ -128,7 +128,7 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
};
/** @param {import('./types.js').EachBlock} block */
const clear_each = (block) => {
const render_each = (block) => {
const flags = block.f;
const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0;
const anchor_node = block.a;
@ -144,16 +144,34 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
: maybe_array == null
? []
: Array.from(maybe_array);
if (key_fn !== null) {
keys = array.map(key_fn);
} else if ((flags & EACH_KEYED) === 0) {
array.map(no_op);
}
const length = array.length;
if (current_hydration_fragment !== null) {
const is_each_else_comment =
/** @type {Comment} */ (current_hydration_fragment?.[0])?.data === 'ssr:each_else';
// Check for hydration mismatch which can happen if the server renders the each fallback
// but the client has items, or vice versa. If so, remove everything inside the anchor and start fresh.
if ((is_each_else_comment && length) || (!is_each_else_comment && !length)) {
remove(/** @type {import('./types.js').TemplateNode[]} */ (current_hydration_fragment));
set_current_hydration_fragment(null);
mismatch = true;
} else if (is_each_else_comment) {
// Remove the each_else comment node or else it will confuse the subsequent hydration algorithm
/** @type {import('./types.js').TemplateNode[]} */ (current_hydration_fragment).shift();
}
}
if (fallback_fn !== null) {
if (length === 0) {
if (block.v.length !== 0 || render === null) {
clear_each(block);
render_each(block);
create_fallback_effect();
return;
}
@ -170,6 +188,7 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
}
}
}
if (render !== null) {
execute_effect(render);
}
@ -178,7 +197,12 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
false
);
render = render_effect(clear_each, block, true);
render = render_effect(render_each, block, true);
if (mismatch) {
// Set a fragment so that Svelte continues to operate in hydration mode
set_current_hydration_fragment([]);
}
push_destroy_fn(each, () => {
const flags = block.f;
@ -251,14 +275,9 @@ function reconcile_indexed_array(
flags,
apply_transitions
) {
var is_proxied_array = STATE_SYMBOL in array && /** @type {any} */ (array[STATE_SYMBOL]).i;
var a_blocks = each_block.v;
var active_transitions = each_block.s;
if (is_proxied_array) {
flags &= ~EACH_ITEM_REACTIVE;
}
/** @type {number | void} */
var a = a_blocks.length;
@ -287,55 +306,70 @@ function reconcile_indexed_array(
}
} else {
var item;
var is_hydrating = current_hydration_fragment !== null;
b_blocks = Array(b);
if (current_hydration_fragment !== null) {
/** @type {Node} */
var hydrating_node = current_hydration_fragment[0];
if (is_hydrating) {
// Hydrate block
var hydration_list = /** @type {import('./types.js').TemplateNode[]} */ (
current_hydration_fragment
);
var hydrating_node = hydration_list[0];
for (; index < length; index++) {
// Hydrate block
item = is_proxied_array ? lazy_property(array, index) : array[index];
var fragment = /** @type {Array<Text | Comment | Element>} */ (
get_hydration_fragment(hydrating_node)
);
set_current_hydration_fragment(fragment);
hydrating_node = /** @type {Node} */ (
if (!fragment) {
// If fragment is null, then that means that the server rendered less items than what
// the client code specifies -> break out and continue with client-side node creation
break;
}
item = array[index];
block = each_item_block(item, null, index, render_fn, flags);
b_blocks[index] = block;
hydrating_node = /** @type {import('./types.js').TemplateNode} */ (
/** @type {Node} */ (/** @type {Node} */ (fragment.at(-1)).nextSibling).nextSibling
);
}
remove_excess_hydration_nodes(hydration_list, hydrating_node);
}
for (; index < length; index++) {
if (index >= a) {
// Add block
item = array[index];
block = each_item_block(item, null, index, render_fn, flags);
b_blocks[index] = block;
insert_each_item_block(block, dom, is_controlled, null);
} else if (index >= b) {
// Remove block
block = a_blocks[index];
destroy_each_item_block(block, active_transitions, apply_transitions);
} else {
// Update block
item = array[index];
block = a_blocks[index];
b_blocks[index] = block;
update_each_item_block(block, item, index, flags);
}
} else {
for (; index < length; index++) {
if (index >= a) {
// Add block
item = is_proxied_array ? lazy_property(array, index) : array[index];
block = each_item_block(item, null, index, render_fn, flags);
b_blocks[index] = block;
insert_each_item_block(block, dom, is_controlled, null);
} else if (index >= b) {
// Remove block
block = a_blocks[index];
destroy_each_item_block(block, active_transitions, apply_transitions);
} else {
// Update block
item = array[index];
block = a_blocks[index];
b_blocks[index] = block;
update_each_item_block(block, item, index, flags);
}
}
}
if (is_hydrating && current_hydration_fragment === null) {
// Server rendered less nodes than the client -> set empty array so that Svelte continues to operate in hydration mode
set_current_hydration_fragment([]);
}
}
each_block.v = b_blocks;
}
// Reconcile arrays by the equality of the elements in the array. This algorithm
// is based on Ivi's reconcilation logic:
//
// https://github.com/localvoid/ivi/blob/9f1bd0918f487da5b131941228604763c5d8ef56/packages/ivi/src/client/core.ts#L968
//
/**
* Reconcile arrays by the equality of the elements in the array. This algorithm
* is based on Ivi's reconcilation logic:
* https://github.com/localvoid/ivi/blob/9f1bd0918f487da5b131941228604763c5d8ef56/packages/ivi/src/client/core.ts#L968
* @template V
* @param {Array<V>} array
* @param {import('./types.js').EachBlock} each_block
@ -391,30 +425,43 @@ function reconcile_tracked_array(
var key;
var item;
var idx;
var is_hydrating = current_hydration_fragment !== null;
b_blocks = Array(b);
if (current_hydration_fragment !== null) {
if (is_hydrating) {
// Hydrate block
var fragment;
/** @type {Node} */
var hydrating_node = current_hydration_fragment[0];
var hydration_list = /** @type {import('./types.js').TemplateNode[]} */ (
current_hydration_fragment
);
var hydrating_node = hydration_list[0];
while (b > 0) {
// Hydrate block
idx = b_end - --b;
item = array[idx];
key = is_computed_key ? keys[idx] : item;
fragment = /** @type {Array<Text | Comment | Element>} */ (
get_hydration_fragment(hydrating_node)
);
set_current_hydration_fragment(fragment);
if (!fragment) {
// If fragment is null, then that means that the server rendered less items than what
// the client code specifies -> break out and continue with client-side node creation
break;
}
idx = b_end - --b;
item = array[idx];
key = is_computed_key ? keys[idx] : item;
block = each_item_block(item, key, idx, render_fn, flags);
b_blocks[idx] = block;
// Get the <!--ssr:..--> tag of the next item in the list
// The fragment array can be empty if each block has no content
hydrating_node = /** @type {Node} */ (
hydrating_node = /** @type {import('./types.js').TemplateNode} */ (
/** @type {Node} */ ((fragment.at(-1) || hydrating_node).nextSibling).nextSibling
);
block = each_item_block(item, key, idx, render_fn, flags);
b_blocks[idx] = block;
}
} else if (a === 0) {
remove_excess_hydration_nodes(hydration_list, hydrating_node);
}
if (a === 0) {
// Create new blocks
while (b > 0) {
idx = b_end - --b;
@ -546,11 +593,30 @@ function reconcile_tracked_array(
}
}
}
if (is_hydrating && current_hydration_fragment === null) {
// Server rendered less nodes than the client -> set empty array so that Svelte continues to operate in hydration mode
set_current_hydration_fragment([]);
}
}
each_block.v = b_blocks;
}
/**
* The server could have rendered more list items than the client specifies.
* In that case, we need to remove the remaining server-rendered nodes.
* @param {import('./types.js').TemplateNode[]} hydration_list
* @param {import('./types.js').TemplateNode | null} next_node
*/
function remove_excess_hydration_nodes(hydration_list, next_node) {
if (next_node === null) return;
var idx = hydration_list.indexOf(next_node);
if (idx !== -1 && hydration_list.length > idx + 1) {
remove(hydration_list.slice(idx));
}
}
/**
* Longest Increased Subsequence algorithm
* @param {Int32Array} a
@ -711,10 +777,9 @@ export function get_first_element(block) {
* @returns {void}
*/
function update_each_item_block(block, item, index, type) {
const block_v = block.v;
if ((type & EACH_ITEM_REACTIVE) !== 0) {
set_signal_value(block.v, item);
} else if (is_lazy_property(block.v)) {
block.v.o[block.v.p] = item;
set_signal_value(block_v, item);
}
const transitions = block.s;
const index_is_reactive = (type & EACH_INDEX_REACTIVE) !== 0;
@ -771,9 +836,7 @@ export function destroy_each_item_block(
/**
* @template V
* @template O
* @template P
* @param {V | import('./types.js').LazyProperty<O, P>} item
* @param {V} item
* @param {unknown} key
* @param {number} index
* @param {(anchor: null, item: V, index: number | import('./types.js').Signal<number>) => void} render_fn

@ -1,12 +1,13 @@
// Handle hydration
import { empty } from './operations.js';
import { schedule_task } from './runtime.js';
/** @type {null | Array<Text | Comment | Element>} */
/** @type {null | Array<import('./types.js').TemplateNode>} */
export let current_hydration_fragment = null;
/**
* @param {null | Array<Text | Comment | Element>} fragment
* @param {null | Array<import('./types.js').TemplateNode>} fragment
* @returns {void}
*/
export function set_current_hydration_fragment(fragment) {
@ -16,10 +17,11 @@ export function set_current_hydration_fragment(fragment) {
/**
* Returns all nodes between the first `<!--ssr:...-->` comment tag pair encountered.
* @param {Node | null} node
* @returns {Array<Text | Comment | Element> | null}
* @param {boolean} [insert_text] Whether to insert an empty text node if the fragment is empty
* @returns {Array<import('./types.js').TemplateNode> | null}
*/
export function get_hydration_fragment(node) {
/** @type {Array<Text | Comment | Element>} */
export function get_hydration_fragment(node, insert_text = false) {
/** @type {Array<import('./types.js').TemplateNode>} */
const fragment = [];
/** @type {null | Node} */
@ -37,6 +39,11 @@ export function get_hydration_fragment(node) {
if (target_depth === null) {
target_depth = depth;
} else if (depth === target_depth) {
if (insert_text && fragment.length === 0) {
const text = empty();
fragment.push(text);
/** @type {Node} */ (current_node.parentNode).insertBefore(text, current_node);
}
return fragment;
} else {
fragment.push(/** @type {Text | Comment | Element} */ (current_node));

@ -158,6 +158,11 @@ export function clone_node(node, deep) {
return /** @type {N} */ (clone_node_method.call(node, deep));
}
/** @returns {Text} */
export function empty() {
return document.createTextNode('');
}
/**
* @template {Node} N
* @param {N} node
@ -169,7 +174,7 @@ export function child(node) {
if (current_hydration_fragment !== null) {
// Child can be null if we have an element with a single child, like `<p>{text}</p>`, where `text` is empty
if (child === null) {
const text = document.createTextNode('');
const text = empty();
node.appendChild(text);
return text;
} else {
@ -193,7 +198,7 @@ export function child_frag(node, is_text) {
// if an {expression} is empty during SSR, there might be no
// text node to hydrate — we must therefore create one
if (is_text && first_node?.nodeType !== 3) {
const text = document.createTextNode('');
const text = empty();
current_hydration_fragment.unshift(text);
if (first_node) {
/** @type {DocumentFragment} */ (first_node.parentNode).insertBefore(text, first_node);
@ -221,8 +226,10 @@ export function child_frag(node, is_text) {
export function sibling(node, is_text = false) {
const next_sibling = next_sibling_get.call(node);
if (current_hydration_fragment !== null) {
// if a sibling {expression} is empty during SSR, there might be no
// text node to hydrate — we must therefore create one
if (is_text && next_sibling?.nodeType !== 3) {
const text = document.createTextNode('');
const text = empty();
if (next_sibling) {
const index = current_hydration_fragment.indexOf(
/** @type {Text | Comment | Element} */ (next_sibling)

@ -31,11 +31,11 @@ export const STATE_SYMBOL = Symbol('$state');
*/
export function proxy(value, immutable = true) {
if (typeof value === 'object' && value != null && !is_frozen(value)) {
// If we have an existing proxy, return it...
if (STATE_SYMBOL in value) {
const metadata = /** @type {import('./types.js').ProxyMetadata<T>} */ (value[STATE_SYMBOL]);
// Check that the incoming value is the same proxy that this state symbol was created for:
// If someone copies over the state symbol to a new object (using Reflect.ownKeys) the referenced
// proxy could be stale and we should not return it.
// ...unless the proxy belonged to a different object, because
// someone copied the state symbol using `Reflect.ownKeys(...)`
if (metadata.t === value || metadata.p === value) return metadata.p;
}
@ -43,16 +43,17 @@ export function proxy(value, immutable = true) {
// TODO handle Map and Set as well
if (prototype === object_prototype || prototype === array_prototype) {
const proxy = new Proxy(
value,
/** @type {ProxyHandler<import('./types.js').ProxyStateObject<T>>} */ (state_proxy_handler)
);
const proxy = new Proxy(value, state_proxy_handler);
define_property(value, STATE_SYMBOL, {
value: init(
/** @type {import('./types.js').ProxyStateObject<T>} */ (value),
/** @type {import('./types.js').ProxyStateObject<T>} */ (proxy),
immutable
),
value: /** @type {import('./types.js').ProxyMetadata} */ ({
s: new Map(),
v: source(0),
a: is_array(value),
i: immutable,
p: proxy,
t: value
}),
writable: true,
enumerable: false
});
@ -70,12 +71,13 @@ export function proxy(value, immutable = true) {
* @param {Map<T, Record<string | symbol, any>>} already_unwrapped
* @returns {Record<string | symbol, any>}
*/
function unwrap(value, already_unwrapped = new Map()) {
function unwrap(value, already_unwrapped) {
if (typeof value === 'object' && value != null && STATE_SYMBOL in value) {
const unwrapped = already_unwrapped.get(value);
if (unwrapped !== undefined) {
return unwrapped;
}
if (is_array(value)) {
/** @type {Record<string | symbol, any>} */
const array = [];
@ -90,6 +92,7 @@ function unwrap(value, already_unwrapped = new Map()) {
const keys = Reflect.ownKeys(value);
const descriptors = get_descriptors(value);
already_unwrapped.set(value, obj);
for (const key of keys) {
if (key === STATE_SYMBOL) continue;
if (descriptors[key].get) {
@ -100,6 +103,7 @@ function unwrap(value, already_unwrapped = new Map()) {
obj[key] = unwrap(property, already_unwrapped);
}
}
return obj;
}
}
@ -113,27 +117,12 @@ function unwrap(value, already_unwrapped = new Map()) {
* @returns {T}
*/
export function unstate(value) {
return /** @type {T} */ (unwrap(/** @type {import('./types.js').ProxyStateObject} */ (value)));
}
/**
* @param {import('./types.js').ProxyStateObject} value
* @param {import('./types.js').ProxyStateObject} proxy
* @param {boolean} immutable
* @returns {import('./types.js').ProxyMetadata}
*/
function init(value, proxy, immutable) {
return {
s: new Map(),
v: source(0),
a: is_array(value),
i: immutable,
p: proxy,
t: value
};
return /** @type {T} */ (
unwrap(/** @type {import('./types.js').ProxyStateObject} */ (value), new Map())
);
}
/** @type {ProxyHandler<import('./types.js').ProxyStateObject>} */
/** @type {ProxyHandler<import('./types.js').ProxyStateObject<any>>} */
const state_proxy_handler = {
defineProperty(target, prop, descriptor) {
if (descriptor.value) {

@ -9,6 +9,25 @@ export function create_fragment_from_html(html) {
return elem.content;
}
/**
* Creating a document fragment from HTML that contains script tags will not execute
* the scripts. We need to replace the script tags with new ones so that they are executed.
* @param {string} html
*/
export function create_fragment_with_script_from_html(html) {
var content = create_fragment_from_html(html);
var scripts = content.querySelectorAll('script');
for (const script of scripts) {
var new_script = document.createElement('script');
for (var i = 0; i < script.attributes.length; i++) {
new_script.setAttribute(script.attributes[i].name, script.attributes[i].value);
}
new_script.textContent = script.textContent;
/** @type {Node} */ (script.parentNode).replaceChild(new_script, script);
}
return content;
}
/**
* @param {Array<import('./types.js').TemplateNode> | import('./types.js').TemplateNode} current
* @param {null | Element} parent_element
@ -63,14 +82,16 @@ export function remove(current) {
}
/**
* Creates the content for a `@html` tag from its string value,
* inserts it before the target anchor and returns the new nodes.
* @template V
* @param {Element | Text | Comment} dom
* @param {Element | Text | Comment} target
* @param {V} value
* @param {boolean} svg
* @returns {Element | Comment | (Element | Comment | Text)[]}
*/
export function reconcile_html(dom, value, svg) {
hydrate_block_anchor(dom);
export function reconcile_html(target, value, svg) {
hydrate_block_anchor(target);
if (current_hydration_fragment !== null) {
return current_hydration_fragment;
}
@ -78,12 +99,11 @@ export function reconcile_html(dom, value, svg) {
// Even if html is the empty string we need to continue to insert something or
// else the element ordering gets out of sync, resulting in subsequent values
// not getting inserted anymore.
var target = dom;
var frag_nodes;
if (svg) {
html = `<svg>${html}</svg>`;
}
var content = create_fragment_from_html(html);
var content = create_fragment_with_script_from_html(html);
if (svg) {
content = /** @type {DocumentFragment} */ (/** @type {unknown} */ (content.firstChild));
}

@ -4,6 +4,7 @@ import {
child,
clone_node,
create_element,
empty,
init_operations,
map_get,
map_set,
@ -23,14 +24,18 @@ import {
PassiveDelegatedEvents,
DelegatedEvents,
AttributeAliases,
namespace_svg,
namespace_html
namespace_svg
} from '../../constants.js';
import { create_fragment_from_html, insert, reconcile_html, remove } from './reconciler.js';
import {
create_fragment_from_html,
create_fragment_with_script_from_html,
insert,
reconcile_html,
remove
} from './reconciler.js';
import {
render_effect,
destroy_signal,
get,
is_signal,
push_destroy_fn,
execute_effect,
@ -71,24 +76,37 @@ const all_registerd_events = new Set();
/** @type {Set<(events: Array<string>) => void>} */
const root_event_handles = new Set();
/** @returns {Text} */
export function empty() {
return document.createTextNode('');
/**
* @param {string} html
* @param {boolean} return_fragment
* @returns {() => Node}
*/
/*#__NO_SIDE_EFFECTS__*/
export function template(html, return_fragment) {
/** @type {undefined | Node} */
let cached_content;
return () => {
if (cached_content === undefined) {
const content = create_fragment_from_html(html);
cached_content = return_fragment ? content : /** @type {Node} */ (child(content));
}
return cached_content;
};
}
/**
* @param {string} html
* @param {boolean} is_fragment
* @param {boolean} return_fragment
* @returns {() => Node}
*/
/*#__NO_SIDE_EFFECTS__*/
export function template(html, is_fragment) {
export function template_with_script(html, return_fragment) {
/** @type {undefined | Node} */
let cached_content;
return () => {
if (cached_content === undefined) {
const content = create_fragment_from_html(html);
cached_content = is_fragment ? content : /** @type {Node} */ (child(content));
const content = create_fragment_with_script_from_html(html);
cached_content = return_fragment ? content : /** @type {Node} */ (child(content));
}
return cached_content;
};
@ -96,17 +114,35 @@ export function template(html, is_fragment) {
/**
* @param {string} svg
* @param {boolean} is_fragment
* @param {boolean} return_fragment
* @returns {() => Node}
*/
/*#__NO_SIDE_EFFECTS__*/
export function svg_template(svg, is_fragment) {
export function svg_template(svg, return_fragment) {
/** @type {undefined | Node} */
let cached_content;
return () => {
if (cached_content === undefined) {
const content = /** @type {Node} */ (child(create_fragment_from_html(`<svg>${svg}</svg>`)));
cached_content = is_fragment ? content : /** @type {Node} */ (child(content));
cached_content = return_fragment ? content : /** @type {Node} */ (child(content));
}
return cached_content;
};
}
/**
* @param {string} svg
* @param {boolean} return_fragment
* @returns {() => Node}
*/
/*#__NO_SIDE_EFFECTS__*/
export function svg_template_with_script(svg, return_fragment) {
/** @type {undefined | Node} */
let cached_content;
return () => {
if (cached_content === undefined) {
const content = /** @type {Node} */ (child(create_fragment_from_html(`<svg>${svg}</svg>`)));
cached_content = return_fragment ? content : /** @type {Node} */ (child(content));
}
return cached_content;
};
@ -172,11 +208,22 @@ const space_template = template(' ', false);
const comment_template = template('<!>', true);
/**
* @param {null | Text | Comment | Element} anchor
* @param {Text | Comment | Element | null} anchor
*/
/*#__NO_SIDE_EFFECTS__*/
export function space(anchor) {
return open(anchor, true, space_template);
/** @type {Node | null} */
var node = /** @type {any} */ (open(anchor, true, space_template));
// if an {expression} is empty during SSR, there might be no
// text node to hydrate (or an anchor comment is falsely detected instead)
// — we must therefore create one
if (current_hydration_fragment !== null && node?.nodeType !== 3) {
node = empty();
// @ts-ignore in this case the anchor should always be a comment,
// if not something more fundamental is wrong and throwing here is better to bail out early
anchor.parentElement.insertBefore(node, anchor);
}
return node;
}
/**
@ -188,6 +235,8 @@ export function comment(anchor) {
}
/**
* Assign the created (or in hydration mode, traversed) dom elements to the current block
* and insert the elements into the dom (in client mode).
* @param {Element | Text} dom
* @param {boolean} is_fragment
* @param {null | Text | Comment | Element} anchor
@ -2826,7 +2875,9 @@ export function mount(component, options) {
const container = options.target;
const block = create_root_block(options.intro || false);
const first_child = /** @type {ChildNode} */ (container.firstChild);
const hydration_fragment = get_hydration_fragment(first_child);
// Call with insert_text == true to prevent empty {expressions} resulting in an empty
// fragment array, resulting in a hydration error down the line
const hydration_fragment = get_hydration_fragment(first_child, true);
const previous_hydration_fragment = current_hydration_fragment;
/** @type {Exports} */

@ -1,6 +1,6 @@
import { DEV } from 'esm-env';
import { subscribe_to_store } from '../../store/utils.js';
import { noop, run_all } from '../common.js';
import { noop, run, run_all } from '../common.js';
import {
array_prototype,
get_descriptor,
@ -39,7 +39,6 @@ const FLUSH_MICROTASK = 0;
const FLUSH_SYNC = 1;
export const UNINITIALIZED = Symbol();
export const LAZY_PROPERTY = Symbol();
// Used for controlling the flush of effects.
let current_scheduler_mode = FLUSH_MICROTASK;
@ -104,18 +103,9 @@ export let current_block = null;
/** @type {import('./types.js').ComponentContext | null} */
export let current_component_context = null;
export let is_ssr = false;
export let updating_derived = false;
/**
* @param {boolean} ssr
* @returns {void}
*/
export function set_is_ssr(ssr) {
is_ssr = ssr;
}
/**
* @param {null | import('./types.js').ComponentContext} context
* @returns {boolean}
@ -147,14 +137,6 @@ export function batch_inspect(target, prop, receiver) {
};
}
/**
* @param {null | import('./types.js').ComponentContext} context_stack_item
* @returns {void}
*/
export function set_current_component_context(context_stack_item) {
current_component_context = context_stack_item;
}
/**
* @param {unknown} a
* @param {unknown} b
@ -181,8 +163,6 @@ function create_source_signal(flags, value) {
f: flags,
// value
v: value,
// context: We can remove this if we get rid of beforeUpdate/afterUpdate
x: null,
// this is for DEV only
inspect: new Set()
};
@ -195,9 +175,7 @@ function create_source_signal(flags, value) {
// flags
f: flags,
// value
v: value,
// context: We can remove this if we get rid of beforeUpdate/afterUpdate
x: null
v: value
};
}
@ -346,12 +324,6 @@ function execute_signal_fn(signal) {
current_skip_consumer = !is_flushing_effect && (flags & UNOWNED) !== 0;
current_untracking = false;
// Render effects are invoked when the UI is about to be updated - run beforeUpdate at that point
if (is_render_effect && current_component_context?.u != null) {
// update_callbacks.execute()
current_component_context.u.e();
}
try {
let res;
if (is_render_effect) {
@ -924,7 +896,7 @@ export function store_set(store, value) {
* @param {import('./types.js').StoreReferencesContainer} stores
*/
export function unsubscribe_on_destroy(stores) {
onDestroy(() => {
on_destroy(() => {
let store_name;
for (store_name in stores) {
const ref = stores[store_name];
@ -1150,7 +1122,7 @@ export function mark_subtree_inert(signal, inert, visited_blocks = new Set()) {
* @returns {void}
*/
function mark_signal_consumers(signal, to_status, force_schedule) {
const runes = is_runes(signal.x);
const runes = is_runes(null);
const consumers = signal.c;
if (consumers !== null) {
const length = consumers.length;
@ -1192,7 +1164,7 @@ export function set_signal_value(signal, value) {
!current_untracking &&
!ignore_mutation_validation &&
current_consumer !== null &&
is_runes(signal.x) &&
is_runes(null) &&
(current_consumer.f & DERIVED) !== 0
) {
throw new Error(
@ -1208,7 +1180,6 @@ export function set_signal_value(signal, value) {
(signal.f & SOURCE) !== 0 &&
!(/** @type {import('./types.js').EqualsFunctions} */ (signal.e)(value, signal.v))
) {
const component_context = signal.x;
signal.v = value;
// If the current signal is running for the first time, it won't have any
// consumers as we only allocate and assign the consumers after the signal
@ -1219,7 +1190,7 @@ export function set_signal_value(signal, value) {
// $effect(() => x++)
//
if (
is_runes(component_context) &&
is_runes(null) &&
current_effect !== null &&
current_effect.c === null &&
(current_effect.f & CLEAN) !== 0
@ -1236,19 +1207,6 @@ export function set_signal_value(signal, value) {
}
}
mark_signal_consumers(signal, DIRTY, true);
// If we have afterUpdates locally on the component, but we're within a render effect
// then we will need to manually invoke the beforeUpdate/afterUpdate logic.
// TODO: should we put this being a is_runes check and only run it in non-runes mode?
if (current_effect === null && current_queued_pre_and_render_effects.length === 0) {
const update_callbacks = component_context?.u;
if (update_callbacks != null) {
run_all(update_callbacks.b);
const managed = managed_effect(() => {
destroy_signal(managed);
run_all(update_callbacks.a);
});
}
}
// @ts-expect-error
if (DEV && signal.inspect) {
@ -1299,7 +1257,7 @@ export function derived(init) {
create_computation_signal(flags | CLEAN, UNINITIALIZED, current_block)
);
signal.i = init;
signal.x = current_component_context;
bind_signal_to_component_context(signal);
signal.e = default_equals;
if (current_consumer !== null) {
push_reference(current_consumer, signal);
@ -1327,10 +1285,27 @@ export function derived_safe_equal(init) {
/*#__NO_SIDE_EFFECTS__*/
export function source(initial_value) {
const source = create_source_signal(SOURCE | CLEAN, initial_value);
source.x = current_component_context;
bind_signal_to_component_context(source);
return source;
}
/**
* This function binds a signal to the component context, so that we can fire
* beforeUpdate/afterUpdate callbacks at the correct time etc
* @param {import('./types.js').Signal} signal
*/
function bind_signal_to_component_context(signal) {
if (current_component_context === null || !current_component_context.r) return;
const signals = current_component_context.d;
if (signals) {
signals.push(signal);
} else {
current_component_context.d = [signal];
}
}
/**
* @template V
* @param {V} initial_value
@ -1566,20 +1541,6 @@ export function is_signal(val) {
);
}
/**
* @template O
* @template P
* @param {any} val
* @returns {val is import('./types.js').LazyProperty<O, P>}
*/
export function is_lazy_property(val) {
return (
typeof val === 'object' &&
val !== null &&
/** @type {import('./types.js').LazyProperty<O, P>} */ (val).t === LAZY_PROPERTY
);
}
/**
* @template V
* @param {unknown} val
@ -1879,18 +1840,11 @@ export function value_or_fallback(value, fallback) {
/**
* Schedules a callback to run immediately before the component is unmounted.
*
* Out of `onMount`, `beforeUpdate`, `afterUpdate` and `onDestroy`, this is the
* only one that runs inside a server-side component.
*
* https://svelte.dev/docs/svelte#ondestroy
* @param {() => any} fn
* @returns {void}
*/
export function onDestroy(fn) {
if (!is_ssr) {
user_effect(() => () => untrack(fn));
}
function on_destroy(fn) {
user_effect(() => () => untrack(fn));
}
/**
@ -1910,6 +1864,8 @@ export function push(props, runes = false) {
m: false,
// parent
p: current_component_context,
// signals
d: null,
// props
s: props,
// runes
@ -1941,6 +1897,56 @@ export function pop(accessors) {
}
}
/**
* Invoke the getter of all signals associated with a component
* so they can be registered to the effect this function is called in.
* @param {import('./types.js').ComponentContext} context
*/
function observe_all(context) {
if (context.d) {
for (const signal of context.d) get(signal);
}
const props = get_descriptors(context.s);
for (const descriptor of Object.values(props)) {
if (descriptor.get) descriptor.get();
}
}
/**
* Legacy-mode only: Call `onMount` callbacks and set up `beforeUpdate`/`afterUpdate` effects
*/
export function init() {
const context = /** @type {import('./types.js').ComponentContext} */ (current_component_context);
const callbacks = context.u;
if (!callbacks) return;
// beforeUpdate
pre_effect(() => {
observe_all(context);
callbacks.b.forEach(run);
});
// onMount (must run before afterUpdate)
user_effect(() => {
const fns = untrack(() => callbacks.m.map(run));
return () => {
for (const fn of fns) {
if (typeof fn === 'function') {
fn();
}
}
};
});
// afterUpdate
user_effect(() => {
observe_all(context);
callbacks.a.forEach(run);
});
}
/**
* @param {any} value
* @param {Set<any>} visited
@ -2059,21 +2065,6 @@ export function inspect(get_value, inspect = console.log) {
});
}
/**
* @template O
* @template P
* @param {O} o
* @param {P} p
* @returns {import('./types.js').LazyProperty<O, P>}
*/
export function lazy_property(o, p) {
return {
o,
p,
t: LAZY_PROPERTY
};
}
/**
* @template V
* @param {V} value
@ -2084,9 +2075,6 @@ export function unwrap(value) {
// @ts-ignore
return get(value);
}
if (is_lazy_property(value)) {
return value.o[value.p];
}
// @ts-ignore
return value;
}

@ -10,8 +10,7 @@ import {
ROOT_BLOCK
} from './block.js';
import { destroy_each_item_block, get_first_element } from './each.js';
import { append_child } from './operations.js';
import { empty } from './render.js';
import { append_child, empty } from './operations.js';
import {
current_block,
current_effect,

@ -11,7 +11,7 @@ import {
SNIPPET_BLOCK
} from './block.js';
import type { STATE_SYMBOL } from './proxy.js';
import { DERIVED, EFFECT, RENDER_EFFECT, SOURCE, PRE_EFFECT, LAZY_PROPERTY } from './runtime.js';
import { DERIVED, EFFECT, RENDER_EFFECT, SOURCE, PRE_EFFECT } from './runtime.js';
// Put all internal types in this file. Once we convert to JSDoc, we can make this a d.ts file
@ -31,16 +31,18 @@ export type Store<V> = {
set(value: V): void;
};
// For all the core internal objects, we use signal character property strings.
// For all the core internal objects, we use single-character property strings.
// This not only reduces code-size and parsing, but it also improves the performance
// when the JS VM JITs the code.
export type ComponentContext = {
/** local signals (needed for beforeUpdate/afterUpdate) */
d: null | Signal<any>[];
/** props */
s: Record<string, unknown>;
/** accessors */
a: Record<string, any> | null;
/** effectgs */
/** effects */
e: null | Array<EffectSignal>;
/** mounted */
m: boolean;
@ -52,12 +54,12 @@ export type ComponentContext = {
r: boolean;
/** update_callbacks */
u: null | {
/** before */
b: Array<() => void>;
/** after */
/** afterUpdate callbacks */
a: Array<() => void>;
/** execute */
e: () => void;
/** beforeUpdate callbacks */
b: Array<() => void>;
/** onMount callbacks */
m: Array<() => any>;
};
};
@ -69,8 +71,6 @@ export type ComponentContext = {
export type SourceSignal<V = unknown> = {
/** consumers: Signals that read from the current signal */
c: null | ComputationSignal[];
/** context: The associated component if this signal is an effect/computed */
x: null | ComponentContext;
/** equals: For value equality */
e: null | EqualsFunctions;
/** flags: The types that the signal represent, as a bitwise value */
@ -123,12 +123,6 @@ export type MaybeSignal<T = unknown> = T | Signal<T>;
export type UnwrappedSignal<T> = T extends Signal<infer U> ? U : T;
export type LazyProperty<O, P> = {
o: O;
p: P;
t: typeof LAZY_PROPERTY;
};
export type EqualsFunctions<T = any> = (a: T, v: T) => boolean;
export type BlockType =

@ -19,3 +19,8 @@ export function run_all(arr) {
arr[i]();
}
}
/** @param {Function} fn */
export function run(fn) {
return fn();
}

@ -30,7 +30,6 @@ export {
exclude_from_object,
store_set,
unsubscribe_on_destroy,
onDestroy,
pop,
push,
reactive_import,
@ -38,7 +37,8 @@ export {
user_root_effect,
inspect,
unwrap,
freeze
freeze,
init
} from './client/runtime.js';
export * from './client/each.js';
export * from './client/render.js';

@ -1,5 +1,4 @@
import * as $ from '../client/runtime.js';
import { set_is_ssr } from '../client/runtime.js';
import { is_promise, noop } from '../common.js';
import { subscribe_to_store } from '../../store/utils.js';
import { DOMBooleanAttributes } from '../../constants.js';
@ -79,6 +78,12 @@ export function assign_payload(p1, p2) {
p1.anchor = p2.anchor;
}
/**
* Array of `onDestroy` callbacks that should be called at the end of the server render function
* @type {Function[]}
*/
export let on_destroy = [];
/**
* @param {(...args: any[]) => void} component
* @param {{ props: Record<string, any>; context?: Map<any, any> }} options
@ -89,7 +94,8 @@ export function render(component, options) {
const root_anchor = create_anchor(payload);
const root_head_anchor = create_anchor(payload.head);
set_is_ssr(true);
const prev_on_destroy = on_destroy;
on_destroy = [];
payload.out += root_anchor;
if (options.context) {
@ -102,7 +108,8 @@ export function render(component, options) {
$.pop();
}
payload.out += root_anchor;
set_is_ssr(false);
for (const cleanup of on_destroy) cleanup();
on_destroy = prev_on_destroy;
return {
head:

@ -1,12 +1,8 @@
import {
current_component_context,
destroy_signal,
get_or_init_context_map,
is_ssr,
managed_effect,
untrack,
user_effect,
flush_local_render_effects
user_effect
} from '../internal/client/runtime.js';
import { is_array } from '../internal/client/utils.js';
@ -25,16 +21,38 @@ import { is_array } from '../internal/client/utils.js';
* @returns {void}
*/
export function onMount(fn) {
if (!is_ssr) {
if (current_component_context === null) {
throw new Error('onMount can only be used during component initialisation.');
}
if (current_component_context.r) {
user_effect(() => {
const result = untrack(fn);
if (typeof result === 'function') {
return /** @type {() => any} */ (result);
}
const cleanup = untrack(fn);
if (typeof cleanup === 'function') return /** @type {() => void} */ (cleanup);
});
} else {
init_update_callbacks(current_component_context).m.push(fn);
}
}
/**
* Schedules a callback to run immediately before the component is unmounted.
*
* Out of `onMount`, `beforeUpdate`, `afterUpdate` and `onDestroy`, this is the
* only one that runs inside a server-side component.
*
* https://svelte.dev/docs/svelte#ondestroy
* @param {() => any} fn
* @returns {void}
*/
export function onDestroy(fn) {
if (current_component_context === null) {
throw new Error('onDestroy can only be used during component initialisation.');
}
onMount(() => () => untrack(fn));
}
/**
* Retrieves the context that belongs to the closest parent component with the specified `key`.
* Must be called during component initialisation.
@ -155,46 +173,6 @@ export function createEventDispatcher() {
};
}
function init_update_callbacks() {
let called_before = false;
let called_after = false;
/** @type {NonNullable<import('../internal/client/types.js').ComponentContext['u']>} */
const update_callbacks = {
b: [],
a: [],
e() {
if (!called_before) {
called_before = true;
// TODO somehow beforeUpdate ran twice on mount in Svelte 4 if it causes a render
// possibly strategy to get this back if needed: analyse beforeUpdate function for assignements to state,
// if yes, add a call to the component to force-run beforeUpdate once.
untrack(() => update_callbacks.b.forEach(/** @param {any} c */ (c) => c()));
flush_local_render_effects();
// beforeUpdate can run again once if afterUpdate causes another update,
// but afterUpdate shouldn't be called again in that case to prevent infinite loops
if (!called_after) {
user_effect(() => {
called_before = false;
called_after = true;
untrack(() => update_callbacks.a.forEach(/** @param {any} c */ (c) => c()));
// managed_effect so that it's not cleaned up when the parent effect is cleaned up
const managed = managed_effect(() => {
destroy_signal(managed);
called_after = false;
});
});
} else {
user_effect(() => {
called_before = false;
});
}
}
}
};
return update_callbacks;
}
// TODO mark beforeUpdate and afterUpdate as deprecated in Svelte 6
/**
@ -210,12 +188,15 @@ function init_update_callbacks() {
* @returns {void}
*/
export function beforeUpdate(fn) {
const component_context = current_component_context;
if (component_context === null) {
throw new Error('beforeUpdate can only be used during component initialisation.');
if (current_component_context === null) {
throw new Error('beforeUpdate can only be used during component initialisation');
}
(component_context.u ??= init_update_callbacks()).b.push(fn);
if (current_component_context.r) {
throw new Error('beforeUpdate cannot be used in runes mode');
}
init_update_callbacks(current_component_context).b.push(fn);
}
/**
@ -231,22 +212,25 @@ export function beforeUpdate(fn) {
* @returns {void}
*/
export function afterUpdate(fn) {
const component_context = current_component_context;
if (component_context === null) {
if (current_component_context === null) {
throw new Error('afterUpdate can only be used during component initialisation.');
}
(component_context.u ??= init_update_callbacks()).a.push(fn);
if (current_component_context.r) {
throw new Error('afterUpdate cannot be used in runes mode');
}
init_update_callbacks(current_component_context).a.push(fn);
}
/**
* Legacy-mode: Init callbacks object for onMount/beforeUpdate/afterUpdate
* @param {import('../internal/client/types.js').ComponentContext} context
*/
function init_update_callbacks(context) {
return (context.u ??= { a: [], b: [], m: [] });
}
// TODO bring implementations in here
// (except probably untrack — do we want to expose that, if there's also a rune?)
export {
flushSync,
createRoot,
mount,
tick,
untrack,
unstate,
onDestroy
} from '../internal/index.js';
export { flushSync, createRoot, mount, tick, untrack, unstate } from '../internal/index.js';

@ -1,3 +1,5 @@
import { on_destroy } from '../internal/server/index.js';
export {
createRoot,
createEventDispatcher,
@ -6,7 +8,6 @@ export {
getContext,
hasContext,
mount,
onDestroy,
setContext,
tick,
untrack
@ -15,6 +16,11 @@ export {
/** @returns {void} */
export function onMount() {}
/** @param {Function} fn */
export function onDestroy(fn) {
on_destroy.push(fn);
}
/** @returns {void} */
export function beforeUpdate() {}

@ -1,4 +1,4 @@
import { noop } from '../internal/common.js';
import { noop, run } from '../internal/common.js';
import { subscribe_to_store } from './utils.js';
/**
@ -106,11 +106,6 @@ export function writable(value, start = noop) {
return { set, update, subscribe };
}
/** @param {Function} fn */
function run(fn) {
return fn();
}
/**
* @param {Function[]} fns
* @returns {void}

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

@ -0,0 +1,8 @@
import { test } from '../../test';
export default test({
error: {
code: 'duplicate-slot-name',
message: "Duplicate slot name 'foo' in <Nested>"
}
});

@ -0,0 +1,9 @@
<script>
import Nested from './irrelevant';
import Inner from './irrelevant';
</script>
<Nested>
<Inner slot="foo">{value}</Inner>
<Inner slot="foo">{value}</Inner>
</Nested>

@ -0,0 +1,8 @@
import { test } from '../../test';
export default test({
error: {
code: 'duplicate-slot-name',
message: "Duplicate slot name 'foo' in <Nested>"
}
});

@ -0,0 +1,9 @@
<script>
import Nested from './irrelevant';
import Inner from './irrelevant';
</script>
<Nested>
<p slot="foo">{value}</p>
<Inner slot="foo">{value}</Inner>
</Nested>

@ -3,7 +3,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-derived-export',
message: 'Cannot export derived state',
position: [24, 66]
message:
'Cannot export derived state. To expose the current derived value, export a function returning its value'
}
});

@ -0,0 +1,4 @@
<script>
let count = $state(0);
export const double = $derived(count * 2);
</script>

@ -0,0 +1,10 @@
import { test } from '../../test';
export default test({
error: {
code: 'invalid-state-export',
message:
"Cannot export state if it is reassigned. Either export a function returning the state value or only mutate the state value's properties",
position: [59, 99]
}
});

@ -0,0 +1,15 @@
<script>
export const object = $state({
ok: true
});
export const primitive = $state('nope');
export function update_object() {
object.ok = !object.ok;
}
export function update_primitive() {
primitive = 'yep';
}
</script>

@ -3,7 +3,8 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-state-export',
message: 'Cannot export state if it is reassigned',
message:
"Cannot export state if it is reassigned. Either export a function returning the state value or only mutate the state value's properties",
position: [46, 86]
}
});

@ -0,0 +1,8 @@
import { test } from '../../test';
export default test({
error: {
code: 'invalid-runes-mode-import',
message: 'beforeUpdate cannot be used in runes mode'
}
});

@ -0,0 +1,5 @@
<svelte:options runes />
<script>
import { beforeUpdate, afterUpdate } from 'svelte';
</script>

@ -3,7 +3,8 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-state-export',
message: 'Cannot export state if it is reassigned',
message:
"Cannot export state if it is reassigned. Either export a function returning the state value or only mutate the state value's properties",
position: [28, 53]
}
});

@ -0,0 +1,9 @@
import { test } from '../../test';
export default test({
error: {
code: 'invalid-prop-export',
message:
'Cannot export properties. To expose the current value of a property, export a function returning its value'
}
});

@ -0,0 +1,4 @@
<script>
let { foo } = $props();
export { foo };
</script>

@ -0,0 +1,9 @@
import { test } from '../../test';
export default test({
error: {
code: 'invalid-each-assignment',
message:
"Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. 'array[i] = value' instead of 'entry = value')"
}
});

@ -0,0 +1,7 @@
<script>
let arr = $state([1,2,3]);
</script>
{#each arr as value}
<input bind:value>
{/each}

@ -0,0 +1,9 @@
import { test } from '../../test';
export default test({
error: {
code: 'invalid-each-assignment',
message:
"Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. 'array[i] = value' instead of 'entry = value')"
}
});

@ -0,0 +1,7 @@
<script>
let arr = $state([1,2,3]);
</script>
{#each arr as value}
<button onclick={() => value += 1}>click</button>
{/each}

@ -0,0 +1,24 @@
@keyframes svelte-xyz-a {
0% {
transform: scale(1);
}
100% {
transform: scale(2);
}
}
@keyframes svelte-xyz-animation {
0% {
transform: scale(1);
}
100% {
transform: scale(2);
}
}
h1.svelte-xyz {
animation: 1s linear infinite svelte-xyz-a;
animation: svelte-xyz-a 1s linear infinite;
animation: 1s linear infinite svelte-xyz-a,svelte-xyz-animation 1s linear infinite;
}

@ -0,0 +1,27 @@
<h1>test</h1>
<style>
@keyframes a {
0% {
transform: scale(1);
}
100% {
transform: scale(2);
}
}
@keyframes animation {
0% {
transform: scale(1);
}
100% {
transform: scale(2);
}
}
h1 {
animation: 1s linear infinite a;
animation: a 1s linear infinite;
animation: 1s linear infinite a,animation 1s linear infinite;
}
</style>

@ -1,7 +1,7 @@
main.svelte-xyz button.svelte-xyz.svelte-xyz {
main.svelte-xyz button:where(.svelte-xyz) {
background-color: red;
}
main.svelte-xyz div.svelte-xyz > button.svelte-xyz {
main.svelte-xyz div:where(.svelte-xyz) > button:where(.svelte-xyz) {
background-color: blue;
}

@ -1,3 +1,3 @@
.test.svelte-xyz > div.svelte-xyz {
.test.svelte-xyz > div:where(.svelte-xyz) {
color: #0af;
}

@ -1,3 +1,3 @@
p.svelte-xyz span.svelte-xyz {
p.svelte-xyz span:where(.svelte-xyz) {
color: red;
}

@ -1,18 +1,18 @@
div.svelte-xyz.svelte-xyz.svelte-xyz {
div.svelte-xyz {
color: red;
}
h2.svelte-xyz > p.svelte-xyz.svelte-xyz {
h2.svelte-xyz > p:where(.svelte-xyz) {
color: red;
}
h2.svelte-xyz span.svelte-xyz.svelte-xyz {
h2.svelte-xyz span:where(.svelte-xyz) {
color: red;
}
h2.svelte-xyz > span.svelte-xyz > b.svelte-xyz {
h2.svelte-xyz > span:where(.svelte-xyz) > b:where(.svelte-xyz) {
color: red;
}
h2.svelte-xyz span b.svelte-xyz.svelte-xyz {
h2.svelte-xyz span b:where(.svelte-xyz) {
color: red;
}
h2.svelte-xyz b.svelte-xyz.svelte-xyz {
h2.svelte-xyz b:where(.svelte-xyz) {
color: red;
}

@ -1,13 +1,13 @@
.a.svelte-xyz ~ .b.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.a.svelte-xyz ~ .c.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.a.svelte-xyz ~ .d.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.a.svelte-xyz ~ .e.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.a.svelte-xyz ~ .f.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.a.svelte-xyz ~ .g.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.a.svelte-xyz ~ .h.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.a.svelte-xyz ~ .b:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .c:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .f:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .g:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .h:where(.svelte-xyz) { color: green; }
.b.svelte-xyz ~ .d.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.c.svelte-xyz ~ .d.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.b.svelte-xyz ~ .e.svelte-xyz ~ .f.svelte-xyz ~ .h.svelte-xyz { color: green; }
.b.svelte-xyz ~ .d.svelte-xyz ~ .h.svelte-xyz.svelte-xyz { color: green; }
.c.svelte-xyz ~ .g.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.b.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
.c.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
.b.svelte-xyz ~ .e:where(.svelte-xyz) ~ .f:where(.svelte-xyz) ~ .h:where(.svelte-xyz) { color: green; }
.b.svelte-xyz ~ .d:where(.svelte-xyz) ~ .h:where(.svelte-xyz) { color: green; }
.c.svelte-xyz ~ .g:where(.svelte-xyz) { color: green; }

@ -1,10 +1,10 @@
.a.svelte-xyz ~ .b.svelte-xyz { color: green; }
.a.svelte-xyz ~ .c.svelte-xyz { color: green; }
.a.svelte-xyz ~ .d.svelte-xyz { color: green; }
.b.svelte-xyz ~ .e.svelte-xyz { color: green; }
.c.svelte-xyz ~ .e.svelte-xyz { color: green; }
.d.svelte-xyz ~ .e.svelte-xyz { color: green; }
.a.svelte-xyz ~ .e.svelte-xyz { color: green; }
.a.svelte-xyz ~ .b:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .c:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
.b.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
.c.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
.d.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
/* no match */
/* (unused) .b ~ .c { color: green; }*/

@ -1,22 +1,22 @@
/* boundary of each */
.a.svelte-xyz ~ .b.svelte-xyz {
.a.svelte-xyz ~ .b:where(.svelte-xyz) {
color: green;
}
.c.svelte-xyz ~ .d.svelte-xyz {
.c.svelte-xyz ~ .d:where(.svelte-xyz) {
color: green;
}
/* if array is empty */
.a.svelte-xyz ~ .d.svelte-xyz {
.a.svelte-xyz ~ .d:where(.svelte-xyz) {
color: green;
}
/* if array has multiple items */
.c.svelte-xyz ~ .b.svelte-xyz {
.c.svelte-xyz ~ .b:where(.svelte-xyz) {
color: green;
}
/* normal sibling */
.b.svelte-xyz ~ .c.svelte-xyz {
.b.svelte-xyz ~ .c:where(.svelte-xyz) {
color: green;
}
.a.svelte-xyz ~ .c.svelte-xyz {
.a.svelte-xyz ~ .c:where(.svelte-xyz) {
color: green;
}

@ -1,30 +1,30 @@
.a.svelte-xyz ~ .e.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.a.svelte-xyz ~ .f.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.b.svelte-xyz ~ .c.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.b.svelte-xyz ~ .d.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.c.svelte-xyz ~ .e.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.c.svelte-xyz ~ .f.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.d.svelte-xyz ~ .e.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.d.svelte-xyz ~ .f.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.e.svelte-xyz ~ .e.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.i.svelte-xyz ~ .j.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.g.svelte-xyz ~ .h.svelte-xyz ~ .j.svelte-xyz.svelte-xyz { color: green; }
.g.svelte-xyz ~ .i.svelte-xyz ~ .j.svelte-xyz.svelte-xyz { color: green; }
.m.svelte-xyz ~ .m.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.m.svelte-xyz ~ .l.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.l.svelte-xyz ~ .m.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.a.svelte-xyz ~ .c.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.a.svelte-xyz ~ .g.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.b.svelte-xyz ~ .e.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.c.svelte-xyz ~ .g.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.c.svelte-xyz ~ .k.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.d.svelte-xyz ~ .d.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.g.svelte-xyz ~ .g.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.h.svelte-xyz ~ .h.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.i.svelte-xyz ~ .i.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.j.svelte-xyz ~ .j.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.g.svelte-xyz ~ .j.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.g.svelte-xyz ~ .h.svelte-xyz ~ .i.svelte-xyz ~ .j.svelte-xyz { color: green; }
.a.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .f:where(.svelte-xyz) { color: green; }
.b.svelte-xyz ~ .c:where(.svelte-xyz) { color: green; }
.b.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
.c.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
.c.svelte-xyz ~ .f:where(.svelte-xyz) { color: green; }
.d.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
.d.svelte-xyz ~ .f:where(.svelte-xyz) { color: green; }
.e.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
.i.svelte-xyz ~ .j:where(.svelte-xyz) { color: green; }
.g.svelte-xyz ~ .h:where(.svelte-xyz) ~ .j:where(.svelte-xyz) { color: green; }
.g.svelte-xyz ~ .i:where(.svelte-xyz) ~ .j:where(.svelte-xyz) { color: green; }
.m.svelte-xyz ~ .m:where(.svelte-xyz) { color: green; }
.m.svelte-xyz ~ .l:where(.svelte-xyz) { color: green; }
.l.svelte-xyz ~ .m:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .c:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .g:where(.svelte-xyz) { color: green; }
.b.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
.c.svelte-xyz ~ .g:where(.svelte-xyz) { color: green; }
.c.svelte-xyz ~ .k:where(.svelte-xyz) { color: green; }
.d.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
.g.svelte-xyz ~ .g:where(.svelte-xyz) { color: green; }
.h.svelte-xyz ~ .h:where(.svelte-xyz) { color: green; }
.i.svelte-xyz ~ .i:where(.svelte-xyz) { color: green; }
.j.svelte-xyz ~ .j:where(.svelte-xyz) { color: green; }
.g.svelte-xyz ~ .j:where(.svelte-xyz) { color: green; }
.g.svelte-xyz ~ .h:where(.svelte-xyz) ~ .i:where(.svelte-xyz) ~ .j:where(.svelte-xyz) { color: green; }
/* no match */
/* (unused) .e ~ .f { color: green; }*/

@ -1,8 +1,8 @@
.a.svelte-xyz ~ .b.svelte-xyz { color: green; }
.a.svelte-xyz ~ .c.svelte-xyz { color: green; }
.b.svelte-xyz ~ .d.svelte-xyz { color: green; }
.c.svelte-xyz ~ .d.svelte-xyz { color: green; }
.a.svelte-xyz ~ .d.svelte-xyz { color: green; }
.a.svelte-xyz ~ .b:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .c:where(.svelte-xyz) { color: green; }
.b.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
.c.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
/* no match */
/* (unused) .b ~ .c { color: green; }*/

@ -1,60 +1,60 @@
/* boundary of each */
.a.svelte-xyz ~ .d.svelte-xyz.svelte-xyz { color: green; }
.a.svelte-xyz ~ .e.svelte-xyz.svelte-xyz { color: green; }
.a.svelte-xyz ~ .f.svelte-xyz.svelte-xyz { color: green; }
.a.svelte-xyz ~ .g.svelte-xyz.svelte-xyz { color: green; }
.c.svelte-xyz ~ .d.svelte-xyz.svelte-xyz { color: green; }
.c.svelte-xyz ~ .e.svelte-xyz.svelte-xyz { color: green; }
.c.svelte-xyz ~ .f.svelte-xyz.svelte-xyz { color: green; }
.c.svelte-xyz ~ .g.svelte-xyz.svelte-xyz { color: green; }
.a.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .f:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .g:where(.svelte-xyz) { color: green; }
.c.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
.c.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
.c.svelte-xyz ~ .f:where(.svelte-xyz) { color: green; }
.c.svelte-xyz ~ .g:where(.svelte-xyz) { color: green; }
/* nested boundary of each */
.j.svelte-xyz ~ .m.svelte-xyz.svelte-xyz { color: green; }
.j.svelte-xyz ~ .n.svelte-xyz.svelte-xyz { color: green; }
.j.svelte-xyz ~ .o.svelte-xyz.svelte-xyz { color: green; }
.k.svelte-xyz ~ .m.svelte-xyz.svelte-xyz { color: green; }
.k.svelte-xyz ~ .n.svelte-xyz.svelte-xyz { color: green; }
.k.svelte-xyz ~ .o.svelte-xyz.svelte-xyz { color: green; }
.l.svelte-xyz ~ .m.svelte-xyz.svelte-xyz { color: green; }
.l.svelte-xyz ~ .n.svelte-xyz.svelte-xyz { color: green; }
.l.svelte-xyz ~ .o.svelte-xyz.svelte-xyz { color: green; }
.j.svelte-xyz ~ .m:where(.svelte-xyz) { color: green; }
.j.svelte-xyz ~ .n:where(.svelte-xyz) { color: green; }
.j.svelte-xyz ~ .o:where(.svelte-xyz) { color: green; }
.k.svelte-xyz ~ .m:where(.svelte-xyz) { color: green; }
.k.svelte-xyz ~ .n:where(.svelte-xyz) { color: green; }
.k.svelte-xyz ~ .o:where(.svelte-xyz) { color: green; }
.l.svelte-xyz ~ .m:where(.svelte-xyz) { color: green; }
.l.svelte-xyz ~ .n:where(.svelte-xyz) { color: green; }
.l.svelte-xyz ~ .o:where(.svelte-xyz) { color: green; }
/* parent each */
.d.svelte-xyz ~ .e.svelte-xyz.svelte-xyz { color: green; }
.e.svelte-xyz ~ .f.svelte-xyz.svelte-xyz { color: green; }
.d.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
.e.svelte-xyz ~ .f:where(.svelte-xyz) { color: green; }
/* child each */
.g.svelte-xyz ~ .h.svelte-xyz.svelte-xyz { color: green; }
.g.svelte-xyz ~ .h:where(.svelte-xyz) { color: green; }
/* wrap around */
.f.svelte-xyz ~ .d.svelte-xyz.svelte-xyz { color: green; }
.f.svelte-xyz ~ .e.svelte-xyz.svelte-xyz { color: green; }
.f.svelte-xyz ~ .f.svelte-xyz.svelte-xyz { color: green; }
.f.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
.f.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
.f.svelte-xyz ~ .f:where(.svelte-xyz) { color: green; }
.h.svelte-xyz ~ .g.svelte-xyz.svelte-xyz { color: green; }
.i.svelte-xyz ~ .h.svelte-xyz.svelte-xyz { color: green; }
.i.svelte-xyz ~ .g.svelte-xyz.svelte-xyz { color: green; }
.h.svelte-xyz ~ .g:where(.svelte-xyz) { color: green; }
.i.svelte-xyz ~ .h:where(.svelte-xyz) { color: green; }
.i.svelte-xyz ~ .g:where(.svelte-xyz) { color: green; }
/* wrap around self */
.d.svelte-xyz ~ .d.svelte-xyz.svelte-xyz { color: green; }
.e.svelte-xyz ~ .e.svelte-xyz.svelte-xyz { color: green; }
.f.svelte-xyz ~ .f.svelte-xyz.svelte-xyz { color: green; }
.g.svelte-xyz ~ .g.svelte-xyz.svelte-xyz { color: green; }
.h.svelte-xyz ~ .h.svelte-xyz.svelte-xyz { color: green; }
.i.svelte-xyz ~ .i.svelte-xyz.svelte-xyz { color: green; }
.d.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
.e.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
.f.svelte-xyz ~ .f:where(.svelte-xyz) { color: green; }
.g.svelte-xyz ~ .g:where(.svelte-xyz) { color: green; }
.h.svelte-xyz ~ .h:where(.svelte-xyz) { color: green; }
.i.svelte-xyz ~ .i:where(.svelte-xyz) { color: green; }
/* wrap around self ~ next */
.e.svelte-xyz ~ .e.svelte-xyz ~ .f.svelte-xyz { color: green; }
.e.svelte-xyz ~ .e.svelte-xyz ~ .d.svelte-xyz { color: green; }
.h.svelte-xyz ~ .h.svelte-xyz ~ .i.svelte-xyz { color: green; }
.h.svelte-xyz ~ .h.svelte-xyz ~ .g.svelte-xyz { color: green; }
.e.svelte-xyz ~ .e:where(.svelte-xyz) ~ .f:where(.svelte-xyz) { color: green; }
.e.svelte-xyz ~ .e:where(.svelte-xyz) ~ .d:where(.svelte-xyz) { color: green; }
.h.svelte-xyz ~ .h:where(.svelte-xyz) ~ .i:where(.svelte-xyz) { color: green; }
.h.svelte-xyz ~ .h:where(.svelte-xyz) ~ .g:where(.svelte-xyz) { color: green; }
/* general siblings */
.a.svelte-xyz ~ .h.svelte-xyz.svelte-xyz { color: green; }
.a.svelte-xyz ~ .i.svelte-xyz.svelte-xyz { color: green; }
.c.svelte-xyz ~ .h.svelte-xyz.svelte-xyz { color: green; }
.c.svelte-xyz ~ .i.svelte-xyz.svelte-xyz { color: green; }
.d.svelte-xyz ~ .f.svelte-xyz.svelte-xyz { color: green; }
.d.svelte-xyz ~ .g.svelte-xyz.svelte-xyz { color: green; }
.e.svelte-xyz ~ .g.svelte-xyz.svelte-xyz { color: green; }
.g.svelte-xyz ~ .i.svelte-xyz.svelte-xyz { color: green; }
.a.svelte-xyz ~ .h:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .i:where(.svelte-xyz) { color: green; }
.c.svelte-xyz ~ .h:where(.svelte-xyz) { color: green; }
.c.svelte-xyz ~ .i:where(.svelte-xyz) { color: green; }
.d.svelte-xyz ~ .f:where(.svelte-xyz) { color: green; }
.d.svelte-xyz ~ .g:where(.svelte-xyz) { color: green; }
.e.svelte-xyz ~ .g:where(.svelte-xyz) { color: green; }
.g.svelte-xyz ~ .i:where(.svelte-xyz) { color: green; }

@ -1,3 +1,3 @@
div.svelte-xyz ~ span.svelte-xyz {
div.svelte-xyz ~ span:where(.svelte-xyz) {
color: green;
}

@ -1,3 +1,3 @@
h1.svelte-xyz ~ p.svelte-xyz {
h1.svelte-xyz ~ p:where(.svelte-xyz) {
color: red;
}

@ -1,12 +1,12 @@
.a.svelte-xyz ~ .b.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.a.svelte-xyz ~ .c.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.a.svelte-xyz ~ .d.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.b.svelte-xyz ~ .d.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.c.svelte-xyz ~ .d.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
.a.svelte-xyz ~ .b:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .c:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
.b.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
.c.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .c.svelte-xyz ~ .c.svelte-xyz.svelte-xyz { color: green; }
.c.svelte-xyz ~ .c.svelte-xyz ~ .d.svelte-xyz.svelte-xyz { color: green; }
.a.svelte-xyz ~ .c.svelte-xyz ~ .c.svelte-xyz ~ .d.svelte-xyz { color: green; }
.a.svelte-xyz ~ .c:where(.svelte-xyz) ~ .c:where(.svelte-xyz) { color: green; }
.c.svelte-xyz ~ .c:where(.svelte-xyz) ~ .d:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .c:where(.svelte-xyz) ~ .c:where(.svelte-xyz) ~ .d:where(.svelte-xyz) { color: green; }
/* no match */
/* (unused) .b ~ .c { color: green; }*/

@ -1,8 +1,8 @@
.a.svelte-xyz ~ .b.svelte-xyz { color: green; }
.a.svelte-xyz ~ .c.svelte-xyz { color: green; }
.a.svelte-xyz ~ .d.svelte-xyz { color: green; }
.b.svelte-xyz ~ .d.svelte-xyz { color: green; }
.c.svelte-xyz ~ .d.svelte-xyz { color: green; }
.a.svelte-xyz ~ .b:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .c:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
.b.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
.c.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
/* no match */
/* (unused) .b ~ .c { color: green; }*/

@ -1,10 +1,10 @@
.a.svelte-xyz ~ .b.svelte-xyz { color: green; }
.a.svelte-xyz ~ .c.svelte-xyz { color: green; }
.a.svelte-xyz ~ .d.svelte-xyz { color: green; }
.b.svelte-xyz ~ .e.svelte-xyz { color: green; }
.c.svelte-xyz ~ .e.svelte-xyz { color: green; }
.d.svelte-xyz ~ .e.svelte-xyz { color: green; }
.a.svelte-xyz ~ .e.svelte-xyz { color: green; }
.a.svelte-xyz ~ .b:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .c:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
.b.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
.c.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
.d.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
/* no match */
/* (unused) .b ~ .c { color: green; }*/

@ -1,3 +1,3 @@
h1.svelte-xyz ~ p.svelte-xyz {
h1.svelte-xyz ~ p:where(.svelte-xyz) {
color: red;
}

@ -1,3 +1,3 @@
h1.svelte-xyz ~ p.svelte-xyz {
h1.svelte-xyz ~ p:where(.svelte-xyz) {
color: red;
}

@ -1,3 +1,3 @@
h1.svelte-xyz ~ p.svelte-xyz {
h1.svelte-xyz ~ p:where(.svelte-xyz) {
color: red;
}

@ -1,5 +1,5 @@
.d.svelte-xyz ~ .e.svelte-xyz { color: green; }
.a.svelte-xyz ~ .g.svelte-xyz { color: green; }
.d.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .g:where(.svelte-xyz) { color: green; }
/* no match */
/* (unused) .a ~ .b { color: green; }*/

@ -1,11 +1,11 @@
h1.svelte-xyz ~ span.svelte-xyz {
h1.svelte-xyz ~ span:where(.svelte-xyz) {
color: green;
}
h1.svelte-xyz ~ p.svelte-xyz {
h1.svelte-xyz ~ p:where(.svelte-xyz) {
color: red;
}
span.svelte-xyz ~ p.svelte-xyz {
span.svelte-xyz ~ p:where(.svelte-xyz) {
color: blue;
}

@ -1,4 +1,4 @@
.match.svelte-xyz > .svelte-xyz ~ .svelte-xyz {
.match.svelte-xyz > :where(.svelte-xyz) ~ :where(.svelte-xyz) {
margin-left: 4px;
}
/* (unused) .not-match > * ~ * {

@ -1,11 +1,11 @@
div.svelte-xyz ~ article.svelte-xyz.svelte-xyz { color: green; }
span.svelte-xyz ~ b.svelte-xyz.svelte-xyz { color: green; }
div.svelte-xyz span.svelte-xyz ~ b.svelte-xyz { color: green; }
.a.svelte-xyz ~ article.svelte-xyz.svelte-xyz { color: green; }
div.svelte-xyz ~ .b.svelte-xyz.svelte-xyz { color: green; }
.a.svelte-xyz ~ .c.svelte-xyz.svelte-xyz { color: green; }
article.svelte-xyz ~ details.svelte-xyz.svelte-xyz { color: green; }
.a.svelte-xyz ~ details.svelte-xyz.svelte-xyz { color: green; }
div.svelte-xyz ~ article:where(.svelte-xyz) { color: green; }
span.svelte-xyz ~ b:where(.svelte-xyz) { color: green; }
div.svelte-xyz span:where(.svelte-xyz) ~ b:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ article:where(.svelte-xyz) { color: green; }
div.svelte-xyz ~ .b:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ .c:where(.svelte-xyz) { color: green; }
article.svelte-xyz ~ details:where(.svelte-xyz) { color: green; }
.a.svelte-xyz ~ details:where(.svelte-xyz) { color: green; }
/* no match */
/* (unused) article ~ div { color: green; }*/

@ -1,3 +1,3 @@
html body .root.svelte-xyz p.svelte-xyz {
html body .root.svelte-xyz p:where(.svelte-xyz) {
color: red;
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save