postcss-calc 11.0.0-rc.2 → 11.0.0-rc.3
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/package.json +11 -5
- package/src/lib/node.js +9 -4
- package/src/lib/parser.js +3 -3
- package/src/lib/serialize.js +28 -1
- package/src/lib/simplify/sum.js +18 -1
- package/types/lib/node.d.ts +2 -1
- package/types/lib/type.d.ts +8 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postcss-calc",
|
|
3
|
-
"version": "11.0.0-rc.
|
|
3
|
+
"version": "11.0.0-rc.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "PostCSS plugin to reduce calc()",
|
|
6
6
|
"keywords": [
|
|
@@ -31,14 +31,20 @@
|
|
|
31
31
|
"engines": {
|
|
32
32
|
"node": "^22.18 || ^24.11 || >=26.0"
|
|
33
33
|
},
|
|
34
|
+
"devEngines": {
|
|
35
|
+
"packageManager": {
|
|
36
|
+
"name": "pnpm",
|
|
37
|
+
"version": "11.21.0"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
34
40
|
"devDependencies": {
|
|
35
41
|
"@csstools/css-calc": "^3.3.0",
|
|
36
42
|
"@rmenke/css-tokenizer-tests": "^1.2.0",
|
|
37
|
-
"@types/node": "^26.
|
|
43
|
+
"@types/node": "^26.2.0",
|
|
38
44
|
"fast-check": "^4.9.0",
|
|
39
|
-
"oxfmt": "^0.
|
|
40
|
-
"oxlint": "^1.
|
|
41
|
-
"postcss": "^8.5.
|
|
45
|
+
"oxfmt": "^0.63.0",
|
|
46
|
+
"oxlint": "^1.78.0",
|
|
47
|
+
"postcss": "^8.5.26",
|
|
42
48
|
"typescript": "~7.0.2"
|
|
43
49
|
},
|
|
44
50
|
"dependencies": {
|
package/src/lib/node.js
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
// is `Num(-5)`, never `Sum([{sign:-1, Num(5)}])`. One form per value.
|
|
6
6
|
// - In a SumTerm with Num/Dim node, sign is always +1; the sign slot is
|
|
7
7
|
// reserved for opaque nodes (Ident, Call, Product, multi-term Sum).
|
|
8
|
-
// - No Sum directly contains another Sum (flattened on
|
|
8
|
+
// - No ungrouped Sum directly contains another Sum (flattened on
|
|
9
|
+
// construction). A grouped Sum is retained so a later negative sign
|
|
10
|
+
// cannot be distributed across opaque terms.
|
|
9
11
|
// - No Product directly contains another Product (flattened).
|
|
10
12
|
// - A Sum/Product with one positive element collapses to that element.
|
|
11
13
|
// - A Sum/Product with no elements collapses to Num(0) / Num(1).
|
|
@@ -18,7 +20,7 @@
|
|
|
18
20
|
* @typedef {{type: 'Ident', name: string}} Ident
|
|
19
21
|
* @typedef {{type: 'Call', name: string, args: Node[]}} Call
|
|
20
22
|
* @typedef {{sign: 1 | -1, node: Node}} SumTerm Sign is always +1 when node is Num or Dim.
|
|
21
|
-
* @typedef {{type: 'Sum', terms: SumTerm[]}} Sum
|
|
23
|
+
* @typedef {{type: 'Sum', terms: SumTerm[], grouped?: boolean}} Sum
|
|
22
24
|
* @typedef {{exponent: 1 | -1, node: Node}} ProductFactor exponent +1 = numerator, -1 = denominator.
|
|
23
25
|
* @typedef {{type: 'Product', factors: ProductFactor[]}} Product
|
|
24
26
|
* @typedef {Num | Dim | Ident | Call | Sum | Product} Node
|
|
@@ -85,7 +87,7 @@ function mkSum(rawTerms) {
|
|
|
85
87
|
function pushSumTerm(out, term) {
|
|
86
88
|
let { sign, node } = term;
|
|
87
89
|
|
|
88
|
-
if (node.type === 'Sum') {
|
|
90
|
+
if (node.type === 'Sum' && !node.grouped) {
|
|
89
91
|
for (const inner of node.terms) {
|
|
90
92
|
pushSumTerm(out, {
|
|
91
93
|
sign: /** @type {1 | -1} */ (sign * inner.sign),
|
|
@@ -170,12 +172,15 @@ function negate(node) {
|
|
|
170
172
|
return dim(-node.value, node.unit);
|
|
171
173
|
}
|
|
172
174
|
if (node.type === 'Sum') {
|
|
173
|
-
|
|
175
|
+
const result = mkSum(
|
|
174
176
|
node.terms.map((t) => ({
|
|
175
177
|
sign: /** @type {1 | -1} */ (-t.sign),
|
|
176
178
|
node: t.node,
|
|
177
179
|
}))
|
|
178
180
|
);
|
|
181
|
+
return node.grouped && result.type === 'Sum'
|
|
182
|
+
? { ...result, grouped: true }
|
|
183
|
+
: result;
|
|
179
184
|
}
|
|
180
185
|
// Opaque (Ident, Call, Product): wrap as a single negative-sign term —
|
|
181
186
|
// the only case where sign=-1 remains on a SumTerm.
|
package/src/lib/parser.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Pratt parser. +/- emit Sum nodes; */÷ emit Product nodes. node.js
|
|
2
|
-
// constructors flatten and normalize on construction,
|
|
3
|
-
//
|
|
2
|
+
// constructors flatten and normalize on construction, while parenthesized
|
|
3
|
+
// sums retain a grouping marker for the opaque-subtraction invariant.
|
|
4
4
|
import { mkSum, mkProduct, negate } from './node.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -266,7 +266,7 @@ const PREFIX = {
|
|
|
266
266
|
'(': (p) => {
|
|
267
267
|
const e = p.parseExpr(0);
|
|
268
268
|
p.expect('punct', ')');
|
|
269
|
-
return e;
|
|
269
|
+
return e.type === 'Sum' ? { ...e, grouped: true } : e;
|
|
270
270
|
},
|
|
271
271
|
|
|
272
272
|
'-': (p) => negate(p.parseExpr(UNARY_BP)),
|
package/src/lib/serialize.js
CHANGED
|
@@ -77,6 +77,25 @@ function serialize(node, opts = {}) {
|
|
|
77
77
|
return `${calcName}(${degenerateKeyword(node.value)} * 1${node.unit})`;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
// A grouped sum with a leading negative term is the canonical result of
|
|
81
|
+
// negating a parenthesized expression. Re-invert its terms for the body so
|
|
82
|
+
// the grouping survives as `-(...)` instead of becoming `-a - b`.
|
|
83
|
+
if (
|
|
84
|
+
node.type === 'Sum' &&
|
|
85
|
+
node.grouped &&
|
|
86
|
+
node.terms.length > 1 &&
|
|
87
|
+
displaySign(node.terms[0]).sign === -1
|
|
88
|
+
) {
|
|
89
|
+
const body = /** @type {Sum} */ ({
|
|
90
|
+
type: 'Sum',
|
|
91
|
+
terms: node.terms.map((t) => ({
|
|
92
|
+
sign: /** @type {1 | -1} */ (-t.sign),
|
|
93
|
+
node: t.node,
|
|
94
|
+
})),
|
|
95
|
+
});
|
|
96
|
+
return `${calcName}(-(${serializeExpr(body, prec)}))`;
|
|
97
|
+
}
|
|
98
|
+
|
|
80
99
|
if (
|
|
81
100
|
node.type === 'Num' ||
|
|
82
101
|
node.type === 'Dim' ||
|
|
@@ -166,13 +185,21 @@ function serializeSum(sum, prec) {
|
|
|
166
185
|
for (const [i, t] of sum.terms.entries()) {
|
|
167
186
|
const { sign, magnitude } = displaySign(t);
|
|
168
187
|
if (i === 0) {
|
|
188
|
+
if (magnitude.type === 'Sum' && magnitude.grouped) {
|
|
189
|
+
const body = `(${serializeExpr(magnitude, prec)})`;
|
|
190
|
+
out = sign === 1 ? body : `-${body}`;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
169
193
|
out =
|
|
170
194
|
sign === 1
|
|
171
195
|
? serializeExpr(magnitude, prec)
|
|
172
196
|
: serializeLeadingNeg(magnitude, prec);
|
|
173
197
|
} else {
|
|
174
198
|
// `-` binds looser than `*`/`/` so the right side never needs parens.
|
|
175
|
-
|
|
199
|
+
let body = serializeExpr(magnitude, prec);
|
|
200
|
+
if (magnitude.type === 'Sum' && magnitude.grouped) {
|
|
201
|
+
body = `(${body})`;
|
|
202
|
+
}
|
|
176
203
|
out += sign === 1 ? ` + ${body}` : ` - ${body}`;
|
|
177
204
|
}
|
|
178
205
|
}
|
package/src/lib/simplify/sum.js
CHANGED
|
@@ -48,6 +48,17 @@ function simplifySum(sum, simplify) {
|
|
|
48
48
|
*/
|
|
49
49
|
function processTerm(sign, n) {
|
|
50
50
|
if (n.type === 'Sum') {
|
|
51
|
+
// Parentheses around a sum are normally algebraically disposable. Keep
|
|
52
|
+
// them only for an opaque sum used negatively: distributing that sign
|
|
53
|
+
// changes the CSS value when a var() contains a sum of its own.
|
|
54
|
+
if (
|
|
55
|
+
n.grouped &&
|
|
56
|
+
sign === -1 &&
|
|
57
|
+
n.terms.some((t) => t.node.type !== 'Num' && t.node.type !== 'Dim')
|
|
58
|
+
) {
|
|
59
|
+
opaque.push({ sign, node: n });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
51
62
|
for (const inner of n.terms) {
|
|
52
63
|
processTerm(/** @type {1 | -1} */ (sign * inner.sign), inner.node);
|
|
53
64
|
}
|
|
@@ -95,7 +106,13 @@ function simplifySum(sum, simplify) {
|
|
|
95
106
|
}
|
|
96
107
|
terms.push(...opaque);
|
|
97
108
|
|
|
98
|
-
|
|
109
|
+
const result = mkSum(terms);
|
|
110
|
+
// Keep the source grouping available to an enclosing subtraction. Numeric
|
|
111
|
+
// groups have already folded and therefore do not need the marker.
|
|
112
|
+
if (sum.grouped && result.type === 'Sum' && opaque.length > 0) {
|
|
113
|
+
return { ...result, grouped: true };
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
99
116
|
}
|
|
100
117
|
|
|
101
118
|
export { simplifySum };
|
package/types/lib/node.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export type SumTerm = {
|
|
|
23
23
|
export type Sum = {
|
|
24
24
|
type: 'Sum';
|
|
25
25
|
terms: SumTerm[];
|
|
26
|
+
grouped?: boolean;
|
|
26
27
|
};
|
|
27
28
|
export type ProductFactor = {
|
|
28
29
|
exponent: 1 | -1;
|
|
@@ -39,7 +40,7 @@ export type Node = Num | Dim | Ident | Call | Sum | Product;
|
|
|
39
40
|
* @typedef {{type: 'Ident', name: string}} Ident
|
|
40
41
|
* @typedef {{type: 'Call', name: string, args: Node[]}} Call
|
|
41
42
|
* @typedef {{sign: 1 | -1, node: Node}} SumTerm Sign is always +1 when node is Num or Dim.
|
|
42
|
-
* @typedef {{type: 'Sum', terms: SumTerm[]}} Sum
|
|
43
|
+
* @typedef {{type: 'Sum', terms: SumTerm[], grouped?: boolean}} Sum
|
|
43
44
|
* @typedef {{exponent: 1 | -1, node: Node}} ProductFactor exponent +1 = numerator, -1 = denominator.
|
|
44
45
|
* @typedef {{type: 'Product', factors: ProductFactor[]}} Product
|
|
45
46
|
* @typedef {Num | Dim | Ident | Call | Sum | Product} Node
|
package/types/lib/type.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
export type BaseType =
|
|
1
|
+
export type BaseType =
|
|
2
|
+
| 'length'
|
|
3
|
+
| 'angle'
|
|
4
|
+
| 'time'
|
|
5
|
+
| 'frequency'
|
|
6
|
+
| 'resolution'
|
|
7
|
+
| 'flex'
|
|
8
|
+
| 'percentage';
|
|
2
9
|
/**
|
|
3
10
|
* @param {string} unit
|
|
4
11
|
* @return {BaseType | null}
|