From 8023be68ce3e59d1dfecaf8262062965e7f8f748 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 19 Jul 2024 13:27:00 -0400 Subject: [PATCH] wip --- packages/tailwindcss/src/variants.test.ts | 34 +++++++----- packages/tailwindcss/src/variants.ts | 64 +++++++++++------------ 2 files changed, 52 insertions(+), 46 deletions(-) diff --git a/packages/tailwindcss/src/variants.test.ts b/packages/tailwindcss/src/variants.test.ts index 4fb91e8ed..9e4a7362d 100644 --- a/packages/tailwindcss/src/variants.test.ts +++ b/packages/tailwindcss/src/variants.test.ts @@ -705,23 +705,23 @@ test('group-[...]', () => { 'group-[&:hover]:group-[&_p]:flex', ]), ).toMatchInlineSnapshot(` - ".group-\\[\\&_p\\]\\:flex:is(:where(.group) p *) { + ".group-\\[\\&_p\\]\\:flex:is(:where(.group):is(* p) *) { display: flex; } - .group-\\[\\&\\:hover\\]\\:group-\\[\\&_p\\]\\:flex:is(:where(.group):hover *):is(:where(.group) p *) { + .group-\\[\\&\\:hover\\]\\:group-\\[\\&_p\\]\\:flex:is(:where(.group):hover *):is(:where(.group):is(* p) *) { display: flex; } - .group-\\[\\&_p\\]\\:hover\\:flex:is(:where(.group) p *):hover { + .group-\\[\\&_p\\]\\:hover\\:flex:is(:where(.group):is(* p) *):hover { display: flex; } - .hover\\:group-\\[\\&_p\\]\\:flex:hover:is(:where(.group) p *) { + .hover\\:group-\\[\\&_p\\]\\:flex:hover:is(:where(.group):is(* p) *) { display: flex; } - .hover\\:group-\\[\\&_p\\]\\:hover\\:flex:hover:is(:where(.group) p *):hover { + .hover\\:group-\\[\\&_p\\]\\:hover\\:flex:hover:is(:where(.group):is(* p) *):hover { display: flex; }" `) @@ -746,6 +746,13 @@ test('group-*', () => { @slot; } } + @variant nested-selectors { + &:hover { + &:focus { + @slot; + } + } + } @tailwind utilities; `, [ @@ -755,6 +762,8 @@ test('group-*', () => { 'group-hover:group-focus:flex', 'group-focus:group-hover:flex', + + 'group-nested-selectors:flex', ], ), ).toMatchInlineSnapshot(` @@ -774,7 +783,11 @@ test('group-*', () => { display: flex; } - .group-hocus\\:flex:is(:is(:where(.group):hover, :where(.group):focus) *) { + .group-hocus\\:flex:is(:where(.group):is(:hover, :focus) *) { + display: flex; + } + + .group-nested-selectors\\:flex:is(:where(.group):focus:hover *) { display: flex; }" `) @@ -783,16 +796,9 @@ test('group-*', () => { compileCss( css` @variant custom-at-rule (@media foo); - @variant nested-selectors { - &:hover { - &:focus { - @slot; - } - } - } @tailwind utilities; `, - ['group-custom-at-rule:flex', 'group-nested-selectors:flex'], + ['group-custom-at-rule:flex'], ), ).toEqual('') }) diff --git a/packages/tailwindcss/src/variants.ts b/packages/tailwindcss/src/variants.ts index 47e9ae16b..22a59495c 100644 --- a/packages/tailwindcss/src/variants.ts +++ b/packages/tailwindcss/src/variants.ts @@ -239,9 +239,7 @@ export function createVariants(theme: Theme): Variants { return null } - console.log({ groups }) - - ruleNode.selector = '&' + groups.join('') + ruleNode.selector = `&:is(${groups.join('')})` ruleNode.nodes = [] }) @@ -252,50 +250,52 @@ export function createVariants(theme: Theme): Variants { ? `:where(.group\\/${variant.modifier.value})` : ':where(.group)' - let didApply = false + let groups: string[] = [] - walk([ruleNode], (node) => { + walk([ruleNode], (node, { path }) => { if (node.kind !== 'rule') return WalkAction.Continue // Skip past at-rules, and continue traversing the children of the at-rule if (node.selector[0] === '@') return WalkAction.Continue - // Throw out any candidates with variants using nested selectors - if (didApply) { - walk([node], (childNode) => { - if (childNode.kind !== 'rule' || childNode.selector[0] === '@') return WalkAction.Continue + // 1. Walk the tree until we find `@slot` + if (node.nodes.length !== 0) return WalkAction.Continue - didApply = false - return WalkAction.Stop - }) + let selectors = [] - return didApply ? WalkAction.Skip : WalkAction.Stop + // 2. Collect the selectors of the parents + for (let ancestor of path) { + if (ancestor.kind !== 'rule') continue + + // Skip past at-rules, and continue traversing the children of the at-rule + if (ancestor.selector[0] === '@') continue + + // Skip over the "root" node + if (ancestor.selector === '&') continue + + // For most variants we rely entirely on CSS nesting to build-up the final + // selector, but there is no way to use CSS nesting to make `&` refer to + // just the `.group` class the way we'd need to for these variants, so we + // need to replace it in the selector ourselves. + let selector = ancestor.selector.replaceAll('&', '*') + + // When the selector is a selector _list_ we need to wrap it in `:is` + // to make sure the matching behavior is consistent with the original + // variant / selector. + selectors.push(`:is(${selector})`) } - // For most variants we rely entirely on CSS nesting to build-up the final - // selector, but there is no way to use CSS nesting to make `&` refer to - // just the `.group` class the way we'd need to for these variants, so we - // need to replace it in the selector ourselves. - node.selector = node.selector.replaceAll('&', groupSelector) + if (selectors.length === 0) return - // When the selector is a selector _list_ we need to wrap it in `:is` - // to make sure the matching behavior is consistent with the original - // variant / selector. - if (segment(node.selector, ',').length > 1) { - node.selector = `:is(${node.selector})` - } - - node.selector = `&:is(${node.selector} *)` - - // Track that the variant was actually applied - didApply = true + groups.push(selectors.join('')) }) - // If the node wasn't modified, this variant is not compatible with - // `group-*` so discard the candidate. - if (!didApply) { + if (groups.length === 0) { return null } + + ruleNode.selector = `&:is(${groupSelector}:is(${groups.join(',')}) *)` + ruleNode.nodes = [] }) variants.suggest('group', () => {