Skip to content

Commit 6ab2893

Browse files
Throw out compound variants using variants with nested selectors (#14018)
1 parent 7052555 commit 6ab2893

File tree

3 files changed

+105
-44
lines changed

3 files changed

+105
-44
lines changed

packages/tailwindcss/src/index.test.ts

Lines changed: 15 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1812,50 +1812,25 @@ describe('@variant', () => {
18121812
`)
18131813
})
18141814

1815-
test('combining multiple complex variants', () => {
1816-
let compiled = compile(css`
1817-
@variant one {
1818-
&.foo-1 {
1819-
&.bar-1 {
1820-
@slot;
1821-
}
1822-
}
1823-
}
1815+
test('at-rule-only variants cannot be used with compound variants', () => {
1816+
expect(
1817+
compileCss(
1818+
css`
1819+
@variant foo (@media foo);
18241820
1825-
@variant two {
1826-
&.foo-2 {
1827-
&.bar-2 {
1828-
@slot;
1821+
@layer utilities {
1822+
@tailwind utilities;
18291823
}
1830-
}
1831-
}
1832-
1833-
@layer utilities {
1834-
@tailwind utilities;
1835-
}
1836-
`).build([
1837-
'group-one:two:underline',
1838-
'one:group-two:underline',
1839-
'peer-one:two:underline',
1840-
'one:peer-two:underline',
1841-
])
1824+
`,
18421825

1843-
expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(`
1826+
['foo:flex', 'group-foo:flex', 'peer-foo:flex', 'has-foo:flex', 'not-foo:flex'],
1827+
),
1828+
).toMatchInlineSnapshot(`
18441829
"@layer utilities {
1845-
.one\\:group-two\\:underline.foo-1.bar-1:is(:where(.group).foo-2 *):is(:where(.group).bar-2 *) {
1846-
text-decoration-line: underline;
1847-
}
1848-
1849-
.one\\:peer-two\\:underline.foo-1.bar-1:is(:where(.peer).foo-2 ~ *):is(:where(.peer).bar-2 ~ *) {
1850-
text-decoration-line: underline;
1851-
}
1852-
1853-
.group-one\\:two\\:underline:is(:where(.group).foo-1 *):is(:where(.group).bar-1 *).foo-2.bar-2 {
1854-
text-decoration-line: underline;
1855-
}
1856-
1857-
.peer-one\\:two\\:underline:is(:where(.peer).foo-1 ~ *):is(:where(.peer).bar-1 ~ *).foo-2.bar-2 {
1858-
text-decoration-line: underline;
1830+
@media foo {
1831+
.foo\\:flex {
1832+
display: flex;
1833+
}
18591834
}
18601835
}"
18611836
`)

packages/tailwindcss/src/variants.test.ts

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -783,9 +783,16 @@ test('group-*', () => {
783783
compileCss(
784784
css`
785785
@variant custom-at-rule (@media foo);
786+
@variant nested-selectors {
787+
&:hover {
788+
&:focus {
789+
@slot;
790+
}
791+
}
792+
}
786793
@tailwind utilities;
787794
`,
788-
['group-custom-at-rule:flex'],
795+
['group-custom-at-rule:flex', 'group-nested-selectors:flex'],
789796
),
790797
).toEqual('')
791798
})
@@ -877,9 +884,16 @@ test('peer-*', () => {
877884
compileCss(
878885
css`
879886
@variant custom-at-rule (@media foo);
887+
@variant nested-selectors {
888+
&:hover {
889+
&:focus {
890+
@slot;
891+
}
892+
}
893+
}
880894
@tailwind utilities;
881895
`,
882-
['peer-custom-at-rule:flex'],
896+
['peer-custom-at-rule:flex', 'peer-nested-selectors:flex'],
883897
),
884898
).toEqual('')
885899
})
@@ -1616,9 +1630,21 @@ test('not', () => {
16161630
compileCss(
16171631
css`
16181632
@variant custom-at-rule (@media foo);
1633+
@variant nested-selectors {
1634+
&:hover {
1635+
&:focus {
1636+
@slot;
1637+
}
1638+
}
1639+
}
16191640
@tailwind utilities;
16201641
`,
1621-
['not-[:checked]/foo:flex', 'not-[@media_print]:flex', 'not-custom-at-rule:flex'],
1642+
[
1643+
'not-[:checked]/foo:flex',
1644+
'not-[@media_print]:flex',
1645+
'not-custom-at-rule:flex',
1646+
'not-nested-selectors:flex',
1647+
],
16221648
),
16231649
).toEqual('')
16241650
})
@@ -1706,9 +1732,21 @@ test('has', () => {
17061732
compileCss(
17071733
css`
17081734
@variant custom-at-rule (@media foo);
1735+
@variant nested-selectors {
1736+
&:hover {
1737+
&:focus {
1738+
@slot;
1739+
}
1740+
}
1741+
}
17091742
@tailwind utilities;
17101743
`,
1711-
['has-[:checked]/foo:flex', 'has-[@media_print]:flex', 'has-custom-at-rule:flex'],
1744+
[
1745+
'has-[:checked]/foo:flex',
1746+
'has-[@media_print]:flex',
1747+
'has-custom-at-rule:flex',
1748+
'has-nested-selectors:flex',
1749+
],
17121750
),
17131751
).toEqual('')
17141752
})

