diff --git a/CHANGELOG.md b/CHANGELOG.md index 811961325ac0..89adda14155d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `forced-colors` variant ([#11694](https://github.com/tailwindlabs/tailwindcss/pull/11694)) - Add `appearance-auto` utility ([#12404](https://github.com/tailwindlabs/tailwindcss/pull/12404)) - Add logical property values for `float` and `clear` utilities ([#12480](https://github.com/tailwindlabs/tailwindcss/pull/12480)) +- Add `*` variant for targeting direct children ([#12551](https://github.com/tailwindlabs/tailwindcss/pull/12551)) ### Changed diff --git a/oxide/crates/core/src/parser.rs b/oxide/crates/core/src/parser.rs index 16831afffd41..2f159f25ab46 100644 --- a/oxide/crates/core/src/parser.rs +++ b/oxide/crates/core/src/parser.rs @@ -278,7 +278,7 @@ impl<'a> Extractor<'a> { } // Allowed first characters. - b'@' | b'!' | b'-' | b'<' | b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z' => { + b'@' | b'!' | b'-' | b'<' | b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z' | b'*' => { // TODO: A bunch of characters that we currently support but maybe we only want it behind // a flag. E.g.: '' | '$' | '^' | '_' @@ -329,6 +329,7 @@ impl<'a> Extractor<'a> { | b'!' | b'@' | b'%' + | b'*' if prev != b']' => { /* TODO: The `b'@'` is necessary for custom separators like _@, maybe we can handle this in a better way... */ @@ -508,6 +509,15 @@ mod test { assert_eq!(candidates, vec!["hover:underline"]); } + #[test] + fn it_can_parse_start_variants() { + let candidates = run("*:underline", false); + assert_eq!(candidates, vec!["*:underline"]); + + let candidates = run("hover:*:underline", false); + assert_eq!(candidates, vec!["hover:*:underline"]); + } + #[test] fn it_can_parse_simple_candidates_with_stacked_variants() { let candidates = run("focus:hover:underline", false); diff --git a/src/corePlugins.js b/src/corePlugins.js index 76443f5544cc..a0687ab675e4 100644 --- a/src/corePlugins.js +++ b/src/corePlugins.js @@ -25,6 +25,9 @@ import { normalize } from './util/dataTypes' import { INTERNAL_FEATURES } from './lib/setupContextUtils' export let variantPlugins = { + childVariant: ({ addVariant }) => { + addVariant('*', '& > *') + }, pseudoElementVariants: ({ addVariant }) => { addVariant('first-letter', '&::first-letter') addVariant('first-line', '&::first-line') diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index 28991dc838c8..d47620218d4c 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -756,6 +756,7 @@ function resolvePlugins(context, root) { // TODO: This is a workaround for backwards compatibility, since custom variants // were historically sorted before screen/stackable variants. let beforeVariants = [ + variantPlugins['childVariant'], variantPlugins['pseudoElementVariants'], variantPlugins['pseudoClassVariants'], variantPlugins['hasVariants'], diff --git a/tests/plugins/variants/__snapshots__/childVariant.test.js.snap b/tests/plugins/variants/__snapshots__/childVariant.test.js.snap new file mode 100644 index 000000000000..c05c5962e964 --- /dev/null +++ b/tests/plugins/variants/__snapshots__/childVariant.test.js.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should test the 'childVariant' plugin 1`] = ` +" +.\\*\\:flex > * { + display: flex; +} +" +`; diff --git a/tests/plugins/variants/childVariant.test.js b/tests/plugins/variants/childVariant.test.js new file mode 100644 index 000000000000..702a11686855 --- /dev/null +++ b/tests/plugins/variants/childVariant.test.js @@ -0,0 +1,3 @@ +import { quickVariantPluginTest } from '../../util/run' + +quickVariantPluginTest('childVariant').toMatchSnapshot() diff --git a/tests/variants.test.js b/tests/variants.test.js index 55facc122f41..139d00ad8d62 100644 --- a/tests/variants.test.js +++ b/tests/variants.test.js @@ -1201,4 +1201,37 @@ crosscheck(({ stable, oxide }) => { } `) }) + + test('* is matched by the parser as the children variant', async () => { + let config = { + content: [ + { + raw: html` +
+
+
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + let result = await run(input, config) + + expect(result.css).toMatchFormattedCss(css` + .\*\:italic > *, + .\*\:hover\:italic:hover > *, + .hover\:\*\:italic > :hover, + .data-\[slot\=label\]\:\*\:hover\:italic:hover > [data-slot='label'], + .\[\&_p\]\:\*\:hover\:italic:hover > * p { + font-style: italic; + } + `) + }) })