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:
Jordan Pittman 2022-09-12 15:08:31 -04:00 committed by GitHub
parent f92b31b12f
commit 01f928d6de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 110 additions and 7 deletions

View File

@ -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

View File

@ -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.:
//

View File

@ -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;
}
}
`)
})
})