From 69d86873c36fdf5bd3ac21cd7969c02cbe662930 Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Tue, 30 May 2023 12:21:08 -0400 Subject: [PATCH] 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> --- CHANGELOG.md | 1 + src/corePlugins.js | 20 +++++ src/lib/setupContextUtils.js | 1 + tests/arbitrary-variants.test.js | 126 +++++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3193b6a9522..5b535d888499 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 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..c49376139a7c 100644 --- a/tests/arbitrary-variants.test.js +++ b/tests/arbitrary-variants.test.js @@ -772,6 +772,132 @@ 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-\[\.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` +
+
+
+
+
+
+ `, + }, + ], + 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: [