diff --git a/.changeset/nasty-lions-double.md b/.changeset/nasty-lions-double.md
new file mode 100644
index 000000000000..404e08168d0c
--- /dev/null
+++ b/.changeset/nasty-lions-double.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: improve pseudo class parsing
diff --git a/.changeset/sweet-pens-sniff.md b/.changeset/sweet-pens-sniff.md
new file mode 100644
index 000000000000..165c1e05f7c5
--- /dev/null
+++ b/.changeset/sweet-pens-sniff.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: allow pseudo classes after `:global(..)`
diff --git a/packages/svelte/src/compiler/phases/1-parse/read/style.js b/packages/svelte/src/compiler/phases/1-parse/read/style.js
index 4d1777638dba..600c10f33a29 100644
--- a/packages/svelte/src/compiler/phases/1-parse/read/style.js
+++ b/packages/svelte/src/compiler/phases/1-parse/read/style.js
@@ -227,6 +227,12 @@ function read_selector(parser, inside_pseudo_class = false) {
start,
end: parser.index
});
+ // We read the inner selectors of a pseudo element to ensure it parses correctly,
+ // but we don't do anything with the result.
+ if (parser.eat('(')) {
+ read_selector_list(parser, true);
+ parser.eat(')', true);
+ }
} else if (parser.eat(':')) {
const name = read_identifier(parser);
diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/Selector.js b/packages/svelte/src/compiler/phases/2-analyze/css/Selector.js
index 2a8e4eff4037..b6796ff961a5 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/css/Selector.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/css/Selector.js
@@ -184,7 +184,9 @@ export default class Selector {
selector.name === 'global' &&
block.selectors.length !== 1 &&
(i === block.selectors.length - 1 ||
- block.selectors.slice(i + 1).some((s) => s.type !== 'PseudoElementSelector'))
+ block.selectors
+ .slice(i + 1)
+ .some((s) => s.type !== 'PseudoElementSelector' && s.type !== 'PseudoClassSelector'))
) {
error(selector, 'invalid-css-global-selector-list');
}
diff --git a/packages/svelte/tests/parser-modern/samples/css-pseudo-classes/input.svelte b/packages/svelte/tests/parser-modern/samples/css-pseudo-classes/input.svelte
new file mode 100644
index 000000000000..6602f9c04477
--- /dev/null
+++ b/packages/svelte/tests/parser-modern/samples/css-pseudo-classes/input.svelte
@@ -0,0 +1,18 @@
+
diff --git a/packages/svelte/tests/parser-modern/samples/css-pseudo-classes/output.json b/packages/svelte/tests/parser-modern/samples/css-pseudo-classes/output.json
new file mode 100644
index 000000000000..f502060e1365
--- /dev/null
+++ b/packages/svelte/tests/parser-modern/samples/css-pseudo-classes/output.json
@@ -0,0 +1,246 @@
+{
+ "css": {
+ "type": "Style",
+ "start": 0,
+ "end": 313,
+ "attributes": [],
+ "children": [
+ {
+ "type": "Rule",
+ "prelude": {
+ "type": "SelectorList",
+ "start": 60,
+ "end": 86,
+ "children": [
+ {
+ "type": "Selector",
+ "start": 60,
+ "end": 86,
+ "children": [
+ {
+ "type": "PseudoElementSelector",
+ "name": "view-transition-old",
+ "start": 60,
+ "end": 81
+ }
+ ]
+ }
+ ]
+ },
+ "block": {
+ "type": "Block",
+ "start": 88,
+ "end": 109,
+ "children": [
+ {
+ "type": "Declaration",
+ "start": 92,
+ "end": 102,
+ "property": "color",
+ "value": "red"
+ }
+ ]
+ },
+ "start": 60,
+ "end": 109
+ },
+ {
+ "type": "Rule",
+ "prelude": {
+ "type": "SelectorList",
+ "start": 111,
+ "end": 146,
+ "children": [
+ {
+ "type": "Selector",
+ "start": 111,
+ "end": 146,
+ "children": [
+ {
+ "type": "PseudoClassSelector",
+ "name": "global",
+ "args": {
+ "type": "SelectorList",
+ "start": 119,
+ "end": 145,
+ "children": [
+ {
+ "type": "Selector",
+ "start": 119,
+ "end": 145,
+ "children": [
+ {
+ "type": "PseudoElementSelector",
+ "name": "view-transition-old",
+ "start": 119,
+ "end": 140
+ }
+ ]
+ }
+ ]
+ },
+ "start": 111,
+ "end": 146
+ }
+ ]
+ }
+ ]
+ },
+ "block": {
+ "type": "Block",
+ "start": 148,
+ "end": 169,
+ "children": [
+ {
+ "type": "Declaration",
+ "start": 152,
+ "end": 162,
+ "property": "color",
+ "value": "red"
+ }
+ ]
+ },
+ "start": 111,
+ "end": 169
+ },
+ {
+ "type": "Rule",
+ "prelude": {
+ "type": "SelectorList",
+ "start": 171,
+ "end": 199,
+ "children": [
+ {
+ "type": "Selector",
+ "start": 171,
+ "end": 199,
+ "children": [
+ {
+ "type": "PseudoElementSelector",
+ "name": "highlight",
+ "start": 171,
+ "end": 182
+ }
+ ]
+ }
+ ]
+ },
+ "block": {
+ "type": "Block",
+ "start": 200,
+ "end": 218,
+ "children": [
+ {
+ "type": "Declaration",
+ "start": 204,
+ "end": 214,
+ "property": "color",
+ "value": "red"
+ }
+ ]
+ },
+ "start": 171,
+ "end": 218
+ },
+ {
+ "type": "Rule",
+ "prelude": {
+ "type": "SelectorList",
+ "start": 220,
+ "end": 245,
+ "children": [
+ {
+ "type": "Selector",
+ "start": 220,
+ "end": 245,
+ "children": [
+ {
+ "type": "TypeSelector",
+ "name": "custom-element",
+ "start": 220,
+ "end": 234
+ },
+ {
+ "type": "PseudoElementSelector",
+ "name": "part",
+ "start": 234,
+ "end": 240
+ }
+ ]
+ }
+ ]
+ },
+ "block": {
+ "type": "Block",
+ "start": 246,
+ "end": 264,
+ "children": [
+ {
+ "type": "Declaration",
+ "start": 250,
+ "end": 260,
+ "property": "color",
+ "value": "red"
+ }
+ ]
+ },
+ "start": 220,
+ "end": 264
+ },
+ {
+ "type": "Rule",
+ "prelude": {
+ "type": "SelectorList",
+ "start": 266,
+ "end": 285,
+ "children": [
+ {
+ "type": "Selector",
+ "start": 266,
+ "end": 285,
+ "children": [
+ {
+ "type": "PseudoElementSelector",
+ "name": "slotted",
+ "start": 266,
+ "end": 275
+ }
+ ]
+ }
+ ]
+ },
+ "block": {
+ "type": "Block",
+ "start": 286,
+ "end": 304,
+ "children": [
+ {
+ "type": "Declaration",
+ "start": 290,
+ "end": 300,
+ "property": "color",
+ "value": "red"
+ }
+ ]
+ },
+ "start": 266,
+ "end": 304
+ }
+ ],
+ "content": {
+ "start": 7,
+ "end": 305,
+ "styles": "\n /* test that all these are parsed correctly */\n\t::view-transition-old(x-y) {\n\t\tcolor: red;\n }\n\t:global(::view-transition-old(x-y)) {\n\t\tcolor: red;\n }\n\t::highlight(rainbow-color-1) {\n\t\tcolor: red;\n\t}\n\tcustom-element::part(foo) {\n\t\tcolor: red;\n\t}\n\t::slotted(.content) {\n\t\tcolor: red;\n\t}\n"
+ }
+ },
+ "js": [],
+ "start": null,
+ "end": null,
+ "type": "Root",
+ "fragment": {
+ "type": "Fragment",
+ "nodes": [],
+ "transparent": false
+ },
+ "options": null
+}
diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-placement-2/errors.json b/packages/svelte/tests/validator/samples/css-invalid-global-placement-2/errors.json
index 7ba32a53fb58..53e24d8d810b 100644
--- a/packages/svelte/tests/validator/samples/css-invalid-global-placement-2/errors.json
+++ b/packages/svelte/tests/validator/samples/css-invalid-global-placement-2/errors.json
@@ -3,11 +3,11 @@
"code": "invalid-css-global-placement",
"message": ":global(...) can be at the start or end of a selector sequence, but not in the middle",
"start": {
- "line": 2,
+ "line": 5,
"column": 6
},
"end": {
- "line": 2,
+ "line": 5,
"column": 19
}
}
diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-placement-2/input.svelte b/packages/svelte/tests/validator/samples/css-invalid-global-placement-2/input.svelte
index 75bd7b66e1d0..2c8e96a48486 100644
--- a/packages/svelte/tests/validator/samples/css-invalid-global-placement-2/input.svelte
+++ b/packages/svelte/tests/validator/samples/css-invalid-global-placement-2/input.svelte
@@ -1,4 +1,7 @@