postcss-calc 11.1.0 → 11.1.2
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 +38 -8
- package/package.json +6 -5
- package/src/index.js +16 -4
- package/src/lib/convertUnits.js +15 -1
- package/src/lib/node.js +22 -18
- package/src/lib/opaque.js +40 -0
- package/src/lib/parser.js +381 -200
- package/src/lib/serialize.js +49 -16
- package/src/lib/simplify/abs.js +1 -1
- package/src/lib/simplify/bucket.js +18 -23
- package/src/lib/simplify/call.js +8 -1
- package/src/lib/simplify/product.js +7 -4
- package/src/lib/simplify/sum.js +8 -5
- package/src/lib/tokenizer.js +6 -135
- package/src/reduce.js +22 -14
- package/types/lib/convertUnits.d.ts +8 -1
- package/types/lib/node.d.ts +12 -6
- package/types/lib/opaque.d.ts +15 -0
- package/types/lib/parser.d.ts +40 -36
- package/types/lib/serialize.d.ts +4 -0
- package/types/lib/simplify/bucket.d.ts +2 -2
- package/types/lib/tokenizer.d.ts +3 -32
- package/types/reduce.d.ts +4 -1
package/README.md
CHANGED
|
@@ -68,7 +68,8 @@ reduceCalc('min(50px, calc(2 * 40px))');
|
|
|
68
68
|
// => '50px'
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
It accepts `precision`, `warnWhenCannotResolve`, `onParseError`,
|
|
71
|
+
It accepts `precision`, `unwrapSingleNegativeNumber`, `warnWhenCannotResolve`, `onParseError`,
|
|
72
|
+
and `onWarn`:
|
|
72
73
|
|
|
73
74
|
```js
|
|
74
75
|
const result = reduceCalc('calc(100% + var(--gap))', {
|
|
@@ -84,7 +85,30 @@ const result = reduceCalc('calc(100% + var(--gap))', {
|
|
|
84
85
|
Unlike the PostCSS plugin, the standalone reducer does not show warnings
|
|
85
86
|
by default; provide `onParseError` and/or `onWarn` if you want diagnostics.
|
|
86
87
|
|
|
87
|
-
###
|
|
88
|
+
### Standalone reducer options
|
|
89
|
+
|
|
90
|
+
#### `unwrapSingleNegativeNumber` (default: `false`)
|
|
91
|
+
|
|
92
|
+
Controls whether a finite negative result is serialized as a bare value or
|
|
93
|
+
wrapped in `calc()`. Keep the default when reducing declaration values; set it
|
|
94
|
+
to `true` when the surrounding CSS context requires a bare negative value, such
|
|
95
|
+
as a selector:
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
reduceCalc('calc(5px - 10px)');
|
|
99
|
+
// => 'calc(-5px)'
|
|
100
|
+
|
|
101
|
+
reduceCalc('calc(5px - 10px)', { unwrapNegativeNumbers: true });
|
|
102
|
+
// => '-5px'
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### PostCSS plugin options
|
|
106
|
+
|
|
107
|
+
These options apply when using the PostCSS plugin:
|
|
108
|
+
|
|
109
|
+
```js
|
|
110
|
+
postcss().use(calc({ precision: 10 }));
|
|
111
|
+
```
|
|
88
112
|
|
|
89
113
|
#### `precision` (default: `5`)
|
|
90
114
|
|
|
@@ -139,7 +163,11 @@ With `mediaQueries: true`, this becomes:
|
|
|
139
163
|
|
|
140
164
|
#### `selectors` (default: `false`)
|
|
141
165
|
|
|
142
|
-
|
|
166
|
+
Reduces `calc()` functions found in selectors. Selectors do not accept
|
|
167
|
+
`calc()` functions, so the plugin replaces them with their reduced values.
|
|
168
|
+
Finite negative results are serialized as bare values because a selector cannot
|
|
169
|
+
contain a `calc()` function; the plugin enables `unwrapSingleNegativeNumber` automatically
|
|
170
|
+
for selectors.
|
|
143
171
|
|
|
144
172
|
```js
|
|
145
173
|
var out = postcss()
|
|
@@ -163,11 +191,13 @@ Callback invoked when a `calc()` body fails to parse or simplify. Matches
|
|
|
163
191
|
[`@csstools/css-calc`][csstools-css-calc]'s shape:
|
|
164
192
|
|
|
165
193
|
```js
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
194
|
+
postcss().use(
|
|
195
|
+
calc({
|
|
196
|
+
onParseError: (err, input) => {
|
|
197
|
+
throw err; // or log, route to a different channel, etc.
|
|
198
|
+
},
|
|
199
|
+
})
|
|
200
|
+
);
|
|
171
201
|
```
|
|
172
202
|
|
|
173
203
|
When omitted, errors are reported via PostCSS `result.warn()` so the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postcss-calc",
|
|
3
|
-
"version": "11.1.
|
|
3
|
+
"version": "11.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "PostCSS plugin to reduce calc()",
|
|
6
6
|
"keywords": [
|
|
@@ -38,16 +38,16 @@
|
|
|
38
38
|
"devEngines": {
|
|
39
39
|
"packageManager": {
|
|
40
40
|
"name": "pnpm",
|
|
41
|
-
"version": "
|
|
41
|
+
"version": "12.3.4"
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@csstools/css-calc": "^3.3.0",
|
|
46
46
|
"@rmenke/css-tokenizer-tests": "^1.2.0",
|
|
47
|
-
"@types/node": "^26.
|
|
47
|
+
"@types/node": "^26.5.0",
|
|
48
48
|
"fast-check": "^4.9.0",
|
|
49
|
-
"oxfmt": "^0.
|
|
50
|
-
"oxlint": "^1.
|
|
49
|
+
"oxfmt": "^0.67.0",
|
|
50
|
+
"oxlint": "^1.82.0",
|
|
51
51
|
"postcss": "^8.5.28",
|
|
52
52
|
"typescript": "~7.0.2"
|
|
53
53
|
},
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"lint": "oxlint . && tsc && oxfmt --check",
|
|
62
62
|
"fmt": "oxfmt",
|
|
63
63
|
"benchmark:arithmetic-chains": "node scripts/benchmark-arithmetic-chains.mjs",
|
|
64
|
+
"benchmark:nested-fallbacks": "node scripts/benchmark-nested-fallbacks.mjs",
|
|
64
65
|
"test": "node --test --test-reporter=dot 'test/**/*.test.mjs' test/index.cjs test/convertUnit.cjs",
|
|
65
66
|
"test:mutation:corpus": "node test/mutation/corpus-selection.mjs",
|
|
66
67
|
"test:corpus:full": "POSTCSS_CALC_FULL_CORPUS=1 node --test test/conformance/corpus.test.mjs"
|
package/src/index.js
CHANGED
|
@@ -24,9 +24,17 @@ import reduceCalc, { hasPotentialMathFunction } from './reduce.js';
|
|
|
24
24
|
* @param {(target: import('postcss').ChildNode, value: string) => void} setProp
|
|
25
25
|
* @param {ResolvedOptions} options
|
|
26
26
|
* @param {import('postcss').Result} result
|
|
27
|
+
* @param {boolean} unwrapSingleNegativeNumber
|
|
27
28
|
* @return {void}
|
|
28
29
|
*/
|
|
29
|
-
function applyTransform(
|
|
30
|
+
function applyTransform(
|
|
31
|
+
node,
|
|
32
|
+
current,
|
|
33
|
+
setProp,
|
|
34
|
+
options,
|
|
35
|
+
result,
|
|
36
|
+
unwrapSingleNegativeNumber
|
|
37
|
+
) {
|
|
30
38
|
if (!hasPotentialMathFunction(current)) {
|
|
31
39
|
return;
|
|
32
40
|
}
|
|
@@ -41,6 +49,7 @@ function applyTransform(node, current, setProp, options, result) {
|
|
|
41
49
|
onWarn: (message) => {
|
|
42
50
|
result.warn(message, { plugin: 'postcss-calc', node });
|
|
43
51
|
},
|
|
52
|
+
unwrapSingleNegativeNumber,
|
|
44
53
|
});
|
|
45
54
|
if (transformed !== current) {
|
|
46
55
|
setProp(node, transformed);
|
|
@@ -77,7 +86,8 @@ function pluginCreator(opts) {
|
|
|
77
86
|
/** @type {import('postcss').Declaration} */ (n).value = v;
|
|
78
87
|
},
|
|
79
88
|
options,
|
|
80
|
-
result
|
|
89
|
+
result,
|
|
90
|
+
false
|
|
81
91
|
);
|
|
82
92
|
}
|
|
83
93
|
if (node.type === 'atrule' && options.mediaQueries) {
|
|
@@ -88,7 +98,8 @@ function pluginCreator(opts) {
|
|
|
88
98
|
/** @type {import('postcss').AtRule} */ (n).params = v;
|
|
89
99
|
},
|
|
90
100
|
options,
|
|
91
|
-
result
|
|
101
|
+
result,
|
|
102
|
+
false
|
|
92
103
|
);
|
|
93
104
|
}
|
|
94
105
|
if (node.type === 'rule' && options.selectors) {
|
|
@@ -101,7 +112,8 @@ function pluginCreator(opts) {
|
|
|
101
112
|
/** @type {import('postcss').Rule} */ (n).selector = v;
|
|
102
113
|
},
|
|
103
114
|
options,
|
|
104
|
-
result
|
|
115
|
+
result,
|
|
116
|
+
true
|
|
105
117
|
);
|
|
106
118
|
}
|
|
107
119
|
});
|
package/src/lib/convertUnits.js
CHANGED
|
@@ -139,4 +139,18 @@ function convert(value, from, to) {
|
|
|
139
139
|
return (value * f) / t;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
|
|
142
|
+
/**
|
|
143
|
+
* Return a base type only for units present in the static conversion table.
|
|
144
|
+
* Units with context-dependent values (em, rem, vw, etc.) return null.
|
|
145
|
+
* @param {string} unit
|
|
146
|
+
* @return {BaseType | null}
|
|
147
|
+
*/
|
|
148
|
+
function staticBaseOf(unit) {
|
|
149
|
+
const key = unit.toLowerCase();
|
|
150
|
+
if (TO_CANONICAL[key] === undefined) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
return UNIT_TO_BASE[key] ?? null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export { baseOf, staticBaseOf, convert };
|
package/src/lib/node.js
CHANGED
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* @typedef {{type: 'Num', value: number}} Num
|
|
19
|
-
* @typedef {{type: 'Dim', value: number, unit: string}} Dim
|
|
20
|
-
* @typedef {{type: 'Ident', name: string}} Ident
|
|
21
|
-
* @typedef {{type: 'Call', name: string, args: Node[]}} Call
|
|
19
|
+
* @typedef {{type: 'Dim', value: number, unit: string, rawUnit?: string}} Dim
|
|
20
|
+
* @typedef {{type: 'Ident', name: string, rawName?: string}} Ident
|
|
21
|
+
* @typedef {{type: 'Call', name: string, args: Node[], rawName?: string}} Call
|
|
22
22
|
* @typedef {{sign: 1 | -1, node: Node}} SumTerm Sign is always +1 when node is Num or Dim.
|
|
23
23
|
* @typedef {{type: 'Sum', terms: SumTerm[], grouped?: boolean}} Sum
|
|
24
24
|
* @typedef {{exponent: 1 | -1, node: Node}} ProductFactor exponent +1 = numerator, -1 = denominator.
|
|
@@ -37,27 +37,36 @@ function num(value) {
|
|
|
37
37
|
/**
|
|
38
38
|
* @param {number} value
|
|
39
39
|
* @param {string} unit
|
|
40
|
+
* @param {string} [rawUnit]
|
|
40
41
|
* @return {Dim}
|
|
41
42
|
*/
|
|
42
|
-
function dim(value, unit) {
|
|
43
|
-
return
|
|
43
|
+
function dim(value, unit, rawUnit) {
|
|
44
|
+
return rawUnit === undefined
|
|
45
|
+
? { type: 'Dim', value, unit }
|
|
46
|
+
: { type: 'Dim', value, unit, rawUnit };
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
/**
|
|
47
50
|
* @param {string} name
|
|
51
|
+
* @param {string} [rawName]
|
|
48
52
|
* @return {Ident}
|
|
49
53
|
*/
|
|
50
|
-
function ident(name) {
|
|
51
|
-
return
|
|
54
|
+
function ident(name, rawName) {
|
|
55
|
+
return rawName === undefined
|
|
56
|
+
? { type: 'Ident', name }
|
|
57
|
+
: { type: 'Ident', name, rawName };
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
/**
|
|
55
61
|
* @param {string} name
|
|
56
62
|
* @param {Node[]} args
|
|
63
|
+
* @param {string} [rawName]
|
|
57
64
|
* @return {Call}
|
|
58
65
|
*/
|
|
59
|
-
function call(name, args) {
|
|
60
|
-
return
|
|
66
|
+
function call(name, args, rawName) {
|
|
67
|
+
return rawName === undefined
|
|
68
|
+
? { type: 'Call', name, args }
|
|
69
|
+
: { type: 'Call', name, args, rawName };
|
|
61
70
|
}
|
|
62
71
|
|
|
63
72
|
/**
|
|
@@ -99,14 +108,9 @@ function pushSumTerm(out, term) {
|
|
|
99
108
|
|
|
100
109
|
// sign=-1 around a Num/Dim leaf collapses into the value's sign — the
|
|
101
110
|
// canonical-form rule downstream code relies on.
|
|
102
|
-
if (sign === -1) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
sign = 1;
|
|
106
|
-
} else if (node.type === 'Dim') {
|
|
107
|
-
node = dim(-node.value, node.unit);
|
|
108
|
-
sign = 1;
|
|
109
|
-
}
|
|
111
|
+
if (sign === -1 && (node.type === 'Num' || node.type === 'Dim')) {
|
|
112
|
+
node = negate(node);
|
|
113
|
+
sign = 1;
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
// Drop zero-valued Nums. Dims with value 0 stay — the unit carries type.
|
|
@@ -169,7 +173,7 @@ function negate(node) {
|
|
|
169
173
|
return num(-node.value);
|
|
170
174
|
}
|
|
171
175
|
if (node.type === 'Dim') {
|
|
172
|
-
return dim(-node.value, node.unit);
|
|
176
|
+
return dim(-node.value, node.unit, node.rawUnit);
|
|
173
177
|
}
|
|
174
178
|
if (node.type === 'Sum') {
|
|
175
179
|
const result = mkSum(
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Private metadata for opaque function contents (including var() fallbacks).
|
|
2
|
+
// Keeping it in a WeakMap means the public calculation AST remains unchanged.
|
|
3
|
+
/** @typedef {import('./node.js').Node} Node */
|
|
4
|
+
/** @typedef {string | Node | Component[]} Component */
|
|
5
|
+
/** @type {WeakMap<Extract<Node, {type: 'Call'}>, Component[]>} */
|
|
6
|
+
const components = new WeakMap();
|
|
7
|
+
/** @param {Extract<Node, {type: 'Call'}>} node @param {Component[]} tree */
|
|
8
|
+
function setComponents(node, tree) {
|
|
9
|
+
components.set(node, tree);
|
|
10
|
+
return node;
|
|
11
|
+
}
|
|
12
|
+
/** @param {Extract<Node, {type: 'Call'}>} node */
|
|
13
|
+
function getComponents(node) {
|
|
14
|
+
return components.get(node);
|
|
15
|
+
}
|
|
16
|
+
/** @param {Component[]} tree @param {(node: Node) => Node} simplify @return {Component[]} */
|
|
17
|
+
function simplifyComponents(tree, simplify) {
|
|
18
|
+
return tree.map((part) => {
|
|
19
|
+
if (typeof part === 'string') return part;
|
|
20
|
+
if (Array.isArray(part)) return simplifyComponents(part, simplify);
|
|
21
|
+
return simplify(part);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/** @param {Component[]} tree @param {(node: Node) => string} serialize @return {string} */
|
|
25
|
+
function serializeComponents(tree, serialize) {
|
|
26
|
+
let result = '';
|
|
27
|
+
for (const part of tree) {
|
|
28
|
+
if (typeof part === 'string') result += part;
|
|
29
|
+
else if (Array.isArray(part))
|
|
30
|
+
result += serializeComponents(part, serialize);
|
|
31
|
+
else result += serialize(part);
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
getComponents,
|
|
37
|
+
setComponents,
|
|
38
|
+
simplifyComponents,
|
|
39
|
+
serializeComponents,
|
|
40
|
+
};
|