Skip to content

Commit

Permalink
Add has-* variants for :has(...) pseudo-class (#11318)
Browse files Browse the repository at this point in the history
* Add `has-*` variants for `:has(...)` pseudo-class

* Update changelog

* Fix mistake in test

---------

Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
  • Loading branch information
adamwathan and adamwathan committed May 30, 2023
1 parent 7248f21 commit 69d8687
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix parsing of `theme()` inside `calc()` when there are no spaces around operators ([#11157](https://github.com/tailwindlabs/tailwindcss/pull/11157))
- Ensure `repeating-conic-gradient` is detected as an image ([#11180](https://github.com/tailwindlabs/tailwindcss/pull/11180))
- Remove `autoprefixer` dependency ([#11315](https://github.com/tailwindlabs/tailwindcss/pull/11315))
- Add `has-*` variants for `:has(...)` pseudo-class ([#11318](https://github.com/tailwindlabs/tailwindcss/pull/11318))

### Added

Expand Down
20 changes: 20 additions & 0 deletions src/corePlugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,26 @@ export let variantPlugins = {
)
},

hasVariants: ({ matchVariant }) => {
matchVariant('has', (value) => `&:has(${normalize(value)})`, { values: {} })
matchVariant(
'group-has',
(value, { modifier }) =>
modifier
? `:merge(.group\\/${modifier}):has(${normalize(value)}) &`
: `:merge(.group):has(${normalize(value)}) &`,
{ values: {} }
)
matchVariant(
'peer-has',
(value, { modifier }) =>
modifier
? `:merge(.peer\\/${modifier}):has(${normalize(value)}) ~ &`
: `:merge(.peer):has(${normalize(value)}) ~ &`,
{ values: {} }
)
},

ariaVariants: ({ matchVariant, theme }) => {
matchVariant('aria', (value) => `&[aria-${normalize(value)}]`, { values: theme('aria') ?? {} })
matchVariant(
Expand Down
1 change: 1 addition & 0 deletions src/lib/setupContextUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,7 @@ function resolvePlugins(context, root) {
let beforeVariants = [
variantPlugins['pseudoElementVariants'],
variantPlugins['pseudoClassVariants'],
variantPlugins['hasVariants'],
variantPlugins['ariaVariants'],
variantPlugins['dataVariants'],
]
Expand Down
126 changes: 126 additions & 0 deletions tests/arbitrary-variants.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,132 @@ it('should support supports', () => {
})
})

test('has-* variants with arbitrary values', () => {
let config = {
theme: {},
content: [
{
raw: html`
<div>
<figure class="has-[figcaption]:inline-block"></figure>
<div class="has-[.foo]:flex"></div>
<div class="has-[.foo:hover]:block"></div>
<div class="has-[[data-active]]:inline"></div>
<div class="has-[>_.potato]:table"></div>
<div class="has-[+_h2]:grid"></div>
<div class="has-[>_h1_+_h2]:contents"></div>
<div class="has-[h2]:has-[.banana]:hidden"></div>
</div>
`,
},
],
corePlugins: { preflight: false },
}

let input = css`
@tailwind utilities;
`

return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.has-\[\.foo\:hover\]\:block:has(.foo:hover) {
display: block;
}
.has-\[figcaption\]\:inline-block:has(figcaption) {
display: inline-block;
}
.has-\[\[data-active\]\]\:inline:has([data-active]) {
display: inline;
}
.has-\[\.foo\]\:flex:has(.foo) {
display: flex;
}
.has-\[\>_\.potato\]\:table:has(> .potato) {
display: table;
}
.has-\[\+_h2\]\:grid:has(+ h2) {
display: grid;
}
.has-\[\>_h1_\+_h2\]\:contents:has(> h1 + h2) {
display: contents;
}
.has-\[h2\]\:has-\[\.banana\]\:hidden:has(.banana):has(h2) {
display: none;
}
`)
})
})

test('group-has-* variants with arbitrary values', () => {
let config = {
theme: {},
content: [
{
raw: html`
<div class="group">
<div class="group-has-[>_h1_+_.foo]:block"></div>
</div>
<div class="group/two">
<div class="group-has-[>_h1_+_.foo]/two:flex"></div>
</div>
`,
},
],
corePlugins: { preflight: false },
}

let input = css`
@tailwind utilities;
`

return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.group:has(> h1 + .foo) .group-has-\[\>_h1_\+_\.foo\]\:block {
display: block;
}
.group\/two:has(> h1 + .foo) .group-has-\[\>_h1_\+_\.foo\]\/two\:flex {
display: flex;
}
`)
})
})

test('peer-has-* variants with arbitrary values', () => {
let config = {
theme: {},
content: [
{
raw: html`
<div>
<div className="peer"></div>
<div class="peer-has-[>_h1_+_.foo]:block"></div>
</div>
<div>
<div className="peer"></div>
<div class="peer-has-[>_h1_+_.foo]/two:flex"></div>
</div>
`,
},
],
corePlugins: { preflight: false },
}

let input = css`
@tailwind utilities;
`

return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.peer:has(> h1 + .foo) ~ .peer-has-\[\>_h1_\+_\.foo\]\:block {
display: block;
}
.peer\/two:has(> h1 + .foo) ~ .peer-has-\[\>_h1_\+_\.foo\]\/two\:flex {
display: flex;
}
`)
})
})

it('should be possible to use modifiers and arbitrary groups', () => {
let config = {
content: [
Expand Down

0 comments on commit 69d8687

Please sign in to comment.