Skip to content

Commit 51b3bc6

Browse files
authored
is-pseudo : handle more complex selector patterns (#923)
* is-pseudo : handle more complex selector patterns * clarify
1 parent 62a8134 commit 51b3bc6

18 files changed

+423
-35
lines changed

plugins/postcss-is-pseudo-class/.tape.mjs

+7-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ postcssTape(plugin)({
1919
},
2020
'basic:oncomplex:warning': {
2121
message: "warns on complex selectors",
22-
warnings: 10,
22+
warnings: 8,
2323
options: {
2424
onComplexSelector: 'warning'
2525
}
@@ -42,6 +42,12 @@ postcssTape(plugin)({
4242
preserve: false
4343
}
4444
},
45+
'compound-after-complex-is': {
46+
message: "can handle compound selectors after complex selectors in :is()",
47+
options: {
48+
preserve: false
49+
}
50+
},
4551
'complex': {
4652
message: "supports complex selectors",
4753
options: {

plugins/postcss-is-pseudo-class/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changes to PostCSS Is Pseudo Class
22

3+
### Unreleased (minor)
4+
5+
- Add support for more complex selector patterns. In particular anything where `:is()` is in the left-most compound selector.
6+
37
### 3.1.1 (February 8, 2023)
48

59
- Reduce the amount of duplicate fallback CSS.

plugins/postcss-is-pseudo-class/dist/index.cjs

+1-1
Large diffs are not rendered by default.

plugins/postcss-is-pseudo-class/dist/index.mjs

+1-1
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export declare function isPseudoInFirstCompound(selector: any): boolean;

plugins/postcss-is-pseudo-class/src/split-selectors/complex.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { sortCompoundSelectorsInsideComplexSelector } from './compound-selector-
33
import { childAdjacentChild } from './complex/child-adjacent-child';
44
import { isInCompoundWithOneOtherElement } from './complex/is-in-compound';
55
import type { Container } from 'postcss-selector-parser';
6+
import { isPseudoInFirstCompound } from './complex/is-pseudo-in-first-compound';
67

78
export default function complexSelectors(selectors: Array<string>, pluginOptions: { onComplexSelector?: 'warning' }, warnOnComplexSelector: () => void, warnOnPseudoElements: () => void) {
89
return selectors.flatMap((selector) => {
@@ -80,7 +81,8 @@ export default function complexSelectors(selectors: Array<string>, pluginOptions
8081

8182
if (
8283
childAdjacentChild(pseudo.parent) ||
83-
isInCompoundWithOneOtherElement(pseudo.parent)
84+
isInCompoundWithOneOtherElement(pseudo.parent) ||
85+
isPseudoInFirstCompound(pseudo.parent)
8486
) {
8587
return;
8688
}

plugins/postcss-is-pseudo-class/src/split-selectors/complex/child-adjacent-child.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
// :-csstools-matches(.a > .b) + :-csstools-matches(.c > .d)
22
// equivalent to
33
// .a.c > .b + .d
4+
//
5+
// and
6+
//
7+
// :-csstools-matches(.a > .b) ~ :-csstools-matches(.c > .d)
8+
// equivalent to
9+
// .a.c ~ .b + .d
10+
//
411
// because adjacent elements have the same parent element.
512
export function childAdjacentChild(selector): boolean {
613
if (!selector || !selector.nodes) {
@@ -19,7 +26,7 @@ export function childAdjacentChild(selector): boolean {
1926
}
2027

2128
// adjacent combinator
22-
if (!selector.nodes[1] || selector.nodes[1].type !== 'combinator' || selector.nodes[1].value !== '+') {
29+
if (!selector.nodes[1] || selector.nodes[1].type !== 'combinator' || (selector.nodes[1].value !== '+' && selector.nodes[1].value !== '~')) {
2330
return false;
2431
}
2532

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// :-csstools-matches(.a > .b) > .c
2+
// equivalent to
3+
// .a > .b > .c
4+
// because `:is()` is in the left-most compound selector
5+
export function isPseudoInFirstCompound(selector): boolean {
6+
if (!selector || !selector.nodes) {
7+
return false;
8+
}
9+
if (selector.type !== 'selector') {
10+
return false;
11+
}
12+
13+
let isPseudoIndex = -1;
14+
for (let i = 0; i < selector.nodes.length; i++) {
15+
const node = selector.nodes[i];
16+
if (node.type === 'combinator') {
17+
return false;
18+
}
19+
20+
if (node.type === 'pseudo' && node.value === ':-csstools-matches') {
21+
if (!node.nodes || node.nodes.length !== 1) {
22+
return false;
23+
}
24+
25+
isPseudoIndex = i;
26+
break;
27+
}
28+
}
29+
30+
if (isPseudoIndex === -1) {
31+
return false;
32+
}
33+
34+
const before = selector.nodes.slice(0, isPseudoIndex);
35+
const isPseudo = selector.nodes[isPseudoIndex];
36+
const after = selector.nodes.slice(isPseudoIndex + 1);
37+
38+
before.forEach((node) => {
39+
isPseudo.nodes[0].append(node.clone());
40+
});
41+
42+
after.forEach((node) => {
43+
isPseudo.nodes[0].append(node.clone());
44+
});
45+
46+
isPseudo.replaceWith(...isPseudo.nodes);
47+
48+
before.forEach((node) => {
49+
node.remove();
50+
});
51+
52+
after.forEach((node) => {
53+
node.remove();
54+
});
55+
56+
return true;
57+
}

plugins/postcss-is-pseudo-class/src/split-selectors/compound-selector-order.ts

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import parser from 'postcss-selector-parser';
22

33
export function sortCompoundSelectorsInsideComplexSelector(node) {
4-
if (!node || !node.nodes) {
4+
if (!node || !node.nodes || node.nodes.length === 1) {
55
return;
66
}
77

@@ -61,8 +61,27 @@ export function sortCompoundSelectorsInsideComplexSelector(node) {
6161
}
6262

6363
for (let i = sortedCompoundSelectors.length - 1; i >= 0; i--) {
64+
const previous = sortedCompoundSelectors[i - 1];
65+
6466
sortedCompoundSelectors[i].remove();
65-
node.prepend(sortedCompoundSelectors[i]);
67+
68+
if (previous && previous.type === 'tag' && sortedCompoundSelectors[i].type === 'tag') {
69+
const wrapped = parser.pseudo({
70+
value: ':is',
71+
nodes: [
72+
parser.selector({
73+
value: '',
74+
nodes: [
75+
sortedCompoundSelectors[i],
76+
],
77+
}),
78+
],
79+
});
80+
81+
node.prepend(wrapped);
82+
} else {
83+
node.prepend(sortedCompoundSelectors[i]);
84+
}
6685
}
6786
}
6887

plugins/postcss-is-pseudo-class/test/basic.css

+4
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ foo[baz=":is(.some, .other)"], .ok {
142142
order: 36;
143143
}
144144

145+
:is(input, button):is(select, textarea) {
146+
order: 37;
147+
}
148+
145149
:is(input, button):is(:hover, :focus) {
146150
to-clone: 1;
147151
}

plugins/postcss-is-pseudo-class/test/basic.does-not-exist.expect.css

+22-6
Original file line numberDiff line numberDiff line change
@@ -74,31 +74,31 @@ foo[baz=":is(.some, .other)"], .ok {
7474
order: 10;
7575
}
7676

77-
.pre:is(.alpha > .beta) + :is(:focus > .beta) {
77+
.alpha > .beta.pre + :is(:focus > .beta) {
7878
order: 11;
7979
}
8080

8181
.pre :is(.alpha > .beta)+ :is(:focus > .beta) {
8282
order: 12;
8383
}
8484

85-
.post:is(.alpha > .beta) + :is(:focus > .beta) {
85+
.alpha > .beta.post + :is(:focus > .beta) {
8686
order: 13;
8787
}
8888

89-
:is(.alpha > .beta) .post + :is(:focus > .beta) {
89+
.alpha > .beta .post + :is(:focus > .beta) {
9090
order: 14;
9191
}
9292

93-
:is(.alpha ~ .delta):focus > .beta + .beta {
93+
.alpha ~ .delta:focus > .beta + .beta {
9494
order: 15;
9595
}
9696

9797
.alpha:focus > .beta + .beta, .alpha:focus > .beta + .two, .one:focus > .two + .beta, .one:focus > .two + .two {
9898
order: 16;
9999
}
100100

101-
:is(.alpha > .beta) ~ :is(:focus > .beta) {
101+
.alpha:focus > .beta ~ .beta {
102102
order: 17;
103103
}
104104

@@ -110,7 +110,7 @@ foo[baz=":is(.some, .other)"], .ok {
110110
order: 19;
111111
}
112112

113-
.pre.alpha:is(.one > .two) {
113+
.one > .two.pre.alpha {
114114
order: 20;
115115
}
116116

@@ -238,6 +238,22 @@ ol:hover > li.foo, ol:hover > li.bar, ol:focus > li.foo, ol:focus > li.bar, ul:h
238238
order: 36;
239239
}
240240

241+
input:is(select) {
242+
order: 37;
243+
}
244+
245+
input:is(textarea) {
246+
order: 37;
247+
}
248+
249+
button:is(select) {
250+
order: 37;
251+
}
252+
253+
button:is(textarea) {
254+
order: 37;
255+
}
256+
241257
input:hover, input:focus, button:hover, button:focus {
242258
to-clone: 1;
243259
}

plugins/postcss-is-pseudo-class/test/basic.expect.css

+22-6
Original file line numberDiff line numberDiff line change
@@ -74,31 +74,31 @@ foo[baz=":is(.some, .other)"], .ok {
7474
order: 10;
7575
}
7676

77-
.pre:is(.alpha > .beta) + :is(:focus > .beta) {
77+
.alpha > .beta.pre + :is(:focus > .beta) {
7878
order: 11;
7979
}
8080

8181
.pre :is(.alpha > .beta)+ :is(:focus > .beta) {
8282
order: 12;
8383
}
8484

85-
.post:is(.alpha > .beta) + :is(:focus > .beta) {
85+
.alpha > .beta.post + :is(:focus > .beta) {
8686
order: 13;
8787
}
8888

89-
:is(.alpha > .beta) .post + :is(:focus > .beta) {
89+
.alpha > .beta .post + :is(:focus > .beta) {
9090
order: 14;
9191
}
9292

93-
:is(.alpha ~ .delta):focus > .beta + .beta {
93+
.alpha ~ .delta:focus > .beta + .beta {
9494
order: 15;
9595
}
9696

9797
.alpha:focus > .beta + .beta, .alpha:focus > .beta + .two, .one:focus > .two + .beta, .one:focus > .two + .two {
9898
order: 16;
9999
}
100100

101-
:is(.alpha > .beta) ~ :is(:focus > .beta) {
101+
.alpha:focus > .beta ~ .beta {
102102
order: 17;
103103
}
104104

@@ -110,7 +110,7 @@ foo[baz=":is(.some, .other)"], .ok {
110110
order: 19;
111111
}
112112

113-
.pre.alpha:is(.one > .two) {
113+
.one > .two.pre.alpha {
114114
order: 20;
115115
}
116116

@@ -238,6 +238,22 @@ ol:hover > li.foo, ol:hover > li.bar, ol:focus > li.foo, ol:focus > li.bar, ul:h
238238
order: 36;
239239
}
240240

241+
input:is(select) {
242+
order: 37;
243+
}
244+
245+
input:is(textarea) {
246+
order: 37;
247+
}
248+
249+
button:is(select) {
250+
order: 37;
251+
}
252+
253+
button:is(textarea) {
254+
order: 37;
255+
}
256+
241257
input:hover, input:focus, button:hover, button:focus {
242258
to-clone: 1;
243259
}

plugins/postcss-is-pseudo-class/test/basic.oncomplex.no-warning.expect.css

+22-6
Original file line numberDiff line numberDiff line change
@@ -74,31 +74,31 @@ foo[baz=":is(.some, .other)"], .ok {
7474
order: 10;
7575
}
7676

77-
.pre:is(.alpha > .beta) + :is(:focus > .beta) {
77+
.alpha > .beta.pre + :is(:focus > .beta) {
7878
order: 11;
7979
}
8080

8181
.pre :is(.alpha > .beta)+ :is(:focus > .beta) {
8282
order: 12;
8383
}
8484

85-
.post:is(.alpha > .beta) + :is(:focus > .beta) {
85+
.alpha > .beta.post + :is(:focus > .beta) {
8686
order: 13;
8787
}
8888

89-
:is(.alpha > .beta) .post + :is(:focus > .beta) {
89+
.alpha > .beta .post + :is(:focus > .beta) {
9090
order: 14;
9191
}
9292

93-
:is(.alpha ~ .delta):focus > .beta + .beta {
93+
.alpha ~ .delta:focus > .beta + .beta {
9494
order: 15;
9595
}
9696

9797
.alpha:focus > .beta + .beta, .alpha:focus > .beta + .two, .one:focus > .two + .beta, .one:focus > .two + .two {
9898
order: 16;
9999
}
100100

101-
:is(.alpha > .beta) ~ :is(:focus > .beta) {
101+
.alpha:focus > .beta ~ .beta {
102102
order: 17;
103103
}
104104

@@ -110,7 +110,7 @@ foo[baz=":is(.some, .other)"], .ok {
110110
order: 19;
111111
}
112112

113-
.pre.alpha:is(.one > .two) {
113+
.one > .two.pre.alpha {
114114
order: 20;
115115
}
116116

@@ -238,6 +238,22 @@ ol:hover > li.foo, ol:hover > li.bar, ol:focus > li.foo, ol:focus > li.bar, ul:h
238238
order: 36;
239239
}
240240

241+
input:is(select) {
242+
order: 37;
243+
}
244+
245+
input:is(textarea) {
246+
order: 37;
247+
}
248+
249+
button:is(select) {
250+
order: 37;
251+
}
252+
253+
button:is(textarea) {
254+
order: 37;
255+
}
256+
241257
input:hover, input:focus, button:hover, button:focus {
242258
to-clone: 1;
243259
}

0 commit comments

Comments
 (0)