diff --git a/CHANGELOG.md b/CHANGELOG.md index 710d4a14f..74f72f10e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Detect utilities when containing capital letters followed by numbers ([#19465](https://github.com/tailwindlabs/tailwindcss/pull/19465)) - Fix class extraction for Rails' strict locals ([#19525](https://github.com/tailwindlabs/tailwindcss/pull/19525)) - Align `@utility` name validation with Oxide scanner rules ([#19524](https://github.com/tailwindlabs/tailwindcss/pull/19524)) +- Fix infinite loop when using `@variant` inside `@custom-variant` ([#19633](https://github.com/tailwindlabs/tailwindcss/pull/19633)) ### Deprecated diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index 94b17714e..5182cdd14 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -4558,6 +4558,97 @@ describe('@custom-variant', () => { `) }) + // https://github.com/tailwindlabs/tailwindcss/issues/19618 + test('@custom-variant can use a @variant that eventually uses another @custom-variant', async () => { + expect( + await compileCss( + css` + @custom-variant a { + @slot; + } + + @custom-variant b { + @variant a { + @slot; + } + } + + @tailwind utilities; + `, + ['a:flex', 'b:flex', 'a:b:flex', 'b:a:flex'], + ), + ).toMatchInlineSnapshot(` + ".a\\:flex, .b\\:flex, .a\\:b\\:flex, .b\\:a\\:flex { + display: flex; + }" + `) + }) + + test('@custom-variant can use a @variant that eventually uses another @custom-variant (2)', async () => { + expect( + await compileCss( + css` + @custom-variant a { + .a { + @slot; + } + } + + @custom-variant b { + .b { + @variant a { + .a-inside-b { + @slot; + } + } + } + } + + @tailwind utilities; + `, + ['a:flex', 'b:flex', 'a:b:flex', 'b:a:flex'], + ), + ).toMatchInlineSnapshot(` + ".a\\:flex .a, .b\\:flex .b .a .a-inside-b, .a\\:b\\:flex .a .b .a .a-inside-b, .b\\:a\\:flex .b .a .a-inside-b .a { + display: flex; + }" + `) + }) + + // https://github.com/tailwindlabs/tailwindcss/issues/19618#issuecomment-3830775912 + test('@custom-variant can use existing @slot @variants', async () => { + expect( + await compileCss( + css` + @custom-variant hocus { + @variant hover { + @variant focus { + @slot; + } + } + } + + @custom-variant hover { + &:hover { + @slot; + } + + &[data-hover] { + @slot; + } + } + + @tailwind utilities; + `, + ['hocus:flex'], + ), + ).toMatchInlineSnapshot(` + ".hocus\\:flex:hover:focus, .hocus\\:flex[data-hover]:focus { + display: flex; + }" + `) + }) + test('@custom-variant setup that results in a circular dependency error can be solved', async () => { expect( await compileCss( diff --git a/packages/tailwindcss/src/variants.ts b/packages/tailwindcss/src/variants.ts index 82a2b8592..0b4bcb0c5 100644 --- a/packages/tailwindcss/src/variants.ts +++ b/packages/tailwindcss/src/variants.ts @@ -1196,7 +1196,7 @@ export function substituteAtSlot(ast: AstNode[], nodes: AstNode[]) { walk(ast, (node) => { // Replace `@slot` with rule nodes if (node.kind === 'at-rule' && node.name === '@slot') { - return WalkAction.Replace(nodes) + return WalkAction.ReplaceSkip(nodes) } // Wrap `@keyframes` and `@property` in `AtRoot` nodes