postcss-calc 11.0.3 → 11.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -8
- package/package.json +10 -6
- package/src/index.js +37 -163
- package/src/lib/node.js +5 -5
- package/src/lib/parser.js +40 -33
- package/src/lib/serialize.js +73 -33
- package/src/lib/simplify/bucket.js +1 -1
- package/src/lib/simplify/call.js +30 -2
- package/src/lib/simplify/clamp.js +25 -3
- package/src/lib/simplify/fold.js +14 -1
- package/src/lib/simplify/hypot.js +3 -3
- package/src/lib/simplify/min-max.js +3 -3
- package/src/lib/simplify/mod-rem.js +3 -3
- package/src/lib/simplify/round.js +4 -4
- package/src/lib/tokenizer.js +42 -30
- package/src/reduce.js +176 -0
- package/types/index.d.ts +0 -15
- package/types/lib/parser.d.ts +16 -0
- package/types/lib/serialize.d.ts +4 -0
- package/types/lib/simplify/call.d.ts +11 -1
- package/types/lib/simplify/fold.d.ts +14 -1
- package/types/lib/tokenizer.d.ts +3 -1
- package/types/reduce.d.ts +41 -0
- package/types/lib/type.d.ts +0 -23
package/src/reduce.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// CSS component-value reducer. This module deliberately has no PostCSS
|
|
2
|
+
// dependency so it can also be used for individual declaration values,
|
|
3
|
+
// at-rule parameters, or selector text.
|
|
4
|
+
import {
|
|
5
|
+
tokenize as cssTokenize,
|
|
6
|
+
TokenType as CssType,
|
|
7
|
+
} from '@csstools/css-tokenizer';
|
|
8
|
+
import { tokenizeTokens } from './lib/tokenizer.js';
|
|
9
|
+
import { parse } from './lib/parser.js';
|
|
10
|
+
import { simplify } from './lib/simplify.js';
|
|
11
|
+
import {
|
|
12
|
+
isSupportedMathFunction,
|
|
13
|
+
hasPotentialMathFunction,
|
|
14
|
+
QUICK_MATH_TEST,
|
|
15
|
+
} from './lib/simplify/call.js';
|
|
16
|
+
import { serialize } from './lib/serialize.js';
|
|
17
|
+
|
|
18
|
+
const MATCH_CALC = /^(?:-(?:moz|webkit)-)?calc$/i;
|
|
19
|
+
|
|
20
|
+
const BLOCK_CLOSE = new Map([
|
|
21
|
+
[CssType.OpenParen, CssType.CloseParen],
|
|
22
|
+
[CssType.OpenSquare, CssType.CloseSquare],
|
|
23
|
+
[CssType.OpenCurly, CssType.CloseCurly],
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {object} ReduceCalcOptions
|
|
28
|
+
* @property {number | false} [precision]
|
|
29
|
+
* @property {boolean} [warnWhenCannotResolve]
|
|
30
|
+
* @property {boolean} [unwrapSingleNegativeNumber] Serialize finite negative results without a `calc()` wrapper. Defaults to `false`.
|
|
31
|
+
* @property {(error: Error, input: string) => void} [onParseError] Invoked when parse/simplify throws.
|
|
32
|
+
* @property {(message: string) => void} [onWarn] Invoked when `warnWhenCannotResolve` is set and an expression cannot be reduced to a single value.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/** @typedef {Required<Omit<ReduceCalcOptions, 'onParseError' | 'onWarn'>> & Pick<ReduceCalcOptions, 'onParseError' | 'onWarn'>} ResolvedReduceCalcOptions */
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Fields threaded through the internal token-range walk.
|
|
39
|
+
*
|
|
40
|
+
* @typedef {object} TransformContext
|
|
41
|
+
* @property {ResolvedReduceCalcOptions} options
|
|
42
|
+
* @property {string} value
|
|
43
|
+
* @property {import('@csstools/css-tokenizer').CSSToken[]} tokens
|
|
44
|
+
* @property {Replacement[]} replacements
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @typedef {object} Replacement
|
|
49
|
+
* @property {number} start
|
|
50
|
+
* @property {number} end
|
|
51
|
+
* @property {import('./lib/node.js').Node} node
|
|
52
|
+
* @property {string} calcName
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Walk one component-value level. Unsupported functions and simple blocks are
|
|
57
|
+
* traversed, while a supported function is treated as one opaque calculation
|
|
58
|
+
* even when parsing it fails. A missing closer consumes through EOF, matching
|
|
59
|
+
* CSS component-value parsing's error recovery.
|
|
60
|
+
*
|
|
61
|
+
* @param {number} start
|
|
62
|
+
* @param {import('@csstools/css-tokenizer').TokenType | undefined} expectedClose
|
|
63
|
+
* @param {TransformContext} ctx
|
|
64
|
+
* @param {boolean} transform
|
|
65
|
+
* @return {number} Index of the matching closer, or the EOF token.
|
|
66
|
+
*/
|
|
67
|
+
function walkTokens(start, expectedClose, ctx, transform) {
|
|
68
|
+
for (let i = start; i < ctx.tokens.length; i++) {
|
|
69
|
+
const token = ctx.tokens[i];
|
|
70
|
+
if (token[0] === CssType.EOF || token[0] === expectedClose) return i;
|
|
71
|
+
|
|
72
|
+
const blockClose = BLOCK_CLOSE.get(token[0]);
|
|
73
|
+
if (blockClose) {
|
|
74
|
+
i = walkTokens(i + 1, blockClose, ctx, transform);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (token[0] !== CssType.Function) continue;
|
|
79
|
+
|
|
80
|
+
const name = token[4].value;
|
|
81
|
+
const isCalc = MATCH_CALC.test(name);
|
|
82
|
+
const isMath = !isCalc && isSupportedMathFunction(name);
|
|
83
|
+
if (!transform || (!isCalc && !isMath)) {
|
|
84
|
+
i = walkTokens(i + 1, CssType.CloseParen, ctx, transform);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Locate the complete outer function without transforming its children.
|
|
89
|
+
const close = walkTokens(i + 1, CssType.CloseParen, ctx, false);
|
|
90
|
+
const closed = ctx.tokens[close][0] === CssType.CloseParen;
|
|
91
|
+
const end = closed ? ctx.tokens[close][3] + 1 : ctx.value.length;
|
|
92
|
+
const sliceStart = isCalc ? i + 1 : i;
|
|
93
|
+
const sliceEnd = closed ? close + (isCalc ? 0 : 1) : close;
|
|
94
|
+
const inputStart = isCalc ? token[3] + 1 : token[2];
|
|
95
|
+
const inputEnd = closed && isCalc ? ctx.tokens[close][2] : end;
|
|
96
|
+
const contents = ctx.value.slice(inputStart, inputEnd);
|
|
97
|
+
try {
|
|
98
|
+
const node = simplify(
|
|
99
|
+
parse(tokenizeTokens(ctx.tokens, end, sliceStart, sliceEnd))
|
|
100
|
+
);
|
|
101
|
+
ctx.replacements.push({
|
|
102
|
+
start: token[2],
|
|
103
|
+
end,
|
|
104
|
+
node,
|
|
105
|
+
calcName: isCalc ? name : 'calc',
|
|
106
|
+
});
|
|
107
|
+
} catch (error) {
|
|
108
|
+
const err = error instanceof Error ? error : new Error('Error');
|
|
109
|
+
ctx.options.onParseError?.(err, contents);
|
|
110
|
+
}
|
|
111
|
+
i = close;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return ctx.tokens.length - 1;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @param {import('./lib/node.js').Node} node
|
|
119
|
+
* @return {boolean}
|
|
120
|
+
*/
|
|
121
|
+
function isUnresolvedResult(node) {
|
|
122
|
+
if (node.type === 'Sum' || node.type === 'Product') {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
return node.type === 'Call' && isSupportedMathFunction(node.name);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Simplify every supported CSS math function in a component-value string.
|
|
130
|
+
* Text outside those functions is preserved byte-for-byte.
|
|
131
|
+
*
|
|
132
|
+
* @param {string} value
|
|
133
|
+
* @param {ReduceCalcOptions} [opts]
|
|
134
|
+
* @return {string}
|
|
135
|
+
*/
|
|
136
|
+
function reduceCalc(value, opts) {
|
|
137
|
+
if (!hasPotentialMathFunction(value)) {
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** @type {ResolvedReduceCalcOptions} */
|
|
142
|
+
const options = {
|
|
143
|
+
precision: 5,
|
|
144
|
+
warnWhenCannotResolve: false,
|
|
145
|
+
unwrapSingleNegativeNumber: false,
|
|
146
|
+
...opts,
|
|
147
|
+
};
|
|
148
|
+
const tokens = cssTokenize({ css: value });
|
|
149
|
+
/** @type {Replacement[]} */
|
|
150
|
+
const replacements = [];
|
|
151
|
+
walkTokens(0, undefined, { options, value, tokens, replacements }, true);
|
|
152
|
+
|
|
153
|
+
if (replacements.length === 0) {
|
|
154
|
+
return value;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let output = '';
|
|
158
|
+
let lastIndex = 0;
|
|
159
|
+
for (const replacement of replacements) {
|
|
160
|
+
const text = serialize(replacement.node, {
|
|
161
|
+
precision: options.precision,
|
|
162
|
+
calcName: replacement.calcName,
|
|
163
|
+
unwrapSingleNegativeNumber: options.unwrapSingleNegativeNumber,
|
|
164
|
+
});
|
|
165
|
+
if (options.warnWhenCannotResolve && isUnresolvedResult(replacement.node)) {
|
|
166
|
+
options.onWarn?.('Could not reduce expression: ' + value);
|
|
167
|
+
}
|
|
168
|
+
output += value.slice(lastIndex, replacement.start) + text;
|
|
169
|
+
lastIndex = replacement.end;
|
|
170
|
+
}
|
|
171
|
+
output += value.slice(lastIndex);
|
|
172
|
+
return output;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export { QUICK_MATH_TEST, hasPotentialMathFunction };
|
|
176
|
+
export default reduceCalc;
|
package/types/index.d.ts
CHANGED
|
@@ -9,21 +9,6 @@ export type PostCssCalcOptions = {
|
|
|
9
9
|
onParseError?: (error: Error, input: string) => void;
|
|
10
10
|
};
|
|
11
11
|
export type ResolvedOptions = Required<Omit<PostCssCalcOptions, 'onParseError'>> & Pick<PostCssCalcOptions, 'onParseError'>;
|
|
12
|
-
export type TransformContext = {
|
|
13
|
-
options: ResolvedOptions;
|
|
14
|
-
result: import('postcss').Result;
|
|
15
|
-
item: import('postcss').ChildNode;
|
|
16
|
-
value: string;
|
|
17
|
-
tokens: import('@csstools/css-tokenizer').CSSToken[];
|
|
18
|
-
replacements: Replacement[];
|
|
19
|
-
};
|
|
20
|
-
export type Replacement = {
|
|
21
|
-
start: number;
|
|
22
|
-
end: number;
|
|
23
|
-
node: import('./lib/node.js').Node;
|
|
24
|
-
calcName: string;
|
|
25
|
-
matchedName: string;
|
|
26
|
-
};
|
|
27
12
|
/**
|
|
28
13
|
* @param {PostCssCalcOptions} [opts]
|
|
29
14
|
* @return {import('postcss').Plugin}
|
package/types/lib/parser.d.ts
CHANGED
|
@@ -21,6 +21,22 @@ declare class Parser {
|
|
|
21
21
|
* @return {Token}
|
|
22
22
|
*/
|
|
23
23
|
expect(type: TokenType, value?: string): Token;
|
|
24
|
+
/**
|
|
25
|
+
* @param {string} value
|
|
26
|
+
* @param {string} [value2]
|
|
27
|
+
* @return {boolean}
|
|
28
|
+
*/
|
|
29
|
+
isPunct(value: string, value2?: string): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* @param {string} value
|
|
32
|
+
* @return {boolean}
|
|
33
|
+
*/
|
|
34
|
+
matchPunct(value: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* @param {string} value
|
|
37
|
+
* @return {Token}
|
|
38
|
+
*/
|
|
39
|
+
expectPunct(value: string): Token;
|
|
24
40
|
/**
|
|
25
41
|
* @param {number} [minBp]
|
|
26
42
|
* @return {Node}
|
package/types/lib/serialize.d.ts
CHANGED
|
@@ -11,6 +11,10 @@ export type SerializeOptions = {
|
|
|
11
11
|
* Wrapper name to use when `calc()` is needed. Default `'calc'`.
|
|
12
12
|
*/
|
|
13
13
|
calcName?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Serialize finite negative scalars without a wrapper. Internal selector-only mode.
|
|
16
|
+
*/
|
|
17
|
+
unwrapSingleNegativeNumber?: boolean;
|
|
14
18
|
};
|
|
15
19
|
/**
|
|
16
20
|
* @param {Node} node
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
export type Node = import('../node.js').Node;
|
|
2
2
|
export type SimplifyFn = import('../simplify.js').SimplifyFn;
|
|
3
3
|
export type MathSimplifier = (name: string, args: Node[]) => Node;
|
|
4
|
+
declare const QUICK_MATH_TEST: RegExp;
|
|
5
|
+
/**
|
|
6
|
+
* Fast check to determine whether a CSS component value could contain
|
|
7
|
+
* a supported calculation or math function call (or an escape sequence
|
|
8
|
+
* that could decode to one).
|
|
9
|
+
*
|
|
10
|
+
* @param {string} value
|
|
11
|
+
* @return {boolean}
|
|
12
|
+
*/
|
|
13
|
+
declare function hasPotentialMathFunction(value: string): boolean;
|
|
4
14
|
/**
|
|
5
15
|
* Whether a bare CSS math function has an implemented simplifier.
|
|
6
16
|
*
|
|
@@ -16,4 +26,4 @@ declare function isSupportedMathFunction(name: string): boolean;
|
|
|
16
26
|
declare function simplifyCall(node: Extract<Node, {
|
|
17
27
|
type: 'Call';
|
|
18
28
|
}>, simplify: SimplifyFn): Node;
|
|
19
|
-
export { isSupportedMathFunction, simplifyCall };
|
|
29
|
+
export { isSupportedMathFunction, simplifyCall, hasPotentialMathFunction, QUICK_MATH_TEST, };
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
export type Node = import('../node.js').Node;
|
|
2
|
+
export type Num = import('../node.js').Num;
|
|
3
|
+
export type Dim = import('../node.js').Dim;
|
|
2
4
|
export type BaseType = import('../convertUnits.js').BaseType;
|
|
3
5
|
/** @typedef {import('../node.js').Node} Node */
|
|
6
|
+
/** @typedef {import('../node.js').Num} Num */
|
|
7
|
+
/** @typedef {import('../node.js').Dim} Dim */
|
|
4
8
|
/** @typedef {import('../convertUnits.js').BaseType} BaseType */
|
|
9
|
+
/**
|
|
10
|
+
* Construct a Num or Dim node from a folded result.
|
|
11
|
+
* @param {{ unit: string }} fold
|
|
12
|
+
* @param {number} value
|
|
13
|
+
* @return {Num | Dim}
|
|
14
|
+
*/
|
|
15
|
+
declare function foldResult(fold: {
|
|
16
|
+
unit: string;
|
|
17
|
+
}, value: number): Num | Dim;
|
|
5
18
|
/**
|
|
6
19
|
* @param {Node[]} args
|
|
7
20
|
* @return {{ values: number[], unit: string } | null}
|
|
@@ -10,4 +23,4 @@ declare function foldConstArgs(args: Node[]): {
|
|
|
10
23
|
values: number[];
|
|
11
24
|
unit: string;
|
|
12
25
|
} | null;
|
|
13
|
-
export { foldConstArgs };
|
|
26
|
+
export { foldConstArgs, foldResult };
|
package/types/lib/tokenizer.d.ts
CHANGED
|
@@ -24,7 +24,9 @@ declare function tokenize(input: string): Token[];
|
|
|
24
24
|
*
|
|
25
25
|
* @param {import('@csstools/css-tokenizer').CSSToken[]} cssTokens
|
|
26
26
|
* @param {number} eofPosition
|
|
27
|
+
* @param {number} [start]
|
|
28
|
+
* @param {number} [end]
|
|
27
29
|
* @return {Token[]}
|
|
28
30
|
*/
|
|
29
|
-
declare function tokenizeTokens(cssTokens: import('@csstools/css-tokenizer').CSSToken[], eofPosition: number): Token[];
|
|
31
|
+
declare function tokenizeTokens(cssTokens: import('@csstools/css-tokenizer').CSSToken[], eofPosition: number, start?: number, end?: number): Token[];
|
|
30
32
|
export { tokenize, tokenizeTokens };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { hasPotentialMathFunction, QUICK_MATH_TEST } from './lib/simplify/call.js';
|
|
2
|
+
export type ReduceCalcOptions = {
|
|
3
|
+
precision?: number | false;
|
|
4
|
+
warnWhenCannotResolve?: boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Serialize finite negative results without a `calc()` wrapper. Defaults to `false`.
|
|
7
|
+
*/
|
|
8
|
+
unwrapSingleNegativeNumber?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Invoked when parse/simplify throws.
|
|
11
|
+
*/
|
|
12
|
+
onParseError?: (error: Error, input: string) => void;
|
|
13
|
+
/**
|
|
14
|
+
* Invoked when `warnWhenCannotResolve` is set and an expression cannot be reduced to a single value.
|
|
15
|
+
*/
|
|
16
|
+
onWarn?: (message: string) => void;
|
|
17
|
+
};
|
|
18
|
+
export type ResolvedReduceCalcOptions = Required<Omit<ReduceCalcOptions, 'onParseError' | 'onWarn'>> & Pick<ReduceCalcOptions, 'onParseError' | 'onWarn'>;
|
|
19
|
+
export type TransformContext = {
|
|
20
|
+
options: ResolvedReduceCalcOptions;
|
|
21
|
+
value: string;
|
|
22
|
+
tokens: import('@csstools/css-tokenizer').CSSToken[];
|
|
23
|
+
replacements: Replacement[];
|
|
24
|
+
};
|
|
25
|
+
export type Replacement = {
|
|
26
|
+
start: number;
|
|
27
|
+
end: number;
|
|
28
|
+
node: import('./lib/node.js').Node;
|
|
29
|
+
calcName: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Simplify every supported CSS math function in a component-value string.
|
|
33
|
+
* Text outside those functions is preserved byte-for-byte.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} value
|
|
36
|
+
* @param {ReduceCalcOptions} [opts]
|
|
37
|
+
* @return {string}
|
|
38
|
+
*/
|
|
39
|
+
declare function reduceCalc(value: string, opts?: ReduceCalcOptions): string;
|
|
40
|
+
export { QUICK_MATH_TEST, hasPotentialMathFunction };
|
|
41
|
+
export default reduceCalc;
|
package/types/lib/type.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export type BaseType =
|
|
2
|
-
| 'length'
|
|
3
|
-
| 'angle'
|
|
4
|
-
| 'time'
|
|
5
|
-
| 'frequency'
|
|
6
|
-
| 'resolution'
|
|
7
|
-
| 'flex'
|
|
8
|
-
| 'percentage';
|
|
9
|
-
/**
|
|
10
|
-
* @param {string} unit
|
|
11
|
-
* @return {BaseType | null}
|
|
12
|
-
*/
|
|
13
|
-
export function baseOf(unit: string): BaseType | null;
|
|
14
|
-
/**
|
|
15
|
-
* Convert a value within a single conversion family. Returns null when
|
|
16
|
-
* either unit is missing from the table (em/rem/vw need runtime context)
|
|
17
|
-
* or when the units belong to different base types.
|
|
18
|
-
* @param {number} value
|
|
19
|
-
* @param {string} from
|
|
20
|
-
* @param {string} to
|
|
21
|
-
* @return {number | null}
|
|
22
|
-
*/
|
|
23
|
-
export function convert(value: number, from: string, to: string): number | null;
|