mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Handle variants in utility selectors using :where() and :has() (#9309)
* Replaces classes in utility selectors like :where and :has * Update changelog * wip
This commit is contained in:
parent
f92b31b12f
commit
01f928d6de
@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Handle variants on complex selector utilities ([#9262](https://github.com/tailwindlabs/tailwindcss/pull/9262))
|
||||
- Don't mutate shared config objects ([#9294](https://github.com/tailwindlabs/tailwindcss/pull/9294))
|
||||
- Fix ordering of parallel variants ([#9282](https://github.com/tailwindlabs/tailwindcss/pull/9282))
|
||||
- Handle variants in utility selectors using `:where()` and `:has()` ([#9309](https://github.com/tailwindlabs/tailwindcss/pull/9309))
|
||||
|
||||
## [3.1.8] - 2022-08-05
|
||||
|
||||
|
||||
@ -81,6 +81,29 @@ function resortSelector(sel) {
|
||||
return sel
|
||||
}
|
||||
|
||||
function eliminateIrrelevantSelectors(sel, base) {
|
||||
let hasClassesMatchingCandidate = false
|
||||
|
||||
sel.walk((child) => {
|
||||
if (child.type === 'class' && child.value === base) {
|
||||
hasClassesMatchingCandidate = true
|
||||
return false // Stop walking
|
||||
}
|
||||
})
|
||||
|
||||
if (!hasClassesMatchingCandidate) {
|
||||
sel.remove()
|
||||
}
|
||||
|
||||
// We do NOT recursively eliminate sub selectors that don't have the base class
|
||||
// as this is NOT a safe operation. For example, if we have:
|
||||
// `.space-x-2 > :not([hidden]) ~ :not([hidden])`
|
||||
// We cannot remove the [hidden] from the :not() because it would change the
|
||||
// meaning of the selector.
|
||||
|
||||
// TODO: Can we do this for :matches, :is, and :where?
|
||||
}
|
||||
|
||||
export function finalizeSelector(
|
||||
format,
|
||||
{
|
||||
@ -115,13 +138,7 @@ export function finalizeSelector(
|
||||
// Remove extraneous selectors that do not include the base class/candidate being matched against
|
||||
// For example if we have a utility defined `.a, .b { color: red}`
|
||||
// And the formatted variant is sm:b then we want the final selector to be `.sm\:b` and not `.a, .sm\:b`
|
||||
ast.each((node) => {
|
||||
let hasClassesMatchingCandidate = node.some((n) => n.type === 'class' && n.value === base)
|
||||
|
||||
if (!hasClassesMatchingCandidate) {
|
||||
node.remove()
|
||||
}
|
||||
})
|
||||
ast.each((sel) => eliminateIrrelevantSelectors(sel, base))
|
||||
|
||||
// Normalize escaped classes, e.g.:
|
||||
//
|
||||
|
||||
@ -944,3 +944,88 @@ test('multi-class utilities handle selector-mutating variants correctly', () =>
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('class inside pseudo-class function :has', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{ raw: html`<div class="foo hover:foo sm:foo"></div>` },
|
||||
{ raw: html`<div class="bar hover:bar sm:bar"></div>` },
|
||||
{ raw: html`<div class="baz hover:baz sm:baz"></div>` },
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
@layer utilities {
|
||||
:where(.foo) {
|
||||
color: red;
|
||||
}
|
||||
:matches(.foo, .bar, .baz) {
|
||||
color: orange;
|
||||
}
|
||||
:is(.foo) {
|
||||
color: yellow;
|
||||
}
|
||||
html:has(.foo) {
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
:where(.foo) {
|
||||
color: red;
|
||||
}
|
||||
:matches(.foo, .bar, .baz) {
|
||||
color: orange;
|
||||
}
|
||||
:is(.foo) {
|
||||
color: yellow;
|
||||
}
|
||||
html:has(.foo) {
|
||||
color: green;
|
||||
}
|
||||
|
||||
:where(.hover\:foo:hover) {
|
||||
color: red;
|
||||
}
|
||||
:matches(.hover\:foo:hover, .bar, .baz) {
|
||||
color: orange;
|
||||
}
|
||||
:matches(.foo, .hover\:bar:hover, .baz) {
|
||||
color: orange;
|
||||
}
|
||||
:matches(.foo, .bar, .hover\:baz:hover) {
|
||||
color: orange;
|
||||
}
|
||||
:is(.hover\:foo:hover) {
|
||||
color: yellow;
|
||||
}
|
||||
html:has(.hover\:foo:hover) {
|
||||
color: green;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
:where(.sm\:foo) {
|
||||
color: red;
|
||||
}
|
||||
:matches(.sm\:foo, .bar, .baz) {
|
||||
color: orange;
|
||||
}
|
||||
:matches(.foo, .sm\:bar, .baz) {
|
||||
color: orange;
|
||||
}
|
||||
:matches(.foo, .bar, .sm\:baz) {
|
||||
color: orange;
|
||||
}
|
||||
:is(.sm\:foo) {
|
||||
color: yellow;
|
||||
}
|
||||
html:has(.sm\:foo) {
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user