Skip to content

Commit f115491

Browse files
authored
feat: add util genCalc and type AbstractCalculator (#184)
* feat: add util calc and type AbstractCalculator * mod: rename the util calc to genCalc
1 parent 57dbfef commit f115491

File tree

7 files changed

+367
-3
lines changed

7 files changed

+367
-3
lines changed

src/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import {
1313
} from './linters';
1414
import type { StyleProviderProps } from './StyleContext';
1515
import { createCache, StyleProvider } from './StyleContext';
16-
import type { DerivativeFunc, TokenType } from './theme';
17-
import { createTheme, Theme } from './theme';
16+
import type { AbstractCalculator, DerivativeFunc, TokenType } from './theme';
17+
import { createTheme, genCalc, Theme } from './theme';
1818
import type { Transformer } from './transformers/interface';
1919
import legacyLogicalPropertiesTransformer from './transformers/legacyLogicalProperties';
2020
import px2remTransformer from './transformers/px2rem';
@@ -46,6 +46,7 @@ export {
4646
// util
4747
token2CSSVar,
4848
unit,
49+
genCalc,
4950
};
5051
export type {
5152
TokenType,
@@ -55,6 +56,7 @@ export type {
5556
Transformer,
5657
Linter,
5758
StyleProviderProps,
59+
AbstractCalculator,
5860
};
5961

6062
export const _experimental = {

src/theme/calc/CSSCalculator.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import AbstractCalculator from './calculator';
2+
3+
const CALC_UNIT = 'CALC_UNIT';
4+
5+
const regexp = new RegExp(CALC_UNIT, 'g');
6+
7+
function unit(value: string | number) {
8+
if (typeof value === 'number') {
9+
return `${value}${CALC_UNIT}`;
10+
}
11+
return value;
12+
}
13+
14+
export default class CSSCalculator extends AbstractCalculator {
15+
result: string = '';
16+
17+
unitlessCssVar: Set<string>;
18+
19+
lowPriority?: boolean;
20+
21+
constructor(
22+
num: number | string | AbstractCalculator,
23+
unitlessCssVar: Set<string>,
24+
) {
25+
super();
26+
27+
const numType = typeof num;
28+
29+
this.unitlessCssVar = unitlessCssVar;
30+
31+
if (num instanceof CSSCalculator) {
32+
this.result = `(${num.result})`;
33+
} else if (numType === 'number') {
34+
this.result = unit(num as number);
35+
} else if (numType === 'string') {
36+
this.result = num as string;
37+
}
38+
}
39+
40+
add(num: number | string | AbstractCalculator): this {
41+
if (num instanceof CSSCalculator) {
42+
this.result = `${this.result} + ${num.getResult()}`;
43+
} else if (typeof num === 'number' || typeof num === 'string') {
44+
this.result = `${this.result} + ${unit(num)}`;
45+
}
46+
this.lowPriority = true;
47+
return this;
48+
}
49+
50+
sub(num: number | string | AbstractCalculator): this {
51+
if (num instanceof CSSCalculator) {
52+
this.result = `${this.result} - ${num.getResult()}`;
53+
} else if (typeof num === 'number' || typeof num === 'string') {
54+
this.result = `${this.result} - ${unit(num)}`;
55+
}
56+
this.lowPriority = true;
57+
return this;
58+
}
59+
60+
mul(num: number | string | AbstractCalculator): this {
61+
if (this.lowPriority) {
62+
this.result = `(${this.result})`;
63+
}
64+
if (num instanceof CSSCalculator) {
65+
this.result = `${this.result} * ${num.getResult(true)}`;
66+
} else if (typeof num === 'number' || typeof num === 'string') {
67+
this.result = `${this.result} * ${num}`;
68+
}
69+
this.lowPriority = false;
70+
return this;
71+
}
72+
73+
div(num: number | string | AbstractCalculator): this {
74+
if (this.lowPriority) {
75+
this.result = `(${this.result})`;
76+
}
77+
if (num instanceof CSSCalculator) {
78+
this.result = `${this.result} / ${num.getResult(true)}`;
79+
} else if (typeof num === 'number' || typeof num === 'string') {
80+
this.result = `${this.result} / ${num}`;
81+
}
82+
this.lowPriority = false;
83+
return this;
84+
}
85+
86+
getResult(force?: boolean): string {
87+
return this.lowPriority || force ? `(${this.result})` : this.result;
88+
}
89+
90+
equal(options?: { unit?: boolean }): string {
91+
const { unit: cssUnit } = options || {};
92+
93+
let mergedUnit: boolean = true;
94+
if (typeof cssUnit === 'boolean') {
95+
mergedUnit = cssUnit;
96+
} else if (
97+
Array.from(this.unitlessCssVar).some((cssVar) =>
98+
this.result.includes(cssVar),
99+
)
100+
) {
101+
mergedUnit = false;
102+
}
103+
104+
this.result = this.result.replace(regexp, mergedUnit ? 'px' : '');
105+
if (typeof this.lowPriority !== 'undefined') {
106+
return `calc(${this.result})`;
107+
}
108+
return this.result;
109+
}
110+
}

src/theme/calc/NumCalculator.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import AbstractCalculator from './calculator';
2+
3+
export default class NumCalculator extends AbstractCalculator {
4+
result: number = 0;
5+
6+
constructor(num: number | string | AbstractCalculator) {
7+
super();
8+
if (num instanceof NumCalculator) {
9+
this.result = num.result;
10+
} else if (typeof num === 'number') {
11+
this.result = num;
12+
}
13+
}
14+
15+
add(num: number | string | AbstractCalculator): this {
16+
if (num instanceof NumCalculator) {
17+
this.result += num.result;
18+
} else if (typeof num === 'number') {
19+
this.result += num;
20+
}
21+
return this;
22+
}
23+
24+
sub(num: number | string | AbstractCalculator): this {
25+
if (num instanceof NumCalculator) {
26+
this.result -= num.result;
27+
} else if (typeof num === 'number') {
28+
this.result -= num;
29+
}
30+
return this;
31+
}
32+
33+
mul(num: number | string | AbstractCalculator): this {
34+
if (num instanceof NumCalculator) {
35+
this.result *= num.result;
36+
} else if (typeof num === 'number') {
37+
this.result *= num;
38+
}
39+
return this;
40+
}
41+
42+
div(num: number | string | AbstractCalculator): this {
43+
if (num instanceof NumCalculator) {
44+
this.result /= num.result;
45+
} else if (typeof num === 'number') {
46+
this.result /= num;
47+
}
48+
return this;
49+
}
50+
51+
equal(): number {
52+
return this.result;
53+
}
54+
}

src/theme/calc/calculator.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
abstract class AbstractCalculator {
2+
/**
3+
* @descCN 计算两数的和,例如:1 + 2
4+
* @descEN Calculate the sum of two numbers, e.g. 1 + 2
5+
*/
6+
abstract add(num: number | string | AbstractCalculator): this;
7+
8+
/**
9+
* @descCN 计算两数的差,例如:1 - 2
10+
* @descEN Calculate the difference between two numbers, e.g. 1 - 2
11+
*/
12+
abstract sub(num: number | string | AbstractCalculator): this;
13+
14+
/**
15+
* @descCN 计算两数的积,例如:1 * 2
16+
* @descEN Calculate the product of two numbers, e.g. 1 * 2
17+
*/
18+
abstract mul(num: number | string | AbstractCalculator): this;
19+
20+
/**
21+
* @descCN 计算两数的商,例如:1 / 2
22+
* @descEN Calculate the quotient of two numbers, e.g. 1 / 2
23+
*/
24+
abstract div(num: number | string | AbstractCalculator): this;
25+
26+
/**
27+
* @descCN 获取计算结果
28+
* @descEN Get the calculation result
29+
*/
30+
abstract equal(options?: { unit?: boolean }): string | number;
31+
}
32+
33+
export default AbstractCalculator;

src/theme/calc/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import type AbstractCalculator from './calculator';
2+
import CSSCalculator from './CSSCalculator';
3+
import NumCalculator from './NumCalculator';
4+
5+
const genCalc = (type: 'css' | 'js', unitlessCssVar: Set<string>) => {
6+
const Calculator = type === 'css' ? CSSCalculator : NumCalculator;
7+
8+
return (num: number | string | AbstractCalculator) =>
9+
new Calculator(num, unitlessCssVar);
10+
};
11+
12+
export default genCalc;

src/theme/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
export { default as genCalc } from './calc';
2+
export type { default as AbstractCalculator } from './calc/calculator';
13
export { default as createTheme } from './createTheme';
4+
export type { DerivativeFunc, TokenType } from './interface';
25
export { default as Theme } from './Theme';
36
export { default as ThemeCache } from './ThemeCache';
4-
export type { TokenType, DerivativeFunc } from './interface';

tests/calc.spec.tsx

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import type { AbstractCalculator } from '../src';
2+
import { genCalc } from '../src';
3+
4+
describe('calculator', () => {
5+
const cases: [
6+
(
7+
calc: (num: number | AbstractCalculator) => AbstractCalculator,
8+
) => string | number,
9+
{ js: number; css: string },
10+
][] = [
11+
[
12+
// 1 + 1
13+
(calc) => calc(1).add(1).equal(),
14+
{
15+
js: 2,
16+
css: 'calc(1px + 1px)',
17+
},
18+
],
19+
[
20+
// (1 + 1) * 4
21+
(calc) => calc(1).add(1).mul(4).equal(),
22+
{
23+
js: 8,
24+
css: 'calc((1px + 1px) * 4)',
25+
},
26+
],
27+
[
28+
// (2 + 4) / 2 - 2
29+
(calc) => calc(2).add(4).div(2).sub(2).equal(),
30+
{
31+
js: 1,
32+
css: 'calc((2px + 4px) / 2 - 2px)',
33+
},
34+
],
35+
[
36+
// Bad case
37+
// (2 + 4) / (3 - 2) - 2
38+
(calc) => calc(2).add(4).div(calc(3).sub(2)).sub(2).equal(),
39+
{
40+
js: 4,
41+
css: 'calc((2px + 4px) / (3px - 2px) - 2px)',
42+
},
43+
],
44+
[
45+
// Bad case
46+
// 2 * (2 + 3)
47+
(calc) => calc(2).mul(calc(2).add(3)).equal(),
48+
{
49+
js: 10,
50+
css: 'calc(2px * (2px + 3px))',
51+
},
52+
],
53+
[
54+
// (1 + 2) * 3
55+
(calc) => calc(calc(1).add(2)).mul(3).equal(),
56+
{
57+
js: 9,
58+
css: 'calc((1px + 2px) * 3)',
59+
},
60+
],
61+
[
62+
// 1 + (2 - 1)
63+
(calc) => calc(1).add(calc(2).sub(1)).equal(),
64+
{
65+
js: 2,
66+
css: 'calc(1px + (2px - 1px))',
67+
},
68+
],
69+
[
70+
// 1 + 2 * 2
71+
(calc) => calc(1).add(calc(2).mul(2)).equal(),
72+
{
73+
js: 5,
74+
css: 'calc(1px + 2px * 2)',
75+
},
76+
],
77+
[
78+
// 5 - (2 - 1)
79+
(calc) => calc(5).sub(calc(2).sub(1)).equal(),
80+
{
81+
js: 4,
82+
css: 'calc(5px - (2px - 1px))',
83+
},
84+
],
85+
[
86+
// 2 * 6 / 3
87+
(calc) => calc(2).mul(6).div(3).equal(),
88+
{
89+
js: 4,
90+
css: 'calc(2px * 6 / 3)',
91+
},
92+
],
93+
[
94+
// 6 / 3 * 2
95+
(calc) => calc(6).div(3).mul(2).equal(),
96+
{
97+
js: 4,
98+
css: 'calc(6px / 3 * 2)',
99+
},
100+
],
101+
[
102+
// Bad case
103+
// 6 / (3 * 2)
104+
(calc) => calc(6).div(calc(3).mul(2)).equal(),
105+
{
106+
js: 1,
107+
css: 'calc(6px / (3px * 2))',
108+
},
109+
],
110+
[
111+
// 6
112+
(calc) => calc(6).equal(),
113+
{
114+
js: 6,
115+
css: '6px',
116+
},
117+
],
118+
[
119+
// 1000 + 100 without unit
120+
(calc) => calc(1000).add(100).equal({ unit: false }),
121+
{
122+
js: 1100,
123+
css: 'calc(1000 + 100)',
124+
},
125+
],
126+
];
127+
128+
cases.forEach(([exp, { js, css }], index) => {
129+
it(`js calc ${index + 1}`, () => {
130+
expect(exp(genCalc('js', new Set()))).toBe(js);
131+
});
132+
133+
it(`css calc ${index + 1}`, () => {
134+
expect(exp(genCalc('css', new Set()))).toBe(css);
135+
});
136+
});
137+
138+
it('css calc should work with string', () => {
139+
const calc = genCalc('css', new Set());
140+
expect(calc('var(--var1)').add('var(--var2)').equal()).toBe(
141+
'calc(var(--var1) + var(--var2))',
142+
);
143+
});
144+
145+
it('css calc var should skip zIndex', () => {
146+
const calc = genCalc('css', new Set(['--ant-z-index']));
147+
expect(calc('var(--ant-z-index)').add(93).equal()).toBe(
148+
'calc(var(--ant-z-index) + 93)',
149+
);
150+
});
151+
});

0 commit comments

Comments
 (0)