Skip to content

Commit 1085fe8

Browse files
fix: better deal with mixed unit math computations (#308)
1 parent ae75978 commit 1085fe8

File tree

3 files changed

+29
-7
lines changed

3 files changed

+29
-7
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tokens-studio/sd-transforms': patch
3+
---
4+
5+
Improve math compute utility to better deal with mixed units computations. Expand on tests.

src/checkAndEvaluateMath.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,17 @@ function parseAndReduce(expr: string, fractionDigits = defaultFractionDigits): s
120120
}
121121

122122
if (typeof result !== 'number') {
123+
let exprToParse = noPixExpr;
124+
// math operators, excluding *
125+
// (** or ^ exponent would theoretically be fine, but postcss-calc-ast-parser does not support it
126+
const operatorsRegex = /[/+%-]/g;
127+
// if we only have * operator, we can consider expression as unitless and compute it that way
128+
// we already know we dont have mixed units from (foundUnits.size > 1) guard above
129+
if (!exprToParse.match(operatorsRegex)) {
130+
exprToParse = exprToParse.replace(new RegExp(resultUnit, 'g'), '');
131+
}
123132
// Try to evaluate as postcss-calc-ast-parser expression
124-
const calcParsed = parse(noPixExpr, { allowInlineCommnets: false });
133+
const calcParsed = parse(exprToParse, { allowInlineCommnets: false });
125134

126135
// Attempt to reduce the math expression
127136
const reduced = reduceExpression(calcParsed);

test/spec/checkAndEvaluateMath.spec.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,21 @@ describe('check and evaluate math', () => {
2929
expect(checkAndEvaluateMath({ value: '4 * 7px * 8px', type: 'dimension' })).to.equal('224px');
3030
});
3131

32-
it('cannot evaluate math expressions where more than one token has a unit, when unit is not px', () => {
33-
expect(checkAndEvaluateMath({ value: '4em * 7em', type: 'dimension' })).to.equal('4em * 7em');
34-
expect(checkAndEvaluateMath({ value: '4 * 7em * 8em', type: 'dimension' })).to.equal(
35-
'4 * 7em * 8em',
36-
);
37-
// exception for pixels, it strips px, making it 4 * 7em = 28em = 448px, where 4px * 7em would be 4px * 112px = 448px as well
32+
it('can evaluate math expressions where more than one token has a unit, assuming no mixed units are used', () => {
33+
expect(checkAndEvaluateMath({ value: '4em + 7em', type: 'dimension' })).to.equal('11em');
34+
expect(checkAndEvaluateMath({ value: '4 + 7rem', type: 'dimension' })).to.equal('4 + 7rem');
35+
expect(checkAndEvaluateMath({ value: '4em + 7rem', type: 'dimension' })).to.equal('4em + 7rem');
36+
});
37+
38+
it('can evaluate mixed units if operators are exclusively multiplication and the mix is px or unitless', () => {
39+
expect(checkAndEvaluateMath({ value: '4 * 7em * 8em', type: 'dimension' })).to.equal('224em');
3840
expect(checkAndEvaluateMath({ value: '4px * 7em', type: 'dimension' })).to.equal('28em');
41+
// 50em would be incorrect when dividing, as em grows, result should shrink, but doesn't
42+
expect(checkAndEvaluateMath({ value: '1000 / 20em', type: 'dimension' })).to.equal(
43+
'1000 / 20em',
44+
);
45+
// cannot be expressed/resolved without knowing the value of em
46+
expect(checkAndEvaluateMath({ value: '4px + 7em', type: 'dimension' })).to.equal('4px + 7em');
3947
});
4048

4149
it('can evaluate math expressions where more than one token has a unit, as long as for each piece of the expression the unit is the same', () => {

0 commit comments

Comments
 (0)