Ensure first argument to var(…) still unescapes \_ (#16206)
Resolves #16170 This PR fixes an issue where the previously opted-out escaping of the first argument for the `var(…)` function was not unescaped at all. This was introduced in https://github.com/tailwindlabs/tailwindcss/pull/14776 where the intention was to not require escaping of underscores in the var function (e.g. `ml-[var(--spacing-1_5)]`). However, I do think it still makes sense to unescape an eventually escaped underline for consistency. ## Test plan The example from #1670 now parses as expected: <img width="904" alt="Screenshot 2025-02-03 at 13 51 35" src="https://github.com/user-attachments/assets/cac0f06e-37da-4dcb-a554-9606d144a8d5" /> --------- Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This commit is contained in:
parent
e1a85ac260
commit
9fd6766308
6 changed files with 157 additions and 10 deletions
|
|
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Fixed
|
||||
|
||||
- Ensure CSS variables in arbitrary values are properly decoded ([#16206](https://github.com/tailwindlabs/tailwindcss/pull/16206))
|
||||
- Ensure that the `containers` JS theme key is added to the `--container-*` namespace ([#16169](https://github.com/tailwindlabs/tailwindcss/pull/16169))
|
||||
- Fix missing `@keyframes` definition ([#16237](https://github.com/tailwindlabs/tailwindcss/pull/16237))
|
||||
- Vite: Skip parsing stylesheets with the `?commonjs-proxy` flag ([#16238](https://github.com/tailwindlabs/tailwindcss/pull/16238))
|
||||
|
|
|
|||
|
|
@ -110,6 +110,10 @@ const candidates = [
|
|||
['!bg-[#0088cc]', 'bg-[#0088cc]!'],
|
||||
['bg-[var(--spacing)-1px]', 'bg-[var(--spacing)-1px]'],
|
||||
['bg-[var(--spacing)_-_1px]', 'bg-[var(--spacing)-1px]'],
|
||||
['bg-[var(--_spacing)]', 'bg-(--_spacing)'],
|
||||
['bg-(--_spacing)', 'bg-(--_spacing)'],
|
||||
['bg-[var(--\_spacing)]', 'bg-(--_spacing)'],
|
||||
['bg-(--\_spacing)', 'bg-(--_spacing)'],
|
||||
['bg-[-1px_-1px]', 'bg-[-1px_-1px]'],
|
||||
['p-[round(to-zero,1px)]', 'p-[round(to-zero,1px)]'],
|
||||
['w-1/2', 'w-1/2'],
|
||||
|
|
|
|||
|
|
@ -241,13 +241,8 @@ function recursivelyEscapeUnderscores(ast: ValueParser.ValueAstNode[]) {
|
|||
node.value === 'theme' ||
|
||||
node.value.endsWith('_theme')
|
||||
) {
|
||||
// Don't decode underscores in the first argument of var() and theme()
|
||||
// but do decode the function name
|
||||
node.value = escapeUnderscore(node.value)
|
||||
for (let i = 0; i < node.nodes.length; i++) {
|
||||
if (i == 0 && node.nodes[i].kind === 'word') {
|
||||
continue
|
||||
}
|
||||
recursivelyEscapeUnderscores([node.nodes[i]])
|
||||
}
|
||||
break
|
||||
|
|
|
|||
|
|
@ -935,6 +935,154 @@ it('should parse a utility with an implicit variable as the modifier', () => {
|
|||
`)
|
||||
})
|
||||
|
||||
it('should properly decode escaped underscores but not convert underscores to spaces for CSS variables in arbitrary positions', () => {
|
||||
let utilities = new Utilities()
|
||||
utilities.functional('flex', () => [])
|
||||
let variants = new Variants()
|
||||
variants.functional('supports', () => {})
|
||||
|
||||
expect(run('flex-(--\\_foo)', { utilities, variants })).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"important": false,
|
||||
"kind": "functional",
|
||||
"modifier": null,
|
||||
"raw": "flex-(--\\_foo)",
|
||||
"root": "flex",
|
||||
"value": {
|
||||
"dataType": null,
|
||||
"kind": "arbitrary",
|
||||
"value": "var(--_foo)",
|
||||
},
|
||||
"variants": [],
|
||||
},
|
||||
]
|
||||
`)
|
||||
expect(run('flex-(--_foo)', { utilities, variants })).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"important": false,
|
||||
"kind": "functional",
|
||||
"modifier": null,
|
||||
"raw": "flex-(--_foo)",
|
||||
"root": "flex",
|
||||
"value": {
|
||||
"dataType": null,
|
||||
"kind": "arbitrary",
|
||||
"value": "var(--_foo)",
|
||||
},
|
||||
"variants": [],
|
||||
},
|
||||
]
|
||||
`)
|
||||
expect(run('flex-[var(--\\_foo)]', { utilities, variants })).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"important": false,
|
||||
"kind": "functional",
|
||||
"modifier": null,
|
||||
"raw": "flex-[var(--\\_foo)]",
|
||||
"root": "flex",
|
||||
"value": {
|
||||
"dataType": null,
|
||||
"kind": "arbitrary",
|
||||
"value": "var(--_foo)",
|
||||
},
|
||||
"variants": [],
|
||||
},
|
||||
]
|
||||
`)
|
||||
expect(run('flex-[var(--_foo)]', { utilities, variants })).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"important": false,
|
||||
"kind": "functional",
|
||||
"modifier": null,
|
||||
"raw": "flex-[var(--_foo)]",
|
||||
"root": "flex",
|
||||
"value": {
|
||||
"dataType": null,
|
||||
"kind": "arbitrary",
|
||||
"value": "var(--_foo)",
|
||||
},
|
||||
"variants": [],
|
||||
},
|
||||
]
|
||||
`)
|
||||
|
||||
expect(run('flex-[calc(var(--\\_foo)*0.2)]', { utilities, variants })).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"important": false,
|
||||
"kind": "functional",
|
||||
"modifier": null,
|
||||
"raw": "flex-[calc(var(--\\_foo)*0.2)]",
|
||||
"root": "flex",
|
||||
"value": {
|
||||
"dataType": null,
|
||||
"kind": "arbitrary",
|
||||
"value": "calc(var(--_foo) * 0.2)",
|
||||
},
|
||||
"variants": [],
|
||||
},
|
||||
]
|
||||
`)
|
||||
expect(run('flex-[calc(var(--_foo)*0.2)]', { utilities, variants })).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"important": false,
|
||||
"kind": "functional",
|
||||
"modifier": null,
|
||||
"raw": "flex-[calc(var(--_foo)*0.2)]",
|
||||
"root": "flex",
|
||||
"value": {
|
||||
"dataType": null,
|
||||
"kind": "arbitrary",
|
||||
"value": "calc(var(--_foo) * 0.2)",
|
||||
},
|
||||
"variants": [],
|
||||
},
|
||||
]
|
||||
`)
|
||||
|
||||
// Due to limitations in the CSS value parser, the `var(…)` inside the `calc(…)` is not correctly
|
||||
// scanned here.
|
||||
expect(run('flex-[calc(0.2*var(--\\_foo)]', { utilities, variants })).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"important": false,
|
||||
"kind": "functional",
|
||||
"modifier": null,
|
||||
"raw": "flex-[calc(0.2*var(--\\_foo)]",
|
||||
"root": "flex",
|
||||
"value": {
|
||||
"dataType": null,
|
||||
"kind": "arbitrary",
|
||||
"value": "calc(0.2 * var(--_foo))",
|
||||
},
|
||||
"variants": [],
|
||||
},
|
||||
]
|
||||
`)
|
||||
expect(run('flex-[calc(0.2*var(--_foo)]', { utilities, variants })).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"important": false,
|
||||
"kind": "functional",
|
||||
"modifier": null,
|
||||
"raw": "flex-[calc(0.2*var(--_foo)]",
|
||||
"root": "flex",
|
||||
"value": {
|
||||
"dataType": null,
|
||||
"kind": "arbitrary",
|
||||
"value": "calc(0.2 * var(-- foo))",
|
||||
},
|
||||
"variants": [],
|
||||
},
|
||||
]
|
||||
`)
|
||||
})
|
||||
|
||||
it('should parse a utility with an implicit variable as the modifier using the shorthand', () => {
|
||||
let utilities = new Utilities()
|
||||
utilities.functional('bg', () => [])
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export function decodeArbitraryValue(input: string): string {
|
|||
* Convert `_` to ` `, except for escaped underscores `\_` they should be
|
||||
* converted to `_` instead.
|
||||
*/
|
||||
function convertUnderscoresToWhitespace(input: string) {
|
||||
function convertUnderscoresToWhitespace(input: string, skipUnderscoreToSpace = false) {
|
||||
let output = ''
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
let char = input[i]
|
||||
|
|
@ -32,7 +32,7 @@ function convertUnderscoresToWhitespace(input: string) {
|
|||
}
|
||||
|
||||
// Unescaped underscore
|
||||
else if (char === '_') {
|
||||
else if (char === '_' && !skipUnderscoreToSpace) {
|
||||
output += ' '
|
||||
}
|
||||
|
||||
|
|
@ -61,11 +61,11 @@ function recursivelyDecodeArbitraryValues(ast: ValueParser.ValueAstNode[]) {
|
|||
node.value === 'theme' ||
|
||||
node.value.endsWith('_theme')
|
||||
) {
|
||||
// Don't decode underscores in the first argument of var() but do
|
||||
// decode the function name
|
||||
node.value = convertUnderscoresToWhitespace(node.value)
|
||||
for (let i = 0; i < node.nodes.length; i++) {
|
||||
// Don't decode underscores to spaces in the first argument of var()
|
||||
if (i == 0 && node.nodes[i].kind === 'word') {
|
||||
node.nodes[i].value = convertUnderscoresToWhitespace(node.nodes[i].value, true)
|
||||
continue
|
||||
}
|
||||
recursivelyDecodeArbitraryValues([node.nodes[i]])
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ export function App() {
|
|||
return (
|
||||
<div className="m-3 p-3 border">
|
||||
<h1 className="text-blue-500">Hello World</h1>
|
||||
<div className="-inset-x-full -inset-y-full -space-x-full -space-y-full -inset-full"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue