Skip to content

Commit

Permalink
Replaces classes in utility selectors like :where and :has
Browse files Browse the repository at this point in the history
  • Loading branch information
thecrypticace committed Sep 11, 2022
1 parent cc1be47 commit 6cdbcf9
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 7 deletions.
31 changes: 24 additions & 7 deletions src/util/formatVariantSelector.js
Expand Up @@ -81,6 +81,29 @@ function resortSelector(sel) {
return sel
}

function eliminateIrrelevantSelectors(sel, base) {
let hasClassesMatchingCandidate = sel.some((n) => n.type === 'class' && n.value === base)

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,
{
Expand Down Expand Up @@ -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.:
//
Expand Down
73 changes: 73 additions & 0 deletions tests/variants.test.js
Expand Up @@ -944,3 +944,76 @@ 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;
}
: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;
}
:is(.sm\:foo) {
color: yellow;
}
html:has(.sm\:foo) {
color: green;
}
}
`)
})
})

0 comments on commit 6cdbcf9

Please sign in to comment.