diff --git a/.changeset/two-jokes-give.md b/.changeset/two-jokes-give.md new file mode 100644 index 0000000..0fd4ad4 --- /dev/null +++ b/.changeset/two-jokes-give.md @@ -0,0 +1,5 @@ +--- +'@tokens-studio/sd-transforms': minor +--- + +Allow changing the resolve math transform amount of decimals to round for using platform options `mathFractionDigits`, change default value from 3 to 4. diff --git a/README.md b/README.md index c75b58f..47e4a65 100644 --- a/README.md +++ b/README.md @@ -487,6 +487,26 @@ This transform checks and evaluates math expressions **matches**: All tokens that have string values. +You can adjust to how many decimals the result should be rounded using `PlatformConfig.mathFractionDigits`: + +```json +{ + "source": ["tokens.json"], + "platforms": { + "css": { + "mathFractionDigits": 3, + "transformGroup": "tokens-studio", + "files": [ + { + "format": "css/variables", + "destination": "output.css" + } + ] + } + } +} +``` + #### before ```json diff --git a/src/checkAndEvaluateMath.ts b/src/checkAndEvaluateMath.ts index 324266b..26048af 100644 --- a/src/checkAndEvaluateMath.ts +++ b/src/checkAndEvaluateMath.ts @@ -2,6 +2,7 @@ import { DesignToken } from 'style-dictionary/types'; import { Parser } from 'expr-eval-fork'; import { parse, reduceExpression } from '@bundled-es-modules/postcss-calc-ast-parser'; +const defaultFractionDigits = 4; const mathChars = ['+', '-', '*', '/']; const parser = new Parser(); @@ -74,7 +75,7 @@ function splitMultiIntoSingleValues(expr: string): string[] { return [expr]; } -function parseAndReduce(expr: string): string | number { +function parseAndReduce(expr: string, fractionDigits = defaultFractionDigits): string | number { let result: string | number = expr; let evaluated; @@ -127,12 +128,15 @@ function parseAndReduce(expr: string): string | number { } // the outer Number() gets rid of insignificant trailing zeros of decimal numbers - const reducedTo3Fixed = Number(Number.parseFloat(`${result}`).toFixed(3)); - result = resultUnit ? `${reducedTo3Fixed}${resultUnit}` : reducedTo3Fixed; + const reducedToFixed = Number(Number.parseFloat(`${result}`).toFixed(fractionDigits)); + result = resultUnit ? `${reducedToFixed}${resultUnit}` : reducedToFixed; return result; } -export function checkAndEvaluateMath(token: DesignToken): DesignToken['value'] { +export function checkAndEvaluateMath( + token: DesignToken, + fractionDigits?: number, +): DesignToken['value'] { const expr = token.$value ?? token.value; const type = token.$type ?? token.type; @@ -145,7 +149,7 @@ export function checkAndEvaluateMath(token: DesignToken): DesignToken['value'] { return expr; } const exprs = splitMultiIntoSingleValues(expr); - const reducedExprs = exprs.map(_expr => parseAndReduce(_expr)); + const reducedExprs = exprs.map(_expr => parseAndReduce(_expr, fractionDigits)); if (reducedExprs.length === 1) { return reducedExprs[0]; } diff --git a/src/register.ts b/src/register.ts index ec4e070..169812e 100644 --- a/src/register.ts +++ b/src/register.ts @@ -73,7 +73,7 @@ export async function register(sd: typeof StyleDictionary, transformOpts?: Trans type: 'value', transitive: true, filter: token => ['string', 'object'].includes(typeof (token.$value ?? token.value)), - transform: token => checkAndEvaluateMath(token), + transform: (token, config) => checkAndEvaluateMath(token, config.options?.mathFractionDigits), }); sd.registerTransform({ diff --git a/test/spec/checkAndEvaluateMath.spec.ts b/test/spec/checkAndEvaluateMath.spec.ts index 91fd710..9f5b63e 100644 --- a/test/spec/checkAndEvaluateMath.spec.ts +++ b/test/spec/checkAndEvaluateMath.spec.ts @@ -1,6 +1,8 @@ import { expect } from 'chai'; import { checkAndEvaluateMath } from '../../src/checkAndEvaluateMath.js'; import { runTransformSuite } from '../suites/transform-suite.spec.js'; +import { cleanup, init } from '../integration/utils.js'; +import { TransformedToken } from 'style-dictionary/types'; runTransformSuite(checkAndEvaluateMath as (value: unknown) => unknown, {}); @@ -15,7 +17,7 @@ describe('check and evaluate math', () => { expect(checkAndEvaluateMath({ value: '4 * 7rem', type: 'dimension' })).to.equal('28rem'); expect( checkAndEvaluateMath({ value: '(15 + 20 - 17 * 8 / 3) * 7px', type: 'dimension' }), - ).to.equal('-72.333px'); + ).to.equal('-72.3333px'); }); it('supports expression of type number', () => { @@ -110,6 +112,38 @@ describe('check and evaluate math', () => { ); }); + it('allows a `mathFractionDigits` option to control the rounding of values in math', async () => { + const dict = await init({ + tokens: { + foo: { + value: '5', + type: 'dimension', + }, + bar: { + value: '{foo} / 16', + type: 'dimension', + }, + }, + platforms: { + css: { + transformGroup: 'tokens-studio', + options: { + mathFractionDigits: 3, + }, + files: [ + { + format: 'css/variables', + destination: 'foo.css', + }, + ], + }, + }, + }); + const enrichedTokens = await dict?.exportPlatform('css'); // platform to parse for is 'css' in this case + cleanup(dict); + expect((enrichedTokens?.bar as TransformedToken).value).to.eql('0.313px'); + }); + it('supports boolean values', () => { expect(checkAndEvaluateMath({ value: false, type: 'boolean' })).to.equal(false); expect(checkAndEvaluateMath({ value: true, type: 'boolean' })).to.equal(true);