From a116f56b05abf9effd3a276d1d89b42840e8e216 Mon Sep 17 00:00:00 2001
From: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Date: Tue, 30 May 2023 11:37:36 -0400
Subject: [PATCH 1/3] Add `has-*` variants for `:has(...)` pseudo-class
---
src/corePlugins.js | 20 +++++
src/lib/setupContextUtils.js | 1 +
tests/arbitrary-variants.test.js | 129 +++++++++++++++++++++++++++++++
3 files changed, 150 insertions(+)
diff --git a/src/corePlugins.js b/src/corePlugins.js
index fc373a77f38f..15b4e8d0354d 100644
--- a/src/corePlugins.js
+++ b/src/corePlugins.js
@@ -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(
diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js
index e8026a50e0b8..0d6bc2509ded 100644
--- a/src/lib/setupContextUtils.js
+++ b/src/lib/setupContextUtils.js
@@ -744,6 +744,7 @@ function resolvePlugins(context, root) {
let beforeVariants = [
variantPlugins['pseudoElementVariants'],
variantPlugins['pseudoClassVariants'],
+ variantPlugins['hasVariants'],
variantPlugins['ariaVariants'],
variantPlugins['dataVariants'],
]
diff --git a/tests/arbitrary-variants.test.js b/tests/arbitrary-variants.test.js
index 1e37a5047ef3..dc023b6cdd0e 100644
--- a/tests/arbitrary-variants.test.js
+++ b/tests/arbitrary-variants.test.js
@@ -772,6 +772,135 @@ it('should support supports', () => {
})
})
+test('has-* variants with arbitrary values', () => {
+ let config = {
+ theme: {},
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .has-\[figcaption\]:has(figcaption) {
+ text-decoration: underline;
+ }
+ .has-\[\.foo\:hover\]\:block:has(.foo:hover) {
+ display: 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;
+ }
+ .has-\[figcaption\]\:underline:has(figcaption) {
+ text-decoration-line: underline;
+ }
+ `)
+ })
+})
+
+test('group-has-* variants with arbitrary values', () => {
+ let config = {
+ theme: {},
+ content: [
+ {
+ raw: html`
+
+
+ `,
+ },
+ ],
+ 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`
+
+
+ `,
+ },
+ ],
+ 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: [
From b84608f2066c61861af9bbebb1efbb8607cf17b5 Mon Sep 17 00:00:00 2001
From: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Date: Tue, 30 May 2023 11:55:54 -0400
Subject: [PATCH 2/3] Update changelog
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2bf9d5b783b1..cc45a59f34fe 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
From 4b83235a03ebbd5677e4c5174cbb459c116fe22f Mon Sep 17 00:00:00 2001
From: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Date: Tue, 30 May 2023 12:08:51 -0400
Subject: [PATCH 3/3] Fix mistake in test
---
tests/arbitrary-variants.test.js | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/tests/arbitrary-variants.test.js b/tests/arbitrary-variants.test.js
index dc023b6cdd0e..c49376139a7c 100644
--- a/tests/arbitrary-variants.test.js
+++ b/tests/arbitrary-variants.test.js
@@ -779,7 +779,7 @@ test('has-* variants with arbitrary values', () => {
{
raw: html`
-
+
@@ -800,12 +800,12 @@ test('has-* variants with arbitrary values', () => {
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
- .has-\[figcaption\]:has(figcaption) {
- text-decoration: underline;
- }
.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;
}
@@ -824,9 +824,6 @@ test('has-* variants with arbitrary values', () => {
.has-\[h2\]\:has-\[\.banana\]\:hidden:has(.banana):has(h2) {
display: none;
}
- .has-\[figcaption\]\:underline:has(figcaption) {
- text-decoration-line: underline;
- }
`)
})
})