From 37822740602392b61968d73293786e1f4a7d2ef1 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Sun, 17 Nov 2024 22:44:13 +0100 Subject: [PATCH 01/12] add new `in-*` variant --- packages/tailwindcss/src/variants.test.ts | 10 ++++++++++ packages/tailwindcss/src/variants.ts | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/packages/tailwindcss/src/variants.test.ts b/packages/tailwindcss/src/variants.test.ts index 54cbe2dbf5d0..a0cb3e63678a 100644 --- a/packages/tailwindcss/src/variants.test.ts +++ b/packages/tailwindcss/src/variants.test.ts @@ -1693,6 +1693,16 @@ test('not', async () => { ).toEqual('') }) +test('in', async () => { + expect(await run(['in-p:flex', 'in-[p]:flex', 'in-[.group]:flex', 'not-in-p:flex'])) + .toMatchInlineSnapshot(` + ".not-in-p\\:flex:not(:where(p) *), :where(p) .in-p\\:flex, :where(.group) .in-\\[\\.group\\]\\:flex, :where(p) .in-\\[p\\]\\:flex { + display: flex; + }" + `) + expect(await run(['in-foo-bar:flex'])).toEqual('') +}) + test('has', async () => { expect( await compileCss( diff --git a/packages/tailwindcss/src/variants.ts b/packages/tailwindcss/src/variants.ts index 2a636f20831c..b679e6f373bd 100644 --- a/packages/tailwindcss/src/variants.ts +++ b/packages/tailwindcss/src/variants.ts @@ -711,6 +711,18 @@ export function createVariants(theme: Theme): Variants { staticVariant('inert', ['&:is([inert], [inert] *)']) + variants.functional('in', (ruleNode, variant) => { + if (!variant.value || variant.modifier) return null + + // Named values should be alphanumeric. This prevents `in-foo-bar` from + // being used as a variant. + if (variant.value.kind === 'named' && !/^[a-zA-Z]+$/i.test(variant.value.value)) { + return null + } + + ruleNode.nodes = [styleRule(`:where(${variant.value.value}) &`, ruleNode.nodes)] + }) + variants.compound('has', Compounds.StyleRules, (ruleNode, variant) => { if (variant.modifier) return null From 6f375d9c18fcce07eabe5a522833446fa011319a Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Sun, 17 Nov 2024 23:15:44 +0100 Subject: [PATCH 02/12] migrate `[p_&]:flex` to `in-p:flex` --- .../modernize-arbitrary-values.test.ts | 4 +++ .../codemods/modernize-arbitrary-values.ts | 36 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.test.ts index f5a2c3d7e6d4..12adc397eba2 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.test.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.test.ts @@ -18,6 +18,10 @@ test.each([ ['[&:first-child]:flex', 'first:flex'], ['[&:not(:first-child)]:flex', 'not-first:flex'], + // in-* variants + ['[p_&]:flex', 'in-p:flex'], + ['[.foo_&]:flex', 'in-[.foo]:flex'], + // nth-child ['[&:nth-child(2)]:flex', 'nth-2:flex'], ['[&:not(:nth-child(2))]:flex', 'not-nth-2:flex'], diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.ts b/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.ts index 7bcd252eb797..cef7ecaf5d7c 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.ts @@ -98,6 +98,42 @@ export function modernizeArbitraryValues( prefixedVariant = designSystem.parseVariant('**') } + // `in-*` variant + if ( + // Only top-level, so `has-[p_&]` is not supported + parent === null && + // `[data-*]` and `[aria-*]` are handled separately + !( + ast.nodes[0].nodes[0].type === 'attribute' && + (ast.nodes[0].nodes[0].attribute.startsWith('data-') || + ast.nodes[0].nodes[0].attribute.startsWith('aria-')) + ) && + // [.foo___&]:flex + // ^^^^ ^ ^ + ast.nodes[0].nodes.at(-1)?.type === 'nesting' + ) { + let selector = ast.nodes[0] + let nodes = selector.nodes + + nodes.pop() // Remove the last node `&` + + // Remove trailing whitespace + let last = nodes.at(-1) + while (last?.type === 'combinator' && last.value === ' ') { + nodes.pop() + last = nodes.at(-1) + } + + changed = true + if (nodes.length === 1 && nodes[0].type === 'tag') { + Object.assign(variant, designSystem.parseVariant(`in-${selector.toString().trim()}`)) + } else { + Object.assign(variant, designSystem.parseVariant(`in-[${selector.toString().trim()}]`)) + } + + continue + } + // Filter out `&`. E.g.: `&[data-foo]` => `[data-foo]` let selectorNodes = ast.nodes[0].filter((node) => node.type !== 'nesting') From e18b37aeb3cf798ed0a4aa80de9a9567bb5c3656 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Sun, 17 Nov 2024 23:35:12 +0100 Subject: [PATCH 03/12] more extreme conversions like `group-[]:flex` to `in-[.group]:flex` --- .../modernize-arbitrary-values.test.ts | 10 ++++++ .../codemods/modernize-arbitrary-values.ts | 35 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.test.ts index 12adc397eba2..f1d6d03e67c2 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.test.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.test.ts @@ -21,6 +21,16 @@ test.each([ // in-* variants ['[p_&]:flex', 'in-p:flex'], ['[.foo_&]:flex', 'in-[.foo]:flex'], + // Some extreme examples of what happens in the wild: + ['group-[]:flex', 'in-[.group]:flex'], + ['group-[]/name:flex', 'in-[.group\\/name]:flex'], + + // These shouldn't happen in the real world (because compound variants are + // new). But this could happen once we allow codemods to run in v4+ projects. + ['has-group-[]:flex', 'has-in-[.group]:flex'], + ['has-group-[]/name:flex', 'has-in-[.group\\/name]:flex'], + ['not-group-[]:flex', 'not-in-[.group]:flex'], + ['not-group-[]/name:flex', 'not-in-[.group\\/name]:flex'], // nth-child ['[&:nth-child(2)]:flex', 'nth-2:flex'], diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.ts b/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.ts index cef7ecaf5d7c..2eb64eb2aed6 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.ts @@ -15,6 +15,41 @@ export function modernizeArbitraryValues( let changed = false for (let [variant, parent] of variants(clone)) { + // Forward modifier from the root to the compound variant + if (variant.kind === 'compound' && (variant.root === 'has' || variant.root === 'not')) { + if (variant.modifier !== null) { + if ('modifier' in variant.variant) { + variant.variant.modifier = variant.modifier + variant.modifier = null + } + } + } + + // Promote `group-[]:flex` to `in-[.group]:flex` + // ^^ Yes, this is empty + // Promote `group-[]/name:flex` to `in-[.group\/name]:flex` + if ( + variant.kind === 'compound' && + variant.root === 'group' && + variant.variant.kind === 'arbitrary' && + variant.variant.selector === '&:is()' + ) { + // `group-[]` + if (variant.modifier === null) { + changed = true + Object.assign(variant, designSystem.parseVariant('in-[.group]')) + } + + // `group-[]/name` + else if (variant.modifier.kind === 'named') { + changed = true + Object.assign( + variant, + designSystem.parseVariant(`in-[.group\\/${variant.modifier.value}]`), + ) + } + } + // Expecting an arbitrary variant if (variant.kind !== 'arbitrary') continue From 7daef2da21d4a492ba2074a09eeb718b8a46325b Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Sun, 17 Nov 2024 23:52:49 +0100 Subject: [PATCH 04/12] update intellisense tests --- .../src/__snapshots__/intellisense.test.ts.snap | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap b/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap index a7a60326cd9e..4e833548465d 100644 --- a/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap +++ b/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap @@ -7554,6 +7554,7 @@ exports[`getVariants 1`] = ` "enabled", "disabled", "inert", + "in", "has", "aria", "data", @@ -7622,6 +7623,7 @@ exports[`getVariants 1`] = ` "enabled", "disabled", "inert", + "in", "has", "aria", "data", @@ -7674,6 +7676,7 @@ exports[`getVariants 1`] = ` "enabled", "disabled", "inert", + "in", "has", "aria", "data", @@ -7972,6 +7975,13 @@ exports[`getVariants 1`] = ` "selectors": [Function], "values": [], }, + { + "hasDash": true, + "isArbitrary": true, + "name": "in", + "selectors": [Function], + "values": [], + }, { "hasDash": true, "isArbitrary": true, @@ -8013,6 +8023,7 @@ exports[`getVariants 1`] = ` "enabled", "disabled", "inert", + "in", "has", "aria", "data", From 3646f5466291d0bf09f59fccdf4bb9b95a27d93e Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 18 Nov 2024 01:22:20 +0100 Subject: [PATCH 05/12] =?UTF-8?q?add=20`isAlpha(=E2=80=A6)`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tailwindcss/src/utils/is-alpha.bench.ts | 25 +++++++++++++++++++ packages/tailwindcss/src/utils/is-alpha.ts | 14 +++++++++++ 2 files changed, 39 insertions(+) create mode 100644 packages/tailwindcss/src/utils/is-alpha.bench.ts create mode 100644 packages/tailwindcss/src/utils/is-alpha.ts diff --git a/packages/tailwindcss/src/utils/is-alpha.bench.ts b/packages/tailwindcss/src/utils/is-alpha.bench.ts new file mode 100644 index 000000000000..8669e2ad367c --- /dev/null +++ b/packages/tailwindcss/src/utils/is-alpha.bench.ts @@ -0,0 +1,25 @@ +import { bench } from 'vitest' +import { isAlpha } from './is-alpha' + +const ALPHA_REGEX_A = /^[a-zA-Z]+$/ +const ALPHA_REGEX_B = /^[a-z]+$/i +const ALPHA_REGEX_C = /^[A-Z]+$/i + +const implementations = new Map boolean>([ + ['RegExp A', (input) => ALPHA_REGEX_A.test(input)], + ['RegExp B', (input) => ALPHA_REGEX_B.test(input)], + ['RegExp C', (input) => ALPHA_REGEX_C.test(input)], + ['Manual', isAlpha], +]) + +for (let [name, check] of implementations) { + bench(name, () => { + for (let i = 0; i < 1e6; i++) { + check('abc') + check('ABC') + check('AbC') + check('a-b-c') + check('123') + } + }) +} diff --git a/packages/tailwindcss/src/utils/is-alpha.ts b/packages/tailwindcss/src/utils/is-alpha.ts new file mode 100644 index 000000000000..8b821d5e088f --- /dev/null +++ b/packages/tailwindcss/src/utils/is-alpha.ts @@ -0,0 +1,14 @@ +const UPPER_A = 65 +const UPPER_Z = 90 +const LOWER_A = 97 +const LOWER_Z = 122 + +export function isAlpha(input: string): boolean { + for (let i = 0; i < input.length; i++) { + let code = input.charCodeAt(i) + if (code < UPPER_A || (code > UPPER_Z && code < LOWER_A) || code > LOWER_Z) { + return false + } + } + return true +} From 6a541abfd7a5742110bcaed4dc54b3c61b46d148 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 18 Nov 2024 01:24:19 +0100 Subject: [PATCH 06/12] =?UTF-8?q?use=20`isAlpha(=E2=80=A6)`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/tailwindcss/src/variants.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/tailwindcss/src/variants.ts b/packages/tailwindcss/src/variants.ts index b679e6f373bd..fbab6835a8d5 100644 --- a/packages/tailwindcss/src/variants.ts +++ b/packages/tailwindcss/src/variants.ts @@ -16,6 +16,7 @@ import type { Theme } from './theme' import { compareBreakpoints } from './utils/compare-breakpoints' import { DefaultMap } from './utils/default-map' import { isPositiveInteger } from './utils/infer-data-type' +import { isAlpha } from './utils/is-alpha' import { segment } from './utils/segment' type VariantFn = ( @@ -714,9 +715,9 @@ export function createVariants(theme: Theme): Variants { variants.functional('in', (ruleNode, variant) => { if (!variant.value || variant.modifier) return null - // Named values should be alphanumeric. This prevents `in-foo-bar` from - // being used as a variant. - if (variant.value.kind === 'named' && !/^[a-zA-Z]+$/i.test(variant.value.value)) { + // Named values should be alpha (tag selector). This prevents `in-foo-bar` + // from being used as a variant. + if (variant.value.kind === 'named' && !isAlpha(variant.value.value)) { return null } From a691ee8038f3c43c22795507498e237684e25e02 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 18 Nov 2024 11:49:38 +0100 Subject: [PATCH 07/12] implement `in-*` as a compound variant Co-authored-by: Eloy Espinaco --- .../tailwindcss/src/utils/is-alpha.bench.ts | 25 -------------- packages/tailwindcss/src/utils/is-alpha.ts | 14 -------- packages/tailwindcss/src/variants.test.ts | 21 ++++++++---- packages/tailwindcss/src/variants.ts | 34 ++++++++++++++----- 4 files changed, 39 insertions(+), 55 deletions(-) delete mode 100644 packages/tailwindcss/src/utils/is-alpha.bench.ts delete mode 100644 packages/tailwindcss/src/utils/is-alpha.ts diff --git a/packages/tailwindcss/src/utils/is-alpha.bench.ts b/packages/tailwindcss/src/utils/is-alpha.bench.ts deleted file mode 100644 index 8669e2ad367c..000000000000 --- a/packages/tailwindcss/src/utils/is-alpha.bench.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { bench } from 'vitest' -import { isAlpha } from './is-alpha' - -const ALPHA_REGEX_A = /^[a-zA-Z]+$/ -const ALPHA_REGEX_B = /^[a-z]+$/i -const ALPHA_REGEX_C = /^[A-Z]+$/i - -const implementations = new Map boolean>([ - ['RegExp A', (input) => ALPHA_REGEX_A.test(input)], - ['RegExp B', (input) => ALPHA_REGEX_B.test(input)], - ['RegExp C', (input) => ALPHA_REGEX_C.test(input)], - ['Manual', isAlpha], -]) - -for (let [name, check] of implementations) { - bench(name, () => { - for (let i = 0; i < 1e6; i++) { - check('abc') - check('ABC') - check('AbC') - check('a-b-c') - check('123') - } - }) -} diff --git a/packages/tailwindcss/src/utils/is-alpha.ts b/packages/tailwindcss/src/utils/is-alpha.ts deleted file mode 100644 index 8b821d5e088f..000000000000 --- a/packages/tailwindcss/src/utils/is-alpha.ts +++ /dev/null @@ -1,14 +0,0 @@ -const UPPER_A = 65 -const UPPER_Z = 90 -const LOWER_A = 97 -const LOWER_Z = 122 - -export function isAlpha(input: string): boolean { - for (let i = 0; i < input.length; i++) { - let code = input.charCodeAt(i) - if (code < UPPER_A || (code > UPPER_Z && code < LOWER_A) || code > LOWER_Z) { - return false - } - } - return true -} diff --git a/packages/tailwindcss/src/variants.test.ts b/packages/tailwindcss/src/variants.test.ts index a0cb3e63678a..c6510fc26fdc 100644 --- a/packages/tailwindcss/src/variants.test.ts +++ b/packages/tailwindcss/src/variants.test.ts @@ -1694,13 +1694,20 @@ test('not', async () => { }) test('in', async () => { - expect(await run(['in-p:flex', 'in-[p]:flex', 'in-[.group]:flex', 'not-in-p:flex'])) - .toMatchInlineSnapshot(` - ".not-in-p\\:flex:not(:where(p) *), :where(p) .in-p\\:flex, :where(.group) .in-\\[\\.group\\]\\:flex, :where(p) .in-\\[p\\]\\:flex { - display: flex; - }" - `) - expect(await run(['in-foo-bar:flex'])).toEqual('') + expect( + await run([ + 'in-[p]:flex', + 'in-[.group]:flex', + 'not-in-[p]:flex', + 'not-in-[.group]:flex', + 'in-data-visible:flex', + ]), + ).toMatchInlineSnapshot(` + ".not-in-\\[\\.group\\]\\:flex:not(:where(.group) *), .not-in-\\[p\\]\\:flex:not(:where(:is(p)) *), :where([data-visible]) .in-data-visible\\:flex, :where(.group) .in-\\[\\.group\\]\\:flex, :where(:is(p)) .in-\\[p\\]\\:flex { + display: flex; + }" + `) + expect(await run(['in-p:flex', 'in-foo-bar:flex'])).toEqual('') }) test('has', async () => { diff --git a/packages/tailwindcss/src/variants.ts b/packages/tailwindcss/src/variants.ts index fbab6835a8d5..68a035b3d1f8 100644 --- a/packages/tailwindcss/src/variants.ts +++ b/packages/tailwindcss/src/variants.ts @@ -16,7 +16,6 @@ import type { Theme } from './theme' import { compareBreakpoints } from './utils/compare-breakpoints' import { DefaultMap } from './utils/default-map' import { isPositiveInteger } from './utils/infer-data-type' -import { isAlpha } from './utils/is-alpha' import { segment } from './utils/segment' type VariantFn = ( @@ -712,16 +711,33 @@ export function createVariants(theme: Theme): Variants { staticVariant('inert', ['&:is([inert], [inert] *)']) - variants.functional('in', (ruleNode, variant) => { - if (!variant.value || variant.modifier) return null + variants.compound('in', Compounds.StyleRules, (ruleNode, variant) => { + if (variant.modifier) return null - // Named values should be alpha (tag selector). This prevents `in-foo-bar` - // from being used as a variant. - if (variant.value.kind === 'named' && !isAlpha(variant.value.value)) { - return null - } + let didApply = false + + walk([ruleNode], (node, { path }) => { + if (node.kind !== 'rule') return WalkAction.Continue + + // Throw out any candidates with variants using nested style rules + for (let parent of path.slice(0, -1)) { + if (parent.kind !== 'rule') continue - ruleNode.nodes = [styleRule(`:where(${variant.value.value}) &`, ruleNode.nodes)] + didApply = false + return WalkAction.Stop + } + + // Replace `&` in target variant with `*`, so variants like `&:hover` + // become `:where(*:hover) &`. The `*` will often be optimized away. + node.selector = `:where(${node.selector.replaceAll('&', '*')}) &` + + // Track that the variant was actually applied + didApply = true + }) + + // If the node wasn't modified, this variant is not compatible with + // `in-*` so discard the candidate. + if (!didApply) return null }) variants.compound('has', Compounds.StyleRules, (ruleNode, variant) => { From 368ac556d517561edb7f82594d9354249a82297b Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 18 Nov 2024 12:13:32 +0100 Subject: [PATCH 08/12] migrate `[[data-visible]_&]` to `in-data-visible` --- .../modernize-arbitrary-values.test.ts | 3 +- .../codemods/modernize-arbitrary-values.ts | 36 +++++++++++++++---- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.test.ts index f1d6d03e67c2..910600cb54bc 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.test.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.test.ts @@ -19,8 +19,9 @@ test.each([ ['[&:not(:first-child)]:flex', 'not-first:flex'], // in-* variants - ['[p_&]:flex', 'in-p:flex'], + ['[p_&]:flex', 'in-[p]:flex'], ['[.foo_&]:flex', 'in-[.foo]:flex'], + ['[[data-visible]_&]:flex', 'in-data-visible:flex'], // Some extreme examples of what happens in the wild: ['group-[]:flex', 'in-[.group]:flex'], ['group-[]/name:flex', 'in-[.group\\/name]:flex'], diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.ts b/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.ts index 2eb64eb2aed6..7398580e3f2d 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.ts @@ -16,7 +16,10 @@ export function modernizeArbitraryValues( for (let [variant, parent] of variants(clone)) { // Forward modifier from the root to the compound variant - if (variant.kind === 'compound' && (variant.root === 'has' || variant.root === 'not')) { + if ( + variant.kind === 'compound' && + (variant.root === 'has' || variant.root === 'not' || variant.root === 'in') + ) { if (variant.modifier !== null) { if ('modifier' in variant.variant) { variant.variant.modifier = variant.modifier @@ -133,6 +136,30 @@ export function modernizeArbitraryValues( prefixedVariant = designSystem.parseVariant('**') } + // Handling a child/parent combinator. E.g.: `[[data-visible]_&]` => `in-data-visible` + if ( + // Only top-level, so `has-[&_[data-visible]]` is not supported + parent === null && + // [[data-visible]___&]:flex + // ^^^^^^^^^^^^^^ ^ ^ + ast.nodes[0].length === 3 && + ast.nodes[0].nodes[0].type === 'attribute' && + ast.nodes[0].nodes[1].type === 'combinator' && + ast.nodes[0].nodes[1].value === ' ' && + ast.nodes[0].nodes[2].type === 'nesting' && + ast.nodes[0].nodes[2].value === '&' + ) { + ast.nodes[0].nodes = [ast.nodes[0].nodes[0]] + changed = true + // When handling a compound like `in-[[data-visible]]`, we will first + // handle `[[data-visible]]`, then the parent `in-*` part. This means + // that we can convert `[[data-visible]_&]` to `in-[[data-visible]]`. + // + // Later this gets converted to `in-data-visible`. + Object.assign(variant, designSystem.parseVariant(`in-[${ast.toString()}]`)) + continue + } + // `in-*` variant if ( // Only top-level, so `has-[p_&]` is not supported @@ -160,12 +187,7 @@ export function modernizeArbitraryValues( } changed = true - if (nodes.length === 1 && nodes[0].type === 'tag') { - Object.assign(variant, designSystem.parseVariant(`in-${selector.toString().trim()}`)) - } else { - Object.assign(variant, designSystem.parseVariant(`in-[${selector.toString().trim()}]`)) - } - + Object.assign(variant, designSystem.parseVariant(`in-[${selector.toString().trim()}]`)) continue } From 4a391e34d3e670188c71f57b3fe65b7bf4e13d54 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 18 Nov 2024 13:25:10 +0100 Subject: [PATCH 09/12] Update packages/tailwindcss/src/variants.ts Co-authored-by: Jordan Pittman --- packages/tailwindcss/src/variants.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/tailwindcss/src/variants.ts b/packages/tailwindcss/src/variants.ts index 68a035b3d1f8..a88f3d50660f 100644 --- a/packages/tailwindcss/src/variants.ts +++ b/packages/tailwindcss/src/variants.ts @@ -740,6 +740,12 @@ export function createVariants(theme: Theme): Variants { if (!didApply) return null }) + variants.suggest('in', () => { + return Array.from(variants.keys()).filter((name) => { + return variants.compoundsWith('in', name) + }) + }) + variants.compound('has', Compounds.StyleRules, (ruleNode, variant) => { if (variant.modifier) return null From f45ce6e01830cef3949d8b33d284c7fd3ac0cb32 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 18 Nov 2024 13:27:35 +0100 Subject: [PATCH 10/12] update intellisense with `in-*` variant --- .../__snapshots__/intellisense.test.ts.snap | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap b/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap index 4e833548465d..aa80fc0376e7 100644 --- a/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap +++ b/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap @@ -7980,7 +7980,53 @@ exports[`getVariants 1`] = ` "isArbitrary": true, "name": "in", "selectors": [Function], - "values": [], + "values": [ + "not", + "group", + "peer", + "first", + "last", + "only", + "odd", + "even", + "first-of-type", + "last-of-type", + "only-of-type", + "visited", + "target", + "open", + "default", + "checked", + "indeterminate", + "placeholder-shown", + "autofill", + "optional", + "required", + "valid", + "invalid", + "in-range", + "out-of-range", + "read-only", + "empty", + "focus-within", + "hover", + "focus", + "focus-visible", + "active", + "enabled", + "disabled", + "inert", + "in", + "has", + "aria", + "data", + "nth", + "nth-last", + "nth-of-type", + "nth-last-of-type", + "ltr", + "rtl", + ], }, { "hasDash": true, From 9433147a932a309cb2927ca955bc45a3bfef9d87 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 18 Nov 2024 13:56:36 +0100 Subject: [PATCH 11/12] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd2b3c2879f9..5b7d8ca4aa29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Reintroduce `max-w-screen-*` utilities that read from the `--breakpoint` namespace as deprecated utilities ([#15013](https://github.com/tailwindlabs/tailwindcss/pull/15013)) - Support using CSS variables as arbitrary values without `var(…)` by using parentheses instead of square brackets (e.g. `bg-(--my-color)`) ([#15020](https://github.com/tailwindlabs/tailwindcss/pull/15020)) +- Add new `in-*` variant ([#15025](https://github.com/tailwindlabs/tailwindcss/pull/15025)) - _Upgrade (experimental)_: Migrate `[&>*]` to the `*` variant ([#15022](https://github.com/tailwindlabs/tailwindcss/pull/15022)) - _Upgrade (experimental)_: Migrate `[&_*]` to the `**` variant ([#15022](https://github.com/tailwindlabs/tailwindcss/pull/15022)) From 4508b79eb82d0659a49b12b78a3a63b2845f078a Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 18 Nov 2024 16:16:18 +0100 Subject: [PATCH 12/12] do not migrate `group-[]:flex` to `in-[.group]:flex` Because this requires prefixing the `.group` if the prefix is configured. --- .../modernize-arbitrary-values.test.ts | 10 -------- .../codemods/modernize-arbitrary-values.ts | 25 ------------------- 2 files changed, 35 deletions(-) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.test.ts index 910600cb54bc..51d26a107eab 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.test.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.test.ts @@ -22,16 +22,6 @@ test.each([ ['[p_&]:flex', 'in-[p]:flex'], ['[.foo_&]:flex', 'in-[.foo]:flex'], ['[[data-visible]_&]:flex', 'in-data-visible:flex'], - // Some extreme examples of what happens in the wild: - ['group-[]:flex', 'in-[.group]:flex'], - ['group-[]/name:flex', 'in-[.group\\/name]:flex'], - - // These shouldn't happen in the real world (because compound variants are - // new). But this could happen once we allow codemods to run in v4+ projects. - ['has-group-[]:flex', 'has-in-[.group]:flex'], - ['has-group-[]/name:flex', 'has-in-[.group\\/name]:flex'], - ['not-group-[]:flex', 'not-in-[.group]:flex'], - ['not-group-[]/name:flex', 'not-in-[.group\\/name]:flex'], // nth-child ['[&:nth-child(2)]:flex', 'nth-2:flex'], diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.ts b/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.ts index 7398580e3f2d..3efa3f5ddd38 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.ts @@ -28,31 +28,6 @@ export function modernizeArbitraryValues( } } - // Promote `group-[]:flex` to `in-[.group]:flex` - // ^^ Yes, this is empty - // Promote `group-[]/name:flex` to `in-[.group\/name]:flex` - if ( - variant.kind === 'compound' && - variant.root === 'group' && - variant.variant.kind === 'arbitrary' && - variant.variant.selector === '&:is()' - ) { - // `group-[]` - if (variant.modifier === null) { - changed = true - Object.assign(variant, designSystem.parseVariant('in-[.group]')) - } - - // `group-[]/name` - else if (variant.modifier.kind === 'named') { - changed = true - Object.assign( - variant, - designSystem.parseVariant(`in-[.group\\/${variant.modifier.value}]`), - ) - } - } - // Expecting an arbitrary variant if (variant.kind !== 'arbitrary') continue