mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Don’t allow at-rule-only variants to be compounded (#14015)
* Don’t allow at-rule-only variants to be compounded * Update changelog --------- Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
This commit is contained in:
parent
cf846a5ff6
commit
e8546df800
@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Ensure opacity modifier with variables work with `color-mix()` ([#13972](https://github.com/tailwindlabs/tailwindcss/pull/13972))
|
||||
- Discard invalid `variants` and `utilities` with modifiers ([#13977](https://github.com/tailwindlabs/tailwindcss/pull/13977))
|
||||
- Add missing utilities that exist in v3, such as `resize`, `fill-none`, `accent-none`, `drop-shadow-none`, and negative `hue-rotate` and `backdrop-hue-rotate` utilities ([#13971](https://github.com/tailwindlabs/tailwindcss/pull/13971))
|
||||
- Don’t allow at-rule-only variants to be compounded ([#14015](https://github.com/tailwindlabs/tailwindcss/pull/14015))
|
||||
|
||||
### Added
|
||||
|
||||
|
||||
@ -725,6 +725,15 @@ test('group-[...]', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
|
||||
expect(
|
||||
compileCss(
|
||||
css`
|
||||
@tailwind utilities;
|
||||
`,
|
||||
['group-[@media_foo]:flex'],
|
||||
),
|
||||
).toEqual('')
|
||||
})
|
||||
|
||||
test('group-*', () => {
|
||||
@ -752,6 +761,16 @@ test('group-*', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
|
||||
expect(
|
||||
compileCss(
|
||||
css`
|
||||
@variant custom-at-rule (@media foo);
|
||||
@tailwind utilities;
|
||||
`,
|
||||
['group-custom-at-rule:flex'],
|
||||
),
|
||||
).toEqual('')
|
||||
})
|
||||
|
||||
test('peer-[...]', () => {
|
||||
@ -784,6 +803,15 @@ test('peer-[...]', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
|
||||
expect(
|
||||
compileCss(
|
||||
css`
|
||||
@tailwind utilities;
|
||||
`,
|
||||
['peer-[@media_foo]:flex'],
|
||||
),
|
||||
).toEqual('')
|
||||
})
|
||||
|
||||
test('peer-*', () => {
|
||||
@ -811,6 +839,16 @@ test('peer-*', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
|
||||
expect(
|
||||
compileCss(
|
||||
css`
|
||||
@variant custom-at-rule (@media foo);
|
||||
@tailwind utilities;
|
||||
`,
|
||||
['peer-custom-at-rule:flex'],
|
||||
),
|
||||
).toEqual('')
|
||||
})
|
||||
|
||||
test('ltr', () => {
|
||||
@ -1505,7 +1543,15 @@ test('not', () => {
|
||||
}"
|
||||
`)
|
||||
|
||||
expect(run(['not-[:checked]/foo:flex'])).toEqual('')
|
||||
expect(
|
||||
compileCss(
|
||||
css`
|
||||
@variant custom-at-rule (@media foo);
|
||||
@tailwind utilities;
|
||||
`,
|
||||
['not-[:checked]/foo:flex', 'not-[@media_print]:flex', 'not-custom-at-rule:flex'],
|
||||
),
|
||||
).toEqual('')
|
||||
})
|
||||
|
||||
test('has', () => {
|
||||
@ -1518,7 +1564,7 @@ test('has', () => {
|
||||
'group-has-checked:flex',
|
||||
|
||||
'peer-has-[:checked]:flex',
|
||||
'peer-has-[:checked]/parent-name:flex',
|
||||
'peer-has-[:checked]/sibling-name:flex',
|
||||
'peer-has-checked:flex',
|
||||
]),
|
||||
).toMatchInlineSnapshot(`
|
||||
@ -1542,7 +1588,7 @@ test('has', () => {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.peer-has-\\[\\:checked\\]\\/parent-name\\:flex:is(:where(.peer\\/parent-name):has(:checked) ~ *) {
|
||||
.peer-has-\\[\\:checked\\]\\/sibling-name\\:flex:is(:where(.peer\\/sibling-name):has(:checked) ~ *) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@ -1550,7 +1596,16 @@ test('has', () => {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
expect(run(['has-[:checked]/foo:flex'])).toEqual('')
|
||||
|
||||
expect(
|
||||
compileCss(
|
||||
css`
|
||||
@variant custom-at-rule (@media foo);
|
||||
@tailwind utilities;
|
||||
`,
|
||||
['has-[:checked]/foo:flex', 'has-[@media_print]:flex', 'has-custom-at-rule:flex'],
|
||||
),
|
||||
).toEqual('')
|
||||
})
|
||||
|
||||
test('aria', () => {
|
||||
|
||||
@ -205,7 +205,28 @@ export function createVariants(theme: Theme): Variants {
|
||||
|
||||
variants.compound('not', (ruleNode, variant) => {
|
||||
if (variant.modifier) return null
|
||||
ruleNode.selector = `&:not(${ruleNode.selector.replace('&', '*')})`
|
||||
|
||||
let didApply = false
|
||||
|
||||
walk([ruleNode], (node) => {
|
||||
if (node.kind !== 'rule') return WalkAction.Continue
|
||||
|
||||
// Skip past at-rules, and continue traversing the children of the at-rule
|
||||
if (node.selector[0] === '@') return WalkAction.Continue
|
||||
|
||||
// Replace `&` in target variant with `*`, so variants like `&:hover`
|
||||
// become `&:not(*:hover)`. The `*` will often be optimized away.
|
||||
node.selector = `&:not(${node.selector.replace('&', '*')})`
|
||||
|
||||
// Track that the variant was actually applied
|
||||
didApply = true
|
||||
})
|
||||
|
||||
// If the node wasn't modified, this variant is not compatible with
|
||||
// `not-*` so discard the candidate.
|
||||
if (!didApply) {
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
variants.compound('group', (ruleNode, variant) => {
|
||||
@ -215,6 +236,8 @@ export function createVariants(theme: Theme): Variants {
|
||||
? `:where(.group\\/${variant.modifier.value})`
|
||||
: ':where(.group)'
|
||||
|
||||
let didApply = false
|
||||
|
||||
walk([ruleNode], (node) => {
|
||||
if (node.kind !== 'rule') return WalkAction.Continue
|
||||
|
||||
@ -234,10 +257,17 @@ export function createVariants(theme: Theme): Variants {
|
||||
node.selector = `:is(${node.selector})`
|
||||
}
|
||||
|
||||
// Use `:where` to make sure the specificity of group variants isn't higher
|
||||
// than the specificity of other variants.
|
||||
node.selector = `&:is(${node.selector} *)`
|
||||
|
||||
// Track that the variant was actually applied
|
||||
didApply = true
|
||||
})
|
||||
|
||||
// If the node wasn't modified, this variant is not compatible with
|
||||
// `group-*` so discard the candidate.
|
||||
if (!didApply) {
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
variants.suggest('group', () => {
|
||||
@ -253,6 +283,8 @@ export function createVariants(theme: Theme): Variants {
|
||||
? `:where(.peer\\/${variant.modifier.value})`
|
||||
: ':where(.peer)'
|
||||
|
||||
let didApply = false
|
||||
|
||||
walk([ruleNode], (node) => {
|
||||
if (node.kind !== 'rule') return WalkAction.Continue
|
||||
|
||||
@ -272,10 +304,17 @@ export function createVariants(theme: Theme): Variants {
|
||||
node.selector = `:is(${node.selector})`
|
||||
}
|
||||
|
||||
// Use `:where` to make sure the specificity of group variants isn't higher
|
||||
// than the specificity of other variants.
|
||||
node.selector = `&:is(${node.selector} ~ *)`
|
||||
|
||||
// Track that the variant was actually applied
|
||||
didApply = true
|
||||
})
|
||||
|
||||
// If the node wasn't modified, this variant is not compatible with
|
||||
// `peer-*` so discard the candidate.
|
||||
if (!didApply) {
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
variants.suggest('peer', () => {
|
||||
@ -392,7 +431,28 @@ export function createVariants(theme: Theme): Variants {
|
||||
|
||||
variants.compound('has', (ruleNode, variant) => {
|
||||
if (variant.modifier) return null
|
||||
ruleNode.selector = `&:has(${ruleNode.selector.replace('&', '*')})`
|
||||
|
||||
let didApply = false
|
||||
|
||||
walk([ruleNode], (node) => {
|
||||
if (node.kind !== 'rule') return WalkAction.Continue
|
||||
|
||||
// Skip past at-rules, and continue traversing the children of the at-rule
|
||||
if (node.selector[0] === '@') return WalkAction.Continue
|
||||
|
||||
// Replace `&` in target variant with `*`, so variants like `&:hover`
|
||||
// become `&:has(*:hover)`. The `*` will often be optimized away.
|
||||
node.selector = `&:has(${node.selector.replace('&', '*')})`
|
||||
|
||||
// Track that the variant was actually applied
|
||||
didApply = true
|
||||
})
|
||||
|
||||
// If the node wasn't modified, this variant is not compatible with
|
||||
// `has-*` so discard the candidate.
|
||||
if (!didApply) {
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
variants.suggest('has', () => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user