Skip to content

Commit c1f01be

Browse files
Move common, trailing pseudo elements when generating selectors (#281)
* Move common, trailing pseudo elements when generating selectors * Fix CS * Update dependencies * Update lockfile
1 parent 23e04c6 commit c1f01be

File tree

5 files changed

+279
-42
lines changed

5 files changed

+279
-42
lines changed

package-lock.json

Lines changed: 11 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@
4646
"dependencies": {
4747
"lodash.castarray": "^4.4.0",
4848
"lodash.isplainobject": "^4.0.6",
49-
"lodash.merge": "^4.6.2"
49+
"lodash.merge": "^4.6.2",
50+
"postcss-selector-parser": "6.0.10"
5051
},
5152
"jest": {
5253
"setupFilesAfterEnv": [

src/index.js

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const plugin = require('tailwindcss/plugin')
22
const merge = require('lodash.merge')
33
const castArray = require('lodash.castarray')
44
const styles = require('./styles')
5+
const { commonTrailingPseudos } = require('./utils')
56

67
const computed = {
78
// Reserved for future "magic properties", for example:
@@ -12,25 +13,11 @@ function inWhere(selector, { className, prefix }) {
1213
let prefixedNot = prefix(`.not-${className}`).slice(1)
1314
let selectorPrefix = selector.startsWith('>') ? `.${className} ` : ''
1415

15-
if (selector.endsWith('::before')) {
16-
return `:where(${selectorPrefix}${selector.slice(
17-
0,
18-
-8
19-
)}):not(:where([class~="${prefixedNot}"] *))::before`
20-
}
21-
22-
if (selector.endsWith('::after')) {
23-
return `:where(${selectorPrefix}${selector.slice(
24-
0,
25-
-7
26-
)}):not(:where([class~="${prefixedNot}"] *))::after`
27-
}
16+
// Parse the selector, if every component ends in the same pseudo element(s) then move it to the end
17+
let [trailingPseudo, rebuiltSelector] = commonTrailingPseudos(selector)
2818

29-
if (selector.endsWith('::marker')) {
30-
return `:where(${selectorPrefix}${selector.slice(
31-
0,
32-
-8
33-
)}):not(:where([class~="${prefixedNot}"] *))::marker`
19+
if (trailingPseudo) {
20+
return `:where(${selectorPrefix}${rebuiltSelector}):not(:where([class~="${prefixedNot}"] *))${trailingPseudo}`
3421
}
3522

3623
return `:where(${selectorPrefix}${selector}):not(:where([class~="${prefixedNot}"] *))`
@@ -118,11 +105,13 @@ module.exports = plugin.withOptions(
118105
]) {
119106
selectors = selectors.length === 0 ? [name] : selectors
120107

121-
let selector = target === 'legacy'
122-
? selectors.map(selector => `& ${selector}`)
123-
: selectors.join(', ')
108+
let selector =
109+
target === 'legacy' ? selectors.map((selector) => `& ${selector}`) : selectors.join(', ')
124110

125-
addVariant(`${className}-${name}`, target === 'legacy' ? selector : `& :is(${inWhere(selector, options)})`)
111+
addVariant(
112+
`${className}-${name}`,
113+
target === 'legacy' ? selector : `& :is(${inWhere(selector, options)})`
114+
)
126115
}
127116

128117
addComponents(

src/index.test.js

Lines changed: 200 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,9 @@ test('modifiers', async () => {
335335
test('legacy target', async () => {
336336
let config = {
337337
plugins: [typographyPlugin({ target: 'legacy' })],
338-
content: [{ raw: html`<div class="prose prose-h1:text-center prose-headings:text-ellipsis"></div>` }],
338+
content: [
339+
{ raw: html`<div class="prose prose-h1:text-center prose-headings:text-ellipsis"></div>` },
340+
],
339341
theme: {
340342
typography: {
341343
DEFAULT: {
@@ -712,7 +714,7 @@ test('element variants', async () => {
712714
.prose-hr\:border-t-2 :is(:where(hr):not(:where([class~='not-prose'] *))) {
713715
border-top-width: 2px;
714716
}
715-
.prose-lead\:italic :is(:where([class~="lead"]):not(:where([class~="not-prose"] *))) {
717+
.prose-lead\:italic :is(:where([class~='lead']):not(:where([class~='not-prose'] *))) {
716718
font-style: italic;
717719
}
718720
`
@@ -886,7 +888,7 @@ test('element variants with custom class name', async () => {
886888
.markdown-hr\:border-t-2 :is(:where(hr):not(:where([class~='not-markdown'] *))) {
887889
border-top-width: 2px;
888890
}
889-
.markdown-lead\:italic :is(:where([class~="lead"]):not(:where([class~="not-markdown"] *))) {
891+
.markdown-lead\:italic :is(:where([class~='lead']):not(:where([class~='not-markdown'] *))) {
890892
font-style: italic;
891893
}
892894
`
@@ -1000,3 +1002,198 @@ it('should be possible to specify custom h5 and h6 styles', () => {
10001002
`)
10011003
})
10021004
})
1005+
1006+
it('should not break with multiple selectors with pseudo elements using variants', () => {
1007+
let config = {
1008+
darkMode: 'class',
1009+
plugins: [typographyPlugin()],
1010+
content: [
1011+
{
1012+
raw: html`<div class="dark:prose"></div>`,
1013+
},
1014+
],
1015+
theme: {
1016+
typography: {
1017+
DEFAULT: {
1018+
css: {
1019+
'ol li::before, ul li::before': {
1020+
color: 'red',
1021+
},
1022+
},
1023+
},
1024+
},
1025+
},
1026+
}
1027+
1028+
return run(config).then((result) => {
1029+
expect(result.css).toIncludeCss(css`
1030+
.dark .dark\:prose :where(ol li, ul li):not(:where([class~='not-prose'] *))::before {
1031+
color: red;
1032+
}
1033+
`)
1034+
})
1035+
})
1036+
1037+
it('lifts all common, trailing pseudo elements when the same across all selectors', () => {
1038+
let config = {
1039+
darkMode: 'class',
1040+
plugins: [typographyPlugin()],
1041+
content: [
1042+
{
1043+
raw: html`<div class="prose dark:prose"></div>`,
1044+
},
1045+
],
1046+
theme: {
1047+
typography: {
1048+
DEFAULT: {
1049+
css: {
1050+
'ol li::marker::before, ul li::marker::before': {
1051+
color: 'red',
1052+
},
1053+
},
1054+
},
1055+
},
1056+
},
1057+
}
1058+
1059+
return run(config).then((result) => {
1060+
expect(result.css).toIncludeCss(css`
1061+
.prose :where(ol li, ul li):not(:where([class~='not-prose'] *))::marker::before {
1062+
color: red;
1063+
}
1064+
`)
1065+
1066+
// TODO: The output here is a bug in tailwindcss variant selector rewriting
1067+
// IT should be ::marker::before
1068+
expect(result.css).toIncludeCss(css`
1069+
.dark .dark\:prose :where(ol li, ul li):not(:where([class~='not-prose'] *))::before::marker {
1070+
color: red;
1071+
}
1072+
`)
1073+
})
1074+
})
1075+
1076+
it('does not modify selectors with differing pseudo elements', () => {
1077+
let config = {
1078+
darkMode: 'class',
1079+
plugins: [typographyPlugin()],
1080+
content: [
1081+
{
1082+
raw: html`<div class="prose dark:prose"></div>`,
1083+
},
1084+
],
1085+
theme: {
1086+
typography: {
1087+
DEFAULT: {
1088+
css: {
1089+
'ol li::before, ul li::after': {
1090+
color: 'red',
1091+
},
1092+
},
1093+
},
1094+
},
1095+
},
1096+
}
1097+
1098+
return run(config).then((result) => {
1099+
expect(result.css).toIncludeCss(css`
1100+
.prose :where(ol li::before, ul li::after):not(:where([class~='not-prose'] *)) {
1101+
color: red;
1102+
}
1103+
`)
1104+
1105+
// TODO: The output here is a bug in tailwindcss variant selector rewriting
1106+
expect(result.css).toIncludeCss(css`
1107+
.dark .dark\:prose :where(ol li, ul li):not(:where([class~='not-prose'] *))::before,
1108+
::after {
1109+
color: red;
1110+
}
1111+
`)
1112+
})
1113+
})
1114+
1115+
it('lifts only the common, trailing pseudo elements from selectors', () => {
1116+
let config = {
1117+
darkMode: 'class',
1118+
plugins: [typographyPlugin()],
1119+
content: [
1120+
{
1121+
raw: html`<div class="prose dark:prose"></div>`,
1122+
},
1123+
],
1124+
theme: {
1125+
typography: {
1126+
DEFAULT: {
1127+
css: {
1128+
'ol li::scroll-thumb::before, ul li::scroll-track::before': {
1129+
color: 'red',
1130+
},
1131+
},
1132+
},
1133+
},
1134+
},
1135+
}
1136+
1137+
return run(config).then((result) => {
1138+
expect(result.css).toIncludeCss(css`
1139+
.prose
1140+
:where(ol li::scroll-thumb, ul li::scroll-track):not(:where([class~='not-prose']
1141+
*))::before {
1142+
color: red;
1143+
}
1144+
`)
1145+
1146+
// TODO: The output here is a bug in tailwindcss variant selector rewriting
1147+
expect(result.css).toIncludeCss(css`
1148+
.dark .dark\:prose :where(ol li, ul li):not(:where([class~='not-prose'] *))::scroll-thumb,
1149+
::scroll-track,
1150+
::before {
1151+
color: red;
1152+
}
1153+
`)
1154+
})
1155+
})
1156+
1157+
it('ignores common non-trailing pseudo-elements in selectors', () => {
1158+
let config = {
1159+
darkMode: 'class',
1160+
plugins: [typographyPlugin()],
1161+
content: [
1162+
{
1163+
raw: html`<div class="prose dark:prose"></div>`,
1164+
},
1165+
],
1166+
theme: {
1167+
typography: {
1168+
DEFAULT: {
1169+
css: {
1170+
'ol li::before::scroll-thumb, ul li::before::scroll-track': {
1171+
color: 'red',
1172+
},
1173+
},
1174+
},
1175+
},
1176+
},
1177+
}
1178+
1179+
return run(config).then((result) => {
1180+
expect(result.css).toIncludeCss(css`
1181+
.prose
1182+
:where(ol li::before::scroll-thumb, ul
1183+
li::before::scroll-track):not(:where([class~='not-prose'] *)) {
1184+
color: red;
1185+
}
1186+
`)
1187+
1188+
// TODO: The output here is a bug in tailwindcss variant selector rewriting
1189+
expect(result.css).toIncludeCss(css`
1190+
.dark
1191+
.dark\:prose
1192+
:where(ol li::scroll-thumb, ul li::scroll-track):not(:where([class~='not-prose']
1193+
*))::before,
1194+
::before {
1195+
color: red;
1196+
}
1197+
`)
1198+
})
1199+
})

0 commit comments

Comments
 (0)