From 5f3eced44c8ebcb537f3ef16810294e43357952b Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 12 Jul 2024 15:55:15 +0200 Subject: [PATCH 1/3] implement `not` variant with `@media`, `@supports` and `@container` variants --- packages/tailwindcss/src/candidate.ts | 18 ++++- packages/tailwindcss/src/variants.test.ts | 83 +++++++++++++++++++++++ packages/tailwindcss/src/variants.ts | 27 +++++++- 3 files changed, 125 insertions(+), 3 deletions(-) diff --git a/packages/tailwindcss/src/candidate.ts b/packages/tailwindcss/src/candidate.ts index 359a5dbe1..15ed05318 100644 --- a/packages/tailwindcss/src/candidate.ts +++ b/packages/tailwindcss/src/candidate.ts @@ -611,14 +611,28 @@ export function parseVariant(variant: string, designSystem: DesignSystem): Varia let subVariant = designSystem.parseVariant(value) if (subVariant === null) return null - if (subVariant.compounds === false) return null + + let compounds: boolean | null = null + + // Special case: allow compounding in `not` because we can invert + // `@media` queries. + if (root === 'not' && subVariant.compounds === false) { + // However, we do not allow further compounding. E.g.: + // `group-not-print` is invalid. + compounds = false + } + + // Discard the variant if it's not allowed to be compounded. + else if (subVariant.compounds === false) { + return null + } return { kind: 'compound', root, modifier: modifier === null ? null : { kind: 'named', value: modifier }, variant: subVariant, - compounds: designSystem.variants.compounds(root), + compounds: compounds ?? designSystem.variants.compounds(root), } } } diff --git a/packages/tailwindcss/src/variants.test.ts b/packages/tailwindcss/src/variants.test.ts index 9c1021fb4..98b753416 100644 --- a/packages/tailwindcss/src/variants.test.ts +++ b/packages/tailwindcss/src/variants.test.ts @@ -1508,6 +1508,89 @@ test('not', () => { expect(run(['not-[:checked]/foo:flex'])).toEqual('') }) +test('not with media queries', () => { + expect( + compileCss( + css` + @theme { + --breakpoint-lg: 1024px; + } + @tailwind utilities; + `, + ['not-print:flex', 'not-dark:flex', 'not-lg:flex'], + ), + ).toMatchInlineSnapshot(` + ":root { + --breakpoint-lg: 1024px; + } + + @media not (width >= 1024px) { + .not-lg\\:flex { + display: flex; + } + } + + @media not (prefers-color-scheme: dark) { + .not-dark\\:flex { + display: flex; + } + } + + @media not print { + .not-print\\:flex { + display: flex; + } + }" + `) + expect( + run(['group-not-dark:flex', 'peer-not-dark:flex', 'not-print/foo:flex', 'not-dark/foo:flex']), + ).toEqual('') +}) + +test('not with support queries', () => { + expect(run(['not-supports-[display:grid]:flex'])).toMatchInlineSnapshot(` + "@supports not (display: grid) { + .not-supports-\\[display\\:grid\\]\\:flex { + display: flex; + } + }" + `) + expect( + run([ + 'group-not-supports-[display:grid]:flex', + 'peer-not-supports-[display:grid]:flex', + 'not-supports-[display:grid]/foo:flex', + ]), + ).toEqual('') +}) + +test('not with container queries', () => { + expect( + compileCss( + css` + @theme { + --width-lg: 1024px; + } + @tailwind utilities; + `, + ['not-@lg:flex', 'not-@lg/foo:flex'], + ), + ).toMatchInlineSnapshot(` + ":root { + --width-lg: 1024px; + } + + @container not (width >= 1024px) { + .not-\\@lg\\:flex { + display: flex; + } + }" + `) + expect( + run(['group-not-@lg:flex', 'peer-not-@lg:flex', 'not-@lg/foo:flex', '@lg/foo:flex']), + ).toEqual('') +}) + test('has', () => { expect( run([ diff --git a/packages/tailwindcss/src/variants.ts b/packages/tailwindcss/src/variants.ts index d5d51883c..cc8f877bf 100644 --- a/packages/tailwindcss/src/variants.ts +++ b/packages/tailwindcss/src/variants.ts @@ -209,7 +209,32 @@ export function createVariants(theme: Theme): Variants { variants.compound('not', (ruleNode, variant) => { if (variant.modifier) return null - ruleNode.selector = `&:not(${ruleNode.selector.replace('&', '*')})` + + // At-rules + if (ruleNode.selector[0] === '@') { + // @media + if (ruleNode.selector.startsWith('@media ')) { + let queries = segment(ruleNode.selector.slice(7), ',') + ruleNode.selector = `@media ${queries.map((query) => `not ${query}`).join(',')}` + } + + // @supports + else if (ruleNode.selector.startsWith('@supports ')) { + let queries = segment(ruleNode.selector.slice(10), ',') + ruleNode.selector = `@supports ${queries.map((query) => `not ${query}`).join(',')}` + } + + // @container + else if (ruleNode.selector.startsWith('@container ')) { + let queries = segment(ruleNode.selector.slice(11), ',') + ruleNode.selector = `@container ${queries.map((query) => `not ${query}`).join(',')}` + } + } + + // Selectors + else { + ruleNode.selector = `&:not(${ruleNode.selector.replace('&', '*')})` + } }) variants.compound('group', (ruleNode, variant) => { From 9bb1f17593afd173be8409cdddf736b60cda630d Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 12 Jul 2024 16:32:02 +0200 Subject: [PATCH 2/3] ensure nested selectors are not modified when using `group-*` --- packages/tailwindcss/src/index.test.ts | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index a7ba81a7d..89927dd5e 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -1737,4 +1737,40 @@ describe('@variant', () => { `) }) }) + + test('combining multiple complex variants', () => { + let compiled = compile(css` + @variant one { + &.foo-1 { + &.bar-1 { + @slot; + } + } + } + + @variant two { + &.foo-2 { + &.bar-2 { + @slot; + } + } + } + + @layer utilities { + @tailwind utilities; + } + `).build(['group-one:two:underline', 'one:group-two:underline']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { + .one\\:group-two\\:underline.foo-1.bar-1:is(:where(.group).foo-2 *):is(:where(.group).bar-2 *) { + text-decoration-line: underline; + } + + .group-one\\:two\\:underline:is(:where(.group).foo-1 *):is(:where(.group).bar-1 *).foo-2.bar-2 { + text-decoration-line: underline; + } + }" + `) + }) }) From a135c801151c382c2ef5c1ae23383f4317dc8087 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 12 Jul 2024 16:32:45 +0200 Subject: [PATCH 3/3] ensure nested selectors are not modified when using `peer-*` --- packages/tailwindcss/src/index.test.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index 89927dd5e..6a5b5615d 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -1759,7 +1759,12 @@ describe('@variant', () => { @layer utilities { @tailwind utilities; } - `).build(['group-one:two:underline', 'one:group-two:underline']) + `).build([ + 'group-one:two:underline', + 'one:group-two:underline', + 'peer-one:two:underline', + 'one:peer-two:underline', + ]) expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` "@layer utilities { @@ -1767,9 +1772,17 @@ describe('@variant', () => { text-decoration-line: underline; } + .one\\:peer-two\\:underline.foo-1.bar-1:is(:where(.peer).foo-2 ~ *):is(:where(.peer).bar-2 ~ *) { + text-decoration-line: underline; + } + .group-one\\:two\\:underline:is(:where(.group).foo-1 *):is(:where(.group).bar-1 *).foo-2.bar-2 { text-decoration-line: underline; } + + .peer-one\\:two\\:underline:is(:where(.peer).foo-1 ~ *):is(:where(.peer).bar-1 ~ *).foo-2.bar-2 { + text-decoration-line: underline; + } }" `) })