From 7b59aac2746c8b3ca316366737258f9b0cb311b0 Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Fri, 6 Sep 2024 09:00:28 -0400 Subject: [PATCH] Properly resolve `theme('someKey.DEFAULT')` when only `--some-key-*` keys exist (#14354) This PR fixes an issue where theme function calls like `theme('transitionTimingFunction.DEFAULT')` would incorrectly resolve to an object when the set of defined CSS theme values looked like this: ```css @theme { --transition-timing-function-in: ease-in; --transition-timing-function-out: ease-out; --transition-timing-function-in-out: ease-out; } ``` We were mistakenly retrieving the entire `--transition-timing-function-*` namespace in this case and returning an object, even though the user is explicitly asking for a single value by including `.DEFAULT` in their call. This ensures it resolves to null instead. Fixes an issue I ran into on this live stream earlier today: https://x.com/adamwathan/status/1831740214051799281 --------- Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com> --- CHANGELOG.md | 1 + .../src/compat/plugin-functions.ts | 27 +++++++++-------- packages/tailwindcss/src/plugin-api.test.ts | 29 +++++++++++++++++++ 3 files changed, 43 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1089c156e..9111c69af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Ensure there is always CLI feedback on save even when no new classes were found ([#14351](https://github.com/tailwindlabs/tailwindcss/pull/14351)) +- Properly resolve `theme('someKey.DEFAULT')` when all `--some-key-*` keys have a suffix ([#14354](https://github.com/tailwindlabs/tailwindcss/pull/14354)) ## [4.0.0-alpha.23] - 2024-09-05 diff --git a/packages/tailwindcss/src/compat/plugin-functions.ts b/packages/tailwindcss/src/compat/plugin-functions.ts index ef0a6d0c8..810953aab 100644 --- a/packages/tailwindcss/src/compat/plugin-functions.ts +++ b/packages/tailwindcss/src/compat/plugin-functions.ts @@ -115,7 +115,7 @@ function readFromCss(theme: Theme, path: string[]) { } // We have to turn the map into object-like structure for v3 compatibility - let obj = {} + let obj: Record = {} let useNestedObjects = false // paths.some((path) => nestedKeys.has(path)) for (let [key, value] of map) { @@ -134,20 +134,19 @@ function readFromCss(theme: Theme, path: string[]) { set(obj, path, value) } - if ('DEFAULT' in obj) { - // The request looked like `theme('animation.DEFAULT')` and was turned into - // a lookup for `--animation-*` and we should extract the value for the - // `DEFAULT` key from the list of possible values - if (path[path.length - 1] === 'DEFAULT') { - return obj.DEFAULT - } + // If the request looked like `theme('animation.DEFAULT')` it would have been + // turned into a lookup for `--animation-*` so we should extract the value for + // the `DEFAULT` key from the list of possible values. If there is no + // `DEFAULT` in the list, there is no match so return `null`. + if (path[path.length - 1] === 'DEFAULT') { + return obj?.DEFAULT ?? null + } - // The request looked like `theme('animation.spin')` and was turned into a - // lookup for `--animation-spin-*` which had only one entry which means it - // should be returned directly - if (Object.keys(obj).length === 1) { - return obj.DEFAULT - } + // The request looked like `theme('animation.spin')` and was turned into a + // lookup for `--animation-spin-*` which had only one entry which means it + // should be returned directly. + if ('DEFAULT' in obj && Object.keys(obj).length === 1) { + return obj.DEFAULT } return obj diff --git a/packages/tailwindcss/src/plugin-api.test.ts b/packages/tailwindcss/src/plugin-api.test.ts index 087dea0bc..e7ed5e8a6 100644 --- a/packages/tailwindcss/src/plugin-api.test.ts +++ b/packages/tailwindcss/src/plugin-api.test.ts @@ -804,6 +804,35 @@ describe('theme', async () => { expect(fn).toHaveBeenCalledWith('blue') }) + test("`theme('*.DEFAULT')` resolves to `undefined` when all theme keys in that namespace have a suffix", async ({ + expect, + }) => { + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + @theme { + --transition-timing-function-in: ease-in; + --transition-timing-function-out: ease-out; + } + ` + + let fn = vi.fn() + + await compile(input, { + loadPlugin: async () => { + return plugin(({ theme }) => { + fn(theme('transitionTimingFunction.DEFAULT')) + fn(theme('transitionTimingFunction.in')) + fn(theme('transitionTimingFunction.out')) + }) + }, + }) + + expect(fn).toHaveBeenNthCalledWith(1, undefined) + expect(fn).toHaveBeenNthCalledWith(2, 'ease-in') + expect(fn).toHaveBeenNthCalledWith(3, 'ease-out') + }) + test('nested theme key lookups work even for flattened keys', async ({ expect }) => { let input = css` @tailwind utilities;