packages/tailwindcss/src/variants.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,18 @@ export function createVariants(theme: Theme): Variants {
214214
// Skip past at-rules, and continue traversing the children of the at-rule
215215
if (node.selector[0] === '@') return WalkAction.Continue
216216

217+
// Throw out any candidates with variants using nested selectors
218+
if (didApply) {
219+
walk([node], (childNode) => {
220+
if (childNode.kind !== 'rule' || childNode.selector[0] === '@') return WalkAction.Continue
221+
222+
didApply = false
223+
return WalkAction.Stop
224+
})
225+
226+
return didApply ? WalkAction.Skip : WalkAction.Stop
227+
}
228+
217229
// Replace `&` in target variant with `*`, so variants like `&:hover`
218230
// become `&:not(*:hover)`. The `*` will often be optimized away.
219231
node.selector = `&:not(${node.selector.replaceAll('&', '*')})`
@@ -244,6 +256,18 @@ export function createVariants(theme: Theme): Variants {
244256
// Skip past at-rules, and continue traversing the children of the at-rule
245257
if (node.selector[0] === '@') return WalkAction.Continue
246258

259+
// Throw out any candidates with variants using nested selectors
260+
if (didApply) {
261+
walk([node], (childNode) => {
262+
if (childNode.kind !== 'rule' || childNode.selector[0] === '@') return WalkAction.Continue
263+
264+
didApply = false
265+
return WalkAction.Stop
266+
})
267+
268+
return didApply ? WalkAction.Skip : WalkAction.Stop
269+
}
270+
247271
// For most variants we rely entirely on CSS nesting to build-up the final
248272
// selector, but there is no way to use CSS nesting to make `&` refer to
249273
// just the `.group` class the way we'd need to for these variants, so we
@@ -291,6 +315,18 @@ export function createVariants(theme: Theme): Variants {
291315
// Skip past at-rules, and continue traversing the children of the at-rule
292316
if (node.selector[0] === '@') return WalkAction.Continue
293317

318+
// Throw out any candidates with variants using nested selectors
319+
if (didApply) {
320+
walk([node], (childNode) => {
321+
if (childNode.kind !== 'rule' || childNode.selector[0] === '@') return WalkAction.Continue
322+
323+
didApply = false
324+
return WalkAction.Stop
325+
})
326+
327+
return didApply ? WalkAction.Skip : WalkAction.Stop
328+
}
329+
294330
// For most variants we rely entirely on CSS nesting to build-up the final
295331
// selector, but there is no way to use CSS nesting to make `&` refer to
296332
// just the `.group` class the way we'd need to for these variants, so we
@@ -440,6 +476,18 @@ export function createVariants(theme: Theme): Variants {
440476
// Skip past at-rules, and continue traversing the children of the at-rule
441477
if (node.selector[0] === '@') return WalkAction.Continue
442478

479+
// Throw out any candidates with variants using nested selectors
480+
if (didApply) {
481+
walk([node], (childNode) => {
482+
if (childNode.kind !== 'rule' || childNode.selector[0] === '@') return WalkAction.Continue
483+
484+
didApply = false
485+
return WalkAction.Stop
486+
})
487+
488+
return didApply ? WalkAction.Skip : WalkAction.Stop
489+
}
490+
443491
// Replace `&` in target variant with `*`, so variants like `&:hover`
444492
// become `&:has(*:hover)`. The `*` will often be optimized away.
445493
node.selector = `&:has(${node.selector.replaceAll('&', '*')})`

0 commit comments

Comments
 (0)