Skip to content

Commit e46a71e

Browse files
authored
fix: handle pseudo class elements with content (#10055)
closes #9398 (the other things in that issue are already addressed) closes #10019
1 parent 6d65b2f commit e46a71e

File tree

8 files changed

+288
-3
lines changed

8 files changed

+288
-3
lines changed

.changeset/nasty-lions-double.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: improve pseudo class parsing

.changeset/sweet-pens-sniff.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: allow pseudo classes after `:global(..)`

packages/svelte/src/compiler/phases/1-parse/read/style.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,12 @@ function read_selector(parser, inside_pseudo_class = false) {
227227
start,
228228
end: parser.index
229229
});
230+
// We read the inner selectors of a pseudo element to ensure it parses correctly,
231+
// but we don't do anything with the result.
232+
if (parser.eat('(')) {
233+
read_selector_list(parser, true);
234+
parser.eat(')', true);
235+
}
230236
} else if (parser.eat(':')) {
231237
const name = read_identifier(parser);
232238

packages/svelte/src/compiler/phases/2-analyze/css/Selector.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,9 @@ export default class Selector {
184184
selector.name === 'global' &&
185185
block.selectors.length !== 1 &&
186186
(i === block.selectors.length - 1 ||
187-
block.selectors.slice(i + 1).some((s) => s.type !== 'PseudoElementSelector'))
187+
block.selectors
188+
.slice(i + 1)
189+
.some((s) => s.type !== 'PseudoElementSelector' && s.type !== 'PseudoClassSelector'))
188190
) {
189191
error(selector, 'invalid-css-global-selector-list');
190192
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<style>
2+
/* test that all these are parsed correctly */
3+
::view-transition-old(x-y) {
4+
color: red;
5+
}
6+
:global(::view-transition-old(x-y)) {
7+
color: red;
8+
}
9+
::highlight(rainbow-color-1) {
10+
color: red;
11+
}
12+
custom-element::part(foo) {
13+
color: red;
14+
}
15+
::slotted(.content) {
16+
color: red;
17+
}
18+
</style>
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
{
2+
"css": {
3+
"type": "Style",
4+
"start": 0,
5+
"end": 313,
6+
"attributes": [],
7+
"children": [
8+
{
9+
"type": "Rule",
10+
"prelude": {
11+
"type": "SelectorList",
12+
"start": 60,
13+
"end": 86,
14+
"children": [
15+
{
16+
"type": "Selector",
17+
"start": 60,
18+
"end": 86,
19+
"children": [
20+
{
21+
"type": "PseudoElementSelector",
22+
"name": "view-transition-old",
23+
"start": 60,
24+
"end": 81
25+
}
26+
]
27+
}
28+
]
29+
},
30+
"block": {
31+
"type": "Block",
32+
"start": 88,
33+
"end": 109,
34+
"children": [
35+
{
36+
"type": "Declaration",
37+
"start": 92,
38+
"end": 102,
39+
"property": "color",
40+
"value": "red"
41+
}
42+
]
43+
},
44+
"start": 60,
45+
"end": 109
46+
},
47+
{
48+
"type": "Rule",
49+
"prelude": {
50+
"type": "SelectorList",
51+
"start": 111,
52+
"end": 146,
53+
"children": [
54+
{
55+
"type": "Selector",
56+
"start": 111,
57+
"end": 146,
58+
"children": [
59+
{
60+
"type": "PseudoClassSelector",
61+
"name": "global",
62+
"args": {
63+
"type": "SelectorList",
64+
"start": 119,
65+
"end": 145,
66+
"children": [
67+
{
68+
"type": "Selector",
69+
"start": 119,
70+
"end": 145,
71+
"children": [
72+
{
73+
"type": "PseudoElementSelector",
74+
"name": "view-transition-old",
75+
"start": 119,
76+
"end": 140
77+
}
78+
]
79+
}
80+
]
81+
},
82+
"start": 111,
83+
"end": 146
84+
}
85+
]
86+
}
87+
]
88+
},
89+
"block": {
90+
"type": "Block",
91+
"start": 148,
92+
"end": 169,
93+
"children": [
94+
{
95+
"type": "Declaration",
96+
"start": 152,
97+
"end": 162,
98+
"property": "color",
99+
"value": "red"
100+
}
101+
]
102+
},
103+
"start": 111,
104+
"end": 169
105+
},
106+
{
107+
"type": "Rule",
108+
"prelude": {
109+
"type": "SelectorList",
110+
"start": 171,
111+
"end": 199,
112+
"children": [
113+
{
114+
"type": "Selector",
115+
"start": 171,
116+
"end": 199,
117+
"children": [
118+
{
119+
"type": "PseudoElementSelector",
120+
"name": "highlight",
121+
"start": 171,
122+
"end": 182
123+
}
124+
]
125+
}
126+
]
127+
},
128+
"block": {
129+
"type": "Block",
130+
"start": 200,
131+
"end": 218,
132+
"children": [
133+
{
134+
"type": "Declaration",
135+
"start": 204,
136+
"end": 214,
137+
"property": "color",
138+
"value": "red"
139+
}
140+
]
141+
},
142+
"start": 171,
143+
"end": 218
144+
},
145+
{
146+
"type": "Rule",
147+
"prelude": {
148+
"type": "SelectorList",
149+
"start": 220,
150+
"end": 245,
151+
"children": [
152+
{
153+
"type": "Selector",
154+
"start": 220,
155+
"end": 245,
156+
"children": [
157+
{
158+
"type": "TypeSelector",
159+
"name": "custom-element",
160+
"start": 220,
161+
"end": 234
162+
},
163+
{
164+
"type": "PseudoElementSelector",
165+
"name": "part",
166+
"start": 234,
167+
"end": 240
168+
}
169+
]
170+
}
171+
]
172+
},
173+
"block": {
174+
"type": "Block",
175+
"start": 246,
176+
"end": 264,
177+
"children": [
178+
{
179+
"type": "Declaration",
180+
"start": 250,
181+
"end": 260,
182+
"property": "color",
183+
"value": "red"
184+
}
185+
]
186+
},
187+
"start": 220,
188+
"end": 264
189+
},
190+
{
191+
"type": "Rule",
192+
"prelude": {
193+
"type": "SelectorList",
194+
"start": 266,
195+
"end": 285,
196+
"children": [
197+
{
198+
"type": "Selector",
199+
"start": 266,
200+
"end": 285,
201+
"children": [
202+
{
203+
"type": "PseudoElementSelector",
204+
"name": "slotted",
205+
"start": 266,
206+
"end": 275
207+
}
208+
]
209+
}
210+
]
211+
},
212+
"block": {
213+
"type": "Block",
214+
"start": 286,
215+
"end": 304,
216+
"children": [
217+
{
218+
"type": "Declaration",
219+
"start": 290,
220+
"end": 300,
221+
"property": "color",
222+
"value": "red"
223+
}
224+
]
225+
},
226+
"start": 266,
227+
"end": 304
228+
}
229+
],
230+
"content": {
231+
"start": 7,
232+
"end": 305,
233+
"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"
234+
}
235+
},
236+
"js": [],
237+
"start": null,
238+
"end": null,
239+
"type": "Root",
240+
"fragment": {
241+
"type": "Fragment",
242+
"nodes": [],
243+
"transparent": false
244+
},
245+
"options": null
246+
}

packages/svelte/tests/validator/samples/css-invalid-global-placement-2/errors.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
"code": "invalid-css-global-placement",
44
"message": ":global(...) can be at the start or end of a selector sequence, but not in the middle",
55
"start": {
6-
"line": 2,
6+
"line": 5,
77
"column": 6
88
},
99
"end": {
10-
"line": 2,
10+
"line": 5,
1111
"column": 19
1212
}
1313
}

packages/svelte/tests/validator/samples/css-invalid-global-placement-2/input.svelte

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
<style>
2+
.foo :global(.bar):first-child {
3+
color: red;
4+
}
25
.foo :global(.bar):first-child .baz {
36
color: red;
47
}

0 commit comments

Comments
 (0)