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>
This commit is contained in:
Adam Wathan 2024-09-06 09:00:28 -04:00 • committed by GitHub
parent f028eae75e
commit 7b59aac274
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 43 additions and 14 deletions

View file

@ -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

View file

@ -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<string, unknown> = {}
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

View file

@ -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;