Add has-* variants for :has(...) pseudo-class (#11318)

* Add `has-*` variants for `:has(...)` pseudo-class

* Update changelog

* Fix mistake in test

---------

Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
This commit is contained in:
Adam Wathan 2023-05-30 12:21:08 -04:00 • committed by Jordan Pittman
parent b70f8fd036
commit d72c1893c8
4 changed files with 148 additions and 0 deletions

View file

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Add `svh`, `lvh`, and `dvh` values to default `height`/`min-height`/`max-height` theme ([#11317](https://github.com/tailwindlabs/tailwindcss/pull/11317))
- Add `has-*` variants for `:has(...)` pseudo-class ([#11318](https://github.com/tailwindlabs/tailwindcss/pull/11318))
## [3.3.7] - 2023-12-18

View file

@ -392,6 +392,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(

View file

@ -758,6 +758,7 @@ function resolvePlugins(context, root) {
let beforeVariants = [
variantPlugins['pseudoElementVariants'],
variantPlugins['pseudoClassVariants'],
variantPlugins['hasVariants'],
variantPlugins['ariaVariants'],
variantPlugins['dataVariants'],
]

View file

@ -845,6 +845,132 @@ crosscheck(({ stable, oxide }) => {
})
})
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: [