Skip to content

Commit

Permalink
feat: add util genCalc and type AbstractCalculator (#184)
Browse files Browse the repository at this point in the history
* feat: add util calc and type AbstractCalculator

* mod: rename the util calc to genCalc
  • Loading branch information
YumoImer authored Jun 17, 2024
1 parent 57dbfef commit f115491
Show file tree
Hide file tree
Showing 7 changed files with 367 additions and 3 deletions.
6 changes: 4 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { createTheme, genCalc, Theme } from './theme';
import type { Transformer } from './transformers/interface';
import legacyLogicalPropertiesTransformer from './transformers/legacyLogicalProperties';
import px2remTransformer from './transformers/px2rem';
Expand Down Expand Up @@ -46,6 +46,7 @@ export {
// util
token2CSSVar,
unit,
genCalc,
};
export type {
TokenType,
Expand All @@ -55,6 +56,7 @@ export type {
Transformer,
Linter,
StyleProviderProps,
AbstractCalculator,
};

export const _experimental = {
Expand Down
110 changes: 110 additions & 0 deletions src/theme/calc/CSSCalculator.ts
Original file line number Diff line number Diff line change
@@ -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<string>;

lowPriority?: boolean;

constructor(
num: number | string | AbstractCalculator,
unitlessCssVar: Set<string>,
) {
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;
}
}
54 changes: 54 additions & 0 deletions src/theme/calc/NumCalculator.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
33 changes: 33 additions & 0 deletions src/theme/calc/calculator.ts
Original file line number Diff line number Diff line change
@@ -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;
12 changes: 12 additions & 0 deletions src/theme/calc/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type AbstractCalculator from './calculator';
import CSSCalculator from './CSSCalculator';
import NumCalculator from './NumCalculator';

const genCalc = (type: 'css' | 'js', unitlessCssVar: Set<string>) => {
const Calculator = type === 'css' ? CSSCalculator : NumCalculator;

return (num: number | string | AbstractCalculator) =>
new Calculator(num, unitlessCssVar);
};

export default genCalc;
4 changes: 3 additions & 1 deletion src/theme/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
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';
export { default as Theme } from './Theme';
export { default as ThemeCache } from './ThemeCache';
export type { TokenType, DerivativeFunc } from './interface';
151 changes: 151 additions & 0 deletions tests/calc.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import type { AbstractCalculator } from '../src';
import { 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)',
);
});
});

0 comments on commit f115491

Please sign in to comment.