Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add util genCalc and type AbstractCalculator #184

Merged
merged 2 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个不用 export 了吧

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

};

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)',
);
});
});
Loading