Fix var(…) as the opacity value inside the theme(…) function (#14653)

Inside the `theme(…)` function, we can use the `/` character for
applying an opacity. For example `theme(colors.red.500 / 50%)` will
apply a 50% opacity to the `colors.red.500` value.

However, if you used a variable instead of the hardcoded `50%` value,
then this was not parsed correctly. E.g.: `theme(colors.red.500 /
var(--opacity))`

_If_ we have this exact syntax (with the spaces), then it parses, but
some information is lost:

```html
<div class="bg-[theme(colors.red.500_/_var(--opacity))]"></div>
```

Results in:
```css
.bg-\[theme\(colors\.red\.500_\/_var\(--opacity\)\)\] {
  background-color: color-mix(in srgb, #ef4444 calc(var * 100%), transparent);
}
```
Notice that the `var(--opacity)` is just parsed as `var`, and the
`--opacity` is lost.

Additionally, if we drop the spaces, then it doesn't parse at all:

```html
<div class="bg-[theme(colors.red.500/var(--opacity))]"></div>
```

Results in:
```css
```

This means that we have to handle 2 issues to make this work:
1. We have to properly handle the `/` character as a proper separator.
2. If we have sub-functions, we have to make sure to print them in full
(instead of only the very first node (`var` in this case)).
This commit is contained in:
Robin Malfait 2024-10-14 14:08:41 +02:00 committed by GitHub
parent a64e209888
commit f2ebb8eb82
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 82 additions and 1 deletions

View File

@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Pass options when using `addComponents` and `matchComponents` ([#14590](https://github.com/tailwindlabs/tailwindcss/pull/14590))
- Ensure `boxShadow` and `animation` theme keys in JS config files are accessible under `--shadow-*` and `--animate-*` using the `theme()` function ([#14642](https://github.com/tailwindlabs/tailwindcss/pull/14642))
- Ensure all theme keys with new names are also accessible under their old names when using the `theme()` function with the legacy dot notation syntax ([#14642](https://github.com/tailwindlabs/tailwindcss/pull/14642))
- Ensure `var(…)` can be used as the opacity value inside the `theme([path] / [modifier])` function ([#14653](https://github.com/tailwindlabs/tailwindcss/pull/14653))
- _Upgrade (experimental)_: Ensure CSS before a layer stays unlayered when running codemods ([#14596](https://github.com/tailwindlabs/tailwindcss/pull/14596))
- _Upgrade (experimental)_: Resolve issues where some prefixed candidates were not properly migrated ([#14600](https://github.com/tailwindlabs/tailwindcss/pull/14600))

View File

@ -136,6 +136,27 @@ describe('theme function', () => {
`)
})
test('theme(colors.red.500/75%)', async () => {
expect(
await compileCss(css`
@theme {
--color-red-500: #f00;
}
.red {
color: theme(colors.red.500/75%);
}
`),
).toMatchInlineSnapshot(`
":root {
--color-red-500: red;
}
.red {
color: #ff0000bf;
}"
`)
})
test('theme(colors.red.500 / 75%)', async () => {
expect(
await compileCss(css`
@ -178,6 +199,49 @@ describe('theme function', () => {
`)
})
test('theme(colors.red.500/var(--opacity))', async () => {
expect(
await compileCss(css`
@theme {
--color-red-500: #f00;
}
.red {
color: theme(colors.red.500/var(--opacity));
}
`),
).toMatchInlineSnapshot(`
":root {
--color-red-500: red;
}
.red {
color: color-mix(in srgb, red calc(var(--opacity) * 100%), transparent);
}"
`)
})
test('theme(colors.red.500/var(--opacity,50%))', async () => {
expect(
await compileCss(css`
@theme {
--color-red-500: #f00;
}
.red {
/* prettier-ignore */
color: theme(colors.red.500/var(--opacity,50%));
}
`),
).toMatchInlineSnapshot(`
":root {
--color-red-500: red;
}
.red {
color: color-mix(in srgb, red calc(var(--opacity, 50%) * 100%), transparent);
}"
`)
})
test('theme(spacing.12)', async () => {
expect(
await compileCss(css`

View File

@ -68,7 +68,7 @@ export function substituteFunctionsInValue(
if (node.nodes[i].value.includes(',')) {
break
}
path += node.nodes[i].value
path += ValueParser.toCss([node.nodes[i]])
skipUntilIndex = i + 1
}

View File

@ -66,6 +66,20 @@ describe('parse', () => {
])
})
it('should parse a function with nested arguments separated by `/`', () => {
expect(parse('theme(colors.red.500/var(--opacity))')).toEqual([
{
kind: 'function',
value: 'theme',
nodes: [
{ kind: 'word', value: 'colors.red.500' },
{ kind: 'separator', value: '/' },
{ kind: 'function', value: 'var', nodes: [{ kind: 'word', value: '--opacity' }] },
],
},
])
})
it('should handle calculations', () => {
expect(parse('calc((1 + 2) * 3)')).toEqual([
{

View File

@ -114,6 +114,7 @@ const SPACE = 0x20
const LESS_THAN = 0x3c
const GREATER_THAN = 0x3e
const EQUALS = 0x3d
const SLASH = 0x2f
export function parse(input: string) {
input = input.replaceAll('\r\n', '\n')
@ -143,6 +144,7 @@ export function parse(input: string) {
case COLON:
case COMMA:
case SPACE:
case SLASH:
case LESS_THAN:
case GREATER_THAN:
case EQUALS: {