Skip to content

Commit ae4d213

Browse files
Improve normalisation of calc()-like functions (#11686)
* parse the `calc()`-like expressions and format them * update changelog * Add test case for double negatives wanted to be sure this worked --------- Co-authored-by: Jordan Pittman <[email protected]>
1 parent e0c52a9 commit ae4d213

File tree

3 files changed

+73
-11
lines changed

3 files changed

+73
-11
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2323
- Bump `jiti`, `lightningcss`, `fast-glob` and `browserlist` dependencies and reflect `lightningcss` related improvements in tests ([#11550](https://github.com/tailwindlabs/tailwindcss/pull/11550))
2424
- Make PostCSS plugin async to improve performance ([#11548](https://github.com/tailwindlabs/tailwindcss/pull/11548))
2525
- Allow variant to be an at-rule without a prelude ([#11589](https://github.com/tailwindlabs/tailwindcss/pull/11589))
26+
- Improve normalisation of `calc()`-like functions ([#11686](https://github.com/tailwindlabs/tailwindcss/pull/11686))
2627

2728
### Added
2829

src/util/dataTypes.js

+52-11
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@ function isCSSFunction(value) {
1010
return cssFunctions.some((fn) => new RegExp(`^${fn}\\(.*\\)`).test(value))
1111
}
1212

13-
const placeholder = '--tw-placeholder'
14-
const placeholderRe = new RegExp(placeholder, 'g')
15-
1613
// This is not a data type, but rather a function that can normalize the
1714
// correct values.
1815
export function normalize(value, isRoot = true) {
@@ -63,15 +60,59 @@ export function normalize(value, isRoot = true) {
6360
*/
6461
function normalizeMathOperatorSpacing(value) {
6562
return value.replace(/(calc|min|max|clamp)\(.+\)/g, (match) => {
66-
let vars = []
63+
let result = ''
6764

68-
return match
69-
.replace(/var\((--.+?)[,)]/g, (match, g1) => {
70-
vars.push(g1)
71-
return match.replace(g1, placeholder)
72-
})
73-
.replace(/(-?\d*\.?\d(?!\b-\d.+[,)](?![^+\-/*])\D)(?:%|[a-z]+)?|\))([+\-/*])/g, '$1 $2 ')
74-
.replace(placeholderRe, () => vars.shift())
65+
function lastChar() {
66+
let char = result.trimEnd()
67+
return char[char.length - 1]
68+
}
69+
70+
for (let i = 0; i < match.length; i++) {
71+
function peek(word) {
72+
return word.split('').every((char, j) => match[i + j] === char)
73+
}
74+
75+
function consumeUntil(chars) {
76+
let minIndex = Infinity
77+
for (let char of chars) {
78+
let index = match.indexOf(char, i)
79+
if (index !== -1 && index < minIndex) {
80+
minIndex = index
81+
}
82+
}
83+
84+
let result = match.slice(i, minIndex)
85+
i += result.length - 1
86+
return result
87+
}
88+
89+
let char = match[i]
90+
91+
// Handle `var(--variable)`
92+
if (peek('var')) {
93+
// When we consume until `)`, then we are dealing with this scenario:
94+
// `var(--example)`
95+
//
96+
// When we consume until `,`, then we are dealing with this scenario:
97+
// `var(--example, 1rem)`
98+
//
99+
// In this case we do want to "format", the default value as well
100+
result += consumeUntil([')', ','])
101+
}
102+
103+
// Handle operators
104+
else if (
105+
['+', '-', '*', '/'].includes(char) &&
106+
!['(', '+', '-', '*', '/'].includes(lastChar())
107+
) {
108+
result += ` ${char} `
109+
} else {
110+
result += char
111+
}
112+
}
113+
114+
// Simplify multiple spaces
115+
return result.replace(/\s+/g, ' ')
75116
})
76117
}
77118

tests/normalize-data-types.test.js

+20
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,26 @@ let table = [
4141
['var(--my-var-with-more-than-3-words)', 'var(--my-var-with-more-than-3-words)'],
4242
['var(--width, calc(100%+1rem))', 'var(--width, calc(100% + 1rem))'],
4343

44+
['calc(1px*(7--12/24))', 'calc(1px * (7 - -12 / 24))'],
45+
['calc((7-32)/(1400-782))', 'calc((7 - 32) / (1400 - 782))'],
46+
['calc((7-3)/(1400-782))', 'calc((7 - 3) / (1400 - 782))'],
47+
['calc((7-32)/(1400-782))', 'calc((7 - 32) / (1400 - 782))'],
48+
['calc((70-3)/(1400-782))', 'calc((70 - 3) / (1400 - 782))'],
49+
['calc((70-32)/(1400-782))', 'calc((70 - 32) / (1400 - 782))'],
50+
['calc((704-3)/(1400-782))', 'calc((704 - 3) / (1400 - 782))'],
51+
['calc((704-320))', 'calc((704 - 320))'],
52+
['calc((704-320)/1)', 'calc((704 - 320) / 1)'],
53+
['calc((704-320)/(1400-782))', 'calc((704 - 320) / (1400 - 782))'],
54+
['calc(24px+-1rem)', 'calc(24px + -1rem)'],
55+
['calc(24px+(-1rem))', 'calc(24px + (-1rem))'],
56+
['calc(24px_+_-1rem)', 'calc(24px + -1rem)'],
57+
['calc(24px+(-1rem))', 'calc(24px + (-1rem))'],
58+
['calc(24px_+_(-1rem))', 'calc(24px + (-1rem))'],
59+
[
60+
'calc(var(--10-10px,calc(-20px-(-30px--40px)-50px)',
61+
'calc(var(--10-10px,calc(-20px - (-30px - -40px) - 50px)',
62+
],
63+
4464
// Misc
4565
['color(0_0_0/1.0)', 'color(0 0 0/1.0)'],
4666
['color(0_0_0_/_1.0)', 'color(0 0 0 / 1.0)'],

0 commit comments

Comments
 (0)