From 2fc711956705da0588cdb67b32e2e67dd2113eea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=8F=8E=EF=B8=8F=20Yumo?= Date: Thu, 13 Jun 2024 17:15:24 +0800 Subject: [PATCH 1/2] feat: add util calc and type AbstractCalculator --- src/index.ts | 6 +- src/theme/calc/CSSCalculator.ts | 110 +++++++++++++++++++++++ src/theme/calc/NumCalculator.ts | 54 ++++++++++++ src/theme/calc/calculator.ts | 33 +++++++ src/theme/calc/index.ts | 12 +++ src/theme/index.ts | 4 +- tests/calc.spec.tsx | 151 ++++++++++++++++++++++++++++++++ 7 files changed, 367 insertions(+), 3 deletions(-) create mode 100644 src/theme/calc/CSSCalculator.ts create mode 100644 src/theme/calc/NumCalculator.ts create mode 100644 src/theme/calc/calculator.ts create mode 100644 src/theme/calc/index.ts create mode 100644 tests/calc.spec.tsx diff --git a/src/index.ts b/src/index.ts index 5dc10a65..cf3629e7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,8 +13,8 @@ import { } from './linters'; import type { StyleProviderProps } from './StyleContext'; import { createCache, StyleProvider } from './StyleContext'; -import type { DerivativeFunc, TokenType } from './theme'; -import { createTheme, Theme } from './theme'; +import type { AbstractCalculator, DerivativeFunc, TokenType } from './theme'; +import { calc, createTheme, Theme } from './theme'; import type { Transformer } from './transformers/interface'; import legacyLogicalPropertiesTransformer from './transformers/legacyLogicalProperties'; import px2remTransformer from './transformers/px2rem'; @@ -46,6 +46,7 @@ export { // util token2CSSVar, unit, + calc, }; export type { TokenType, @@ -55,6 +56,7 @@ export type { Transformer, Linter, StyleProviderProps, + AbstractCalculator, }; export const _experimental = { diff --git a/src/theme/calc/CSSCalculator.ts b/src/theme/calc/CSSCalculator.ts new file mode 100644 index 00000000..7c25fcc4 --- /dev/null +++ b/src/theme/calc/CSSCalculator.ts @@ -0,0 +1,110 @@ +import AbstractCalculator from './calculator'; + +const CALC_UNIT = 'CALC_UNIT'; + +const regexp = new RegExp(CALC_UNIT, 'g'); + +function unit(value: string | number) { + if (typeof value === 'number') { + return `${value}${CALC_UNIT}`; + } + return value; +} + +export default class CSSCalculator extends AbstractCalculator { + result: string = ''; + + unitlessCssVar: Set; + + lowPriority?: boolean; + + constructor( + num: number | string | AbstractCalculator, + unitlessCssVar: Set, + ) { + super(); + + const numType = typeof num; + + this.unitlessCssVar = unitlessCssVar; + + if (num instanceof CSSCalculator) { + this.result = `(${num.result})`; + } else if (numType === 'number') { + this.result = unit(num as number); + } else if (numType === 'string') { + this.result = num as string; + } + } + + add(num: number | string | AbstractCalculator): this { + if (num instanceof CSSCalculator) { + this.result = `${this.result} + ${num.getResult()}`; + } else if (typeof num === 'number' || typeof num === 'string') { + this.result = `${this.result} + ${unit(num)}`; + } + this.lowPriority = true; + return this; + } + + sub(num: number | string | AbstractCalculator): this { + if (num instanceof CSSCalculator) { + this.result = `${this.result} - ${num.getResult()}`; + } else if (typeof num === 'number' || typeof num === 'string') { + this.result = `${this.result} - ${unit(num)}`; + } + this.lowPriority = true; + return this; + } + + mul(num: number | string | AbstractCalculator): this { + if (this.lowPriority) { + this.result = `(${this.result})`; + } + if (num instanceof CSSCalculator) { + this.result = `${this.result} * ${num.getResult(true)}`; + } else if (typeof num === 'number' || typeof num === 'string') { + this.result = `${this.result} * ${num}`; + } + this.lowPriority = false; + return this; + } + + div(num: number | string | AbstractCalculator): this { + if (this.lowPriority) { + this.result = `(${this.result})`; + } + if (num instanceof CSSCalculator) { + this.result = `${this.result} / ${num.getResult(true)}`; + } else if (typeof num === 'number' || typeof num === 'string') { + this.result = `${this.result} / ${num}`; + } + this.lowPriority = false; + return this; + } + + getResult(force?: boolean): string { + return this.lowPriority || force ? `(${this.result})` : this.result; + } + + equal(options?: { unit?: boolean }): string { + const { unit: cssUnit } = options || {}; + + let mergedUnit: boolean = true; + if (typeof cssUnit === 'boolean') { + mergedUnit = cssUnit; + } else if ( + Array.from(this.unitlessCssVar).some((cssVar) => + this.result.includes(cssVar), + ) + ) { + mergedUnit = false; + } + + this.result = this.result.replace(regexp, mergedUnit ? 'px' : ''); + if (typeof this.lowPriority !== 'undefined') { + return `calc(${this.result})`; + } + return this.result; + } +} diff --git a/src/theme/calc/NumCalculator.ts b/src/theme/calc/NumCalculator.ts new file mode 100644 index 00000000..44538e3c --- /dev/null +++ b/src/theme/calc/NumCalculator.ts @@ -0,0 +1,54 @@ +import AbstractCalculator from './calculator'; + +export default class NumCalculator extends AbstractCalculator { + result: number = 0; + + constructor(num: number | string | AbstractCalculator) { + super(); + if (num instanceof NumCalculator) { + this.result = num.result; + } else if (typeof num === 'number') { + this.result = num; + } + } + + add(num: number | string | AbstractCalculator): this { + if (num instanceof NumCalculator) { + this.result += num.result; + } else if (typeof num === 'number') { + this.result += num; + } + return this; + } + + sub(num: number | string | AbstractCalculator): this { + if (num instanceof NumCalculator) { + this.result -= num.result; + } else if (typeof num === 'number') { + this.result -= num; + } + return this; + } + + mul(num: number | string | AbstractCalculator): this { + if (num instanceof NumCalculator) { + this.result *= num.result; + } else if (typeof num === 'number') { + this.result *= num; + } + return this; + } + + div(num: number | string | AbstractCalculator): this { + if (num instanceof NumCalculator) { + this.result /= num.result; + } else if (typeof num === 'number') { + this.result /= num; + } + return this; + } + + equal(): number { + return this.result; + } +} diff --git a/src/theme/calc/calculator.ts b/src/theme/calc/calculator.ts new file mode 100644 index 00000000..cfd19ab5 --- /dev/null +++ b/src/theme/calc/calculator.ts @@ -0,0 +1,33 @@ +abstract class AbstractCalculator { + /** + * @descCN 计算两数的和,例如:1 + 2 + * @descEN Calculate the sum of two numbers, e.g. 1 + 2 + */ + abstract add(num: number | string | AbstractCalculator): this; + + /** + * @descCN 计算两数的差,例如:1 - 2 + * @descEN Calculate the difference between two numbers, e.g. 1 - 2 + */ + abstract sub(num: number | string | AbstractCalculator): this; + + /** + * @descCN 计算两数的积,例如:1 * 2 + * @descEN Calculate the product of two numbers, e.g. 1 * 2 + */ + abstract mul(num: number | string | AbstractCalculator): this; + + /** + * @descCN 计算两数的商,例如:1 / 2 + * @descEN Calculate the quotient of two numbers, e.g. 1 / 2 + */ + abstract div(num: number | string | AbstractCalculator): this; + + /** + * @descCN 获取计算结果 + * @descEN Get the calculation result + */ + abstract equal(options?: { unit?: boolean }): string | number; +} + +export default AbstractCalculator; diff --git a/src/theme/calc/index.ts b/src/theme/calc/index.ts new file mode 100644 index 00000000..e6704c9b --- /dev/null +++ b/src/theme/calc/index.ts @@ -0,0 +1,12 @@ +import type AbstractCalculator from './calculator'; +import CSSCalculator from './CSSCalculator'; +import NumCalculator from './NumCalculator'; + +const genCalc = (type: 'css' | 'js', unitlessCssVar: Set) => { + const Calculator = type === 'css' ? CSSCalculator : NumCalculator; + + return (num: number | string | AbstractCalculator) => + new Calculator(num, unitlessCssVar); +}; + +export default genCalc; diff --git a/src/theme/index.ts b/src/theme/index.ts index b3c2ff4b..72b2df16 100644 --- a/src/theme/index.ts +++ b/src/theme/index.ts @@ -1,4 +1,6 @@ +export { default as calc } from './calc'; +export type { default as AbstractCalculator } from './calc/calculator'; export { default as createTheme } from './createTheme'; +export type { DerivativeFunc, TokenType } from './interface'; export { default as Theme } from './Theme'; export { default as ThemeCache } from './ThemeCache'; -export type { TokenType, DerivativeFunc } from './interface'; diff --git a/tests/calc.spec.tsx b/tests/calc.spec.tsx new file mode 100644 index 00000000..269d40fa --- /dev/null +++ b/tests/calc.spec.tsx @@ -0,0 +1,151 @@ +import type { AbstractCalculator } from '../src'; +import { calc as genCalc } from '../src'; + +describe('calculator', () => { + const cases: [ + ( + calc: (num: number | AbstractCalculator) => AbstractCalculator, + ) => string | number, + { js: number; css: string }, + ][] = [ + [ + // 1 + 1 + (calc) => calc(1).add(1).equal(), + { + js: 2, + css: 'calc(1px + 1px)', + }, + ], + [ + // (1 + 1) * 4 + (calc) => calc(1).add(1).mul(4).equal(), + { + js: 8, + css: 'calc((1px + 1px) * 4)', + }, + ], + [ + // (2 + 4) / 2 - 2 + (calc) => calc(2).add(4).div(2).sub(2).equal(), + { + js: 1, + css: 'calc((2px + 4px) / 2 - 2px)', + }, + ], + [ + // Bad case + // (2 + 4) / (3 - 2) - 2 + (calc) => calc(2).add(4).div(calc(3).sub(2)).sub(2).equal(), + { + js: 4, + css: 'calc((2px + 4px) / (3px - 2px) - 2px)', + }, + ], + [ + // Bad case + // 2 * (2 + 3) + (calc) => calc(2).mul(calc(2).add(3)).equal(), + { + js: 10, + css: 'calc(2px * (2px + 3px))', + }, + ], + [ + // (1 + 2) * 3 + (calc) => calc(calc(1).add(2)).mul(3).equal(), + { + js: 9, + css: 'calc((1px + 2px) * 3)', + }, + ], + [ + // 1 + (2 - 1) + (calc) => calc(1).add(calc(2).sub(1)).equal(), + { + js: 2, + css: 'calc(1px + (2px - 1px))', + }, + ], + [ + // 1 + 2 * 2 + (calc) => calc(1).add(calc(2).mul(2)).equal(), + { + js: 5, + css: 'calc(1px + 2px * 2)', + }, + ], + [ + // 5 - (2 - 1) + (calc) => calc(5).sub(calc(2).sub(1)).equal(), + { + js: 4, + css: 'calc(5px - (2px - 1px))', + }, + ], + [ + // 2 * 6 / 3 + (calc) => calc(2).mul(6).div(3).equal(), + { + js: 4, + css: 'calc(2px * 6 / 3)', + }, + ], + [ + // 6 / 3 * 2 + (calc) => calc(6).div(3).mul(2).equal(), + { + js: 4, + css: 'calc(6px / 3 * 2)', + }, + ], + [ + // Bad case + // 6 / (3 * 2) + (calc) => calc(6).div(calc(3).mul(2)).equal(), + { + js: 1, + css: 'calc(6px / (3px * 2))', + }, + ], + [ + // 6 + (calc) => calc(6).equal(), + { + js: 6, + css: '6px', + }, + ], + [ + // 1000 + 100 without unit + (calc) => calc(1000).add(100).equal({ unit: false }), + { + js: 1100, + css: 'calc(1000 + 100)', + }, + ], + ]; + + cases.forEach(([exp, { js, css }], index) => { + it(`js calc ${index + 1}`, () => { + expect(exp(genCalc('js', new Set()))).toBe(js); + }); + + it(`css calc ${index + 1}`, () => { + expect(exp(genCalc('css', new Set()))).toBe(css); + }); + }); + + it('css calc should work with string', () => { + const calc = genCalc('css', new Set()); + expect(calc('var(--var1)').add('var(--var2)').equal()).toBe( + 'calc(var(--var1) + var(--var2))', + ); + }); + + it('css calc var should skip zIndex', () => { + const calc = genCalc('css', new Set(['--ant-z-index'])); + expect(calc('var(--ant-z-index)').add(93).equal()).toBe( + 'calc(var(--ant-z-index) + 93)', + ); + }); +}); From 1e06d3f81dc267c0fcadc0bf0a4c7fe4952e9478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=8F=8E=EF=B8=8F=20Yumo?= Date: Thu, 13 Jun 2024 17:58:44 +0800 Subject: [PATCH 2/2] mod: rename the util calc to genCalc --- src/index.ts | 4 ++-- src/theme/index.ts | 2 +- tests/calc.spec.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index cf3629e7..c9bf5ced 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,7 +14,7 @@ import { import type { StyleProviderProps } from './StyleContext'; import { createCache, StyleProvider } from './StyleContext'; import type { AbstractCalculator, DerivativeFunc, TokenType } from './theme'; -import { calc, createTheme, Theme } from './theme'; +import { createTheme, genCalc, Theme } from './theme'; import type { Transformer } from './transformers/interface'; import legacyLogicalPropertiesTransformer from './transformers/legacyLogicalProperties'; import px2remTransformer from './transformers/px2rem'; @@ -46,7 +46,7 @@ export { // util token2CSSVar, unit, - calc, + genCalc, }; export type { TokenType, diff --git a/src/theme/index.ts b/src/theme/index.ts index 72b2df16..60bcc422 100644 --- a/src/theme/index.ts +++ b/src/theme/index.ts @@ -1,4 +1,4 @@ -export { default as calc } from './calc'; +export { default as genCalc } from './calc'; export type { default as AbstractCalculator } from './calc/calculator'; export { default as createTheme } from './createTheme'; export type { DerivativeFunc, TokenType } from './interface'; diff --git a/tests/calc.spec.tsx b/tests/calc.spec.tsx index 269d40fa..c6658204 100644 --- a/tests/calc.spec.tsx +++ b/tests/calc.spec.tsx @@ -1,5 +1,5 @@ import type { AbstractCalculator } from '../src'; -import { calc as genCalc } from '../src'; +import { genCalc } from '../src'; describe('calculator', () => { const cases: [