From d987f9bf1826f02be04c266eb3e313581a93095e Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Thu, 27 Feb 2025 15:29:09 +0100 Subject: [PATCH] Emit `@keyframes` in prefixed setup --- CHANGELOG.md | 1 + packages/tailwindcss/src/ast.ts | 2 +- packages/tailwindcss/src/index.test.ts | 67 ++++++++++++++++++++++++++ packages/tailwindcss/src/theme.ts | 6 +-- 4 files changed, 72 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e8d8577d8c0..0d817dcdd8a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Ensure `not-*` does not remove `:is(…)` from variants ([#16825](https://github.com/tailwindlabs/tailwindcss/pull/16825)) +- Ensure `@keyframes` are correctly emitted when using a prefixed setup ([#16850](https://github.com/tailwindlabs/tailwindcss/pull/16850)) ## [4.0.9] - 2025-02-25 diff --git a/packages/tailwindcss/src/ast.ts b/packages/tailwindcss/src/ast.ts index bd99347974f1..39b6770e25cc 100644 --- a/packages/tailwindcss/src/ast.ts +++ b/packages/tailwindcss/src/ast.ts @@ -429,7 +429,7 @@ export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { variableDependencies, ) if (variableUsed) { - if (declaration.property.startsWith('--animate-')) { + if (declaration.property.startsWith(designSystem.theme.prefixKey('--animate-'))) { let parts = declaration.value!.split(/\s+/) for (let part of parts) usedKeyframeNames.add(part) } diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index b6f1e4781637..ea07499b61ff 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -1613,6 +1613,73 @@ describe('Parsing theme values from CSS', () => { `) }) + test('keyframes are generated when used in an animation within a prefixed setup', async () => { + expect( + await compileCss( + css` + @theme prefix(tw) { + --animate-foo: used 1s infinite; + --animate-bar: unused 1s infinite; + + @keyframes used { + to { + opacity: 1; + } + } + + @keyframes unused { + to { + opacity: 0; + } + } + } + + @tailwind utilities; + `, + ['tw:animate-foo'], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --tw-animate-foo: used 1s infinite; + } + + .tw\\:animate-foo { + animation: var(--tw-animate-foo); + } + + @keyframes used { + to { + opacity: 1; + } + }" + `) + }) + + test('custom properties are generated when used from a CSS var with a prefixed setup', async () => { + expect( + await compileCss( + css` + @theme prefix(tw) { + --color-tomato: #e10c04; + } + @tailwind utilities; + .red { + color: var(--tw-color-tomato); + } + `, + [], + ), + ).toMatchInlineSnapshot(` + ":root, :host { + --tw-color-tomato: #e10c04; + } + + .red { + color: var(--tw-color-tomato); + }" + `) + }) + // https://github.com/tailwindlabs/tailwindcss/issues/16374 test('custom properties in keyframes preserved', async () => { expect( diff --git a/packages/tailwindcss/src/theme.ts b/packages/tailwindcss/src/theme.ts index d38d5918b33d..d5fca3b02d3d 100644 --- a/packages/tailwindcss/src/theme.ts +++ b/packages/tailwindcss/src/theme.ts @@ -117,12 +117,12 @@ export class Theme { if (!this.prefix) return this.values.entries() return Array.from(this.values, (entry) => { - entry[0] = this.#prefixKey(entry[0]) + entry[0] = this.prefixKey(entry[0]) return entry }) } - #prefixKey(key: string) { + prefixKey(key: string) { if (!this.prefix) return key return `--${this.prefix}-${key.slice(2)}` } @@ -190,7 +190,7 @@ export class Theme { fallback = value.value } - return `var(${escape(this.#prefixKey(themeKey))}${fallback ? `, ${fallback}` : ''})` + return `var(${escape(this.prefixKey(themeKey))}${fallback ? `, ${fallback}` : ''})` } markUsedVariable(themeKey: string) {