postcss-calc 11.1.0 → 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 +38 -8
- package/package.json +1 -1
- package/src/index.js +16 -4
- package/src/lib/serialize.js +36 -11
- package/src/reduce.js +21 -10
- package/types/lib/serialize.d.ts +4 -0
- 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
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/serialize.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Spec: https://www.w3.org/TR/css-values-4/#serialize-a-calculation-tree
|
|
2
|
-
// Outer calc() is added
|
|
3
|
-
//
|
|
4
|
-
// are ever required on valid canonical input.
|
|
2
|
+
// Outer calc() is added when the top-level result contains an arithmetic
|
|
3
|
+
// operator, or when a finite scalar is negative.
|
|
5
4
|
|
|
6
5
|
import { num, dim } from './node.js';
|
|
7
6
|
|
|
@@ -13,6 +12,7 @@ import { num, dim } from './node.js';
|
|
|
13
12
|
* @typedef {object} SerializeOptions
|
|
14
13
|
* @property {number | false} [precision] Decimal places for numbers. `false` disables rounding. Default 5.
|
|
15
14
|
* @property {string} [calcName] Wrapper name to use when `calc()` is needed. Default `'calc'`.
|
|
15
|
+
* @property {boolean} [unwrapSingleNegativeNumber] Serialize finite negative scalars without a wrapper. Internal selector-only mode.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
// Below this is float noise, not a value: `0.1 + 0.2 - 0.3` is 5.5e-17.
|
|
@@ -80,6 +80,20 @@ function serializeNumber(v) {
|
|
|
80
80
|
return text;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Round and serialize a finite scalar once so callers can use the same value
|
|
85
|
+
* to decide its syntactic context and render its text.
|
|
86
|
+
*
|
|
87
|
+
* @param {import('./node.js').Num | import('./node.js').Dim} node
|
|
88
|
+
* @param {number | false} prec
|
|
89
|
+
* @return {{value: number, text: string}}
|
|
90
|
+
*/
|
|
91
|
+
function serializeScalar(node, prec) {
|
|
92
|
+
const value = round(node.value, prec);
|
|
93
|
+
const text = `${serializeNumber(value)}${node.type === 'Dim' ? node.unit : ''}`;
|
|
94
|
+
return { value, text };
|
|
95
|
+
}
|
|
96
|
+
|
|
83
97
|
/**
|
|
84
98
|
* @param {Node} node
|
|
85
99
|
* @param {SerializeOptions} [opts]
|
|
@@ -98,6 +112,22 @@ function serialize(node, opts = {}) {
|
|
|
98
112
|
return `${calcName}(${degenerateKeyword(node.value)} * 1${node.unit})`;
|
|
99
113
|
}
|
|
100
114
|
|
|
115
|
+
if (node.type === 'Num' || node.type === 'Dim') {
|
|
116
|
+
const scalar = serializeScalar(node, prec);
|
|
117
|
+
|
|
118
|
+
// A finite negative scalar must stay inside calc() so CSS parses it as a
|
|
119
|
+
// calculation result (and can apply range clamping) rather than as an
|
|
120
|
+
// invalid bare value. Base this on the serialized value so tiny negative
|
|
121
|
+
// floating-point noise that rounds to zero does not get wrapped.
|
|
122
|
+
if (scalar.value < 0) {
|
|
123
|
+
return opts.unwrapSingleNegativeNumber
|
|
124
|
+
? scalar.text
|
|
125
|
+
: `${calcName}(${scalar.text})`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return scalar.text;
|
|
129
|
+
}
|
|
130
|
+
|
|
101
131
|
// A grouped sum with a leading negative term is the canonical result of
|
|
102
132
|
// negating a parenthesized expression. Re-invert its terms for the body so
|
|
103
133
|
// the grouping survives as `-(...)` instead of becoming `-a - b`.
|
|
@@ -114,12 +144,7 @@ function serialize(node, opts = {}) {
|
|
|
114
144
|
return `${calcName}(-(${serializeSumTerms(invertedTerms, prec)}))`;
|
|
115
145
|
}
|
|
116
146
|
|
|
117
|
-
if (
|
|
118
|
-
node.type === 'Num' ||
|
|
119
|
-
node.type === 'Dim' ||
|
|
120
|
-
node.type === 'Ident' ||
|
|
121
|
-
node.type === 'Call'
|
|
122
|
-
) {
|
|
147
|
+
if (node.type === 'Ident' || node.type === 'Call') {
|
|
123
148
|
return serializeExpr(node, prec);
|
|
124
149
|
}
|
|
125
150
|
|
|
@@ -145,7 +170,7 @@ function serializeExpr(node, prec) {
|
|
|
145
170
|
if (isDegenerate(node.value)) {
|
|
146
171
|
return degenerateKeyword(node.value);
|
|
147
172
|
}
|
|
148
|
-
return
|
|
173
|
+
return serializeScalar(node, prec).text;
|
|
149
174
|
case 'Dim':
|
|
150
175
|
if (isDegenerate(node.value)) {
|
|
151
176
|
// Nested degenerate Dim wraps in calc() so the `<kw> * 1<unit>` form
|
|
@@ -153,7 +178,7 @@ function serializeExpr(node, prec) {
|
|
|
153
178
|
// inside a Product — `0 * Dim(Infinity, px)` would re-fold as NaN.
|
|
154
179
|
return `calc(${degenerateKeyword(node.value)} * 1${node.unit})`;
|
|
155
180
|
}
|
|
156
|
-
return
|
|
181
|
+
return serializeScalar(node, prec).text;
|
|
157
182
|
case 'Ident':
|
|
158
183
|
return node.name;
|
|
159
184
|
case 'Call': {
|
package/src/reduce.js
CHANGED
|
@@ -27,6 +27,7 @@ const BLOCK_CLOSE = new Map([
|
|
|
27
27
|
* @typedef {object} ReduceCalcOptions
|
|
28
28
|
* @property {number | false} [precision]
|
|
29
29
|
* @property {boolean} [warnWhenCannotResolve]
|
|
30
|
+
* @property {boolean} [unwrapSingleNegativeNumber] Serialize finite negative results without a `calc()` wrapper. Defaults to `false`.
|
|
30
31
|
* @property {(error: Error, input: string) => void} [onParseError] Invoked when parse/simplify throws.
|
|
31
32
|
* @property {(message: string) => void} [onWarn] Invoked when `warnWhenCannotResolve` is set and an expression cannot be reduced to a single value.
|
|
32
33
|
*/
|
|
@@ -34,9 +35,7 @@ const BLOCK_CLOSE = new Map([
|
|
|
34
35
|
/** @typedef {Required<Omit<ReduceCalcOptions, 'onParseError' | 'onWarn'>> & Pick<ReduceCalcOptions, 'onParseError' | 'onWarn'>} ResolvedReduceCalcOptions */
|
|
35
36
|
|
|
36
37
|
/**
|
|
37
|
-
* Fields threaded
|
|
38
|
-
* `value` is the original full property text, used only for the
|
|
39
|
-
* warnWhenCannotResolve message.
|
|
38
|
+
* Fields threaded through the internal token-range walk.
|
|
40
39
|
*
|
|
41
40
|
* @typedef {object} TransformContext
|
|
42
41
|
* @property {ResolvedReduceCalcOptions} options
|
|
@@ -51,7 +50,6 @@ const BLOCK_CLOSE = new Map([
|
|
|
51
50
|
* @property {number} end
|
|
52
51
|
* @property {import('./lib/node.js').Node} node
|
|
53
52
|
* @property {string} calcName
|
|
54
|
-
* @property {string} matchedName
|
|
55
53
|
*/
|
|
56
54
|
|
|
57
55
|
/**
|
|
@@ -105,7 +103,6 @@ function walkTokens(start, expectedClose, ctx, transform) {
|
|
|
105
103
|
end,
|
|
106
104
|
node,
|
|
107
105
|
calcName: isCalc ? name : 'calc',
|
|
108
|
-
matchedName: name,
|
|
109
106
|
});
|
|
110
107
|
} catch (error) {
|
|
111
108
|
const err = error instanceof Error ? error : new Error('Error');
|
|
@@ -117,6 +114,17 @@ function walkTokens(start, expectedClose, ctx, transform) {
|
|
|
117
114
|
return ctx.tokens.length - 1;
|
|
118
115
|
}
|
|
119
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
|
+
|
|
120
128
|
/**
|
|
121
129
|
* Simplify every supported CSS math function in a component-value string.
|
|
122
130
|
* Text outside those functions is preserved byte-for-byte.
|
|
@@ -131,7 +139,12 @@ function reduceCalc(value, opts) {
|
|
|
131
139
|
}
|
|
132
140
|
|
|
133
141
|
/** @type {ResolvedReduceCalcOptions} */
|
|
134
|
-
const options = {
|
|
142
|
+
const options = {
|
|
143
|
+
precision: 5,
|
|
144
|
+
warnWhenCannotResolve: false,
|
|
145
|
+
unwrapSingleNegativeNumber: false,
|
|
146
|
+
...opts,
|
|
147
|
+
};
|
|
135
148
|
const tokens = cssTokenize({ css: value });
|
|
136
149
|
/** @type {Replacement[]} */
|
|
137
150
|
const replacements = [];
|
|
@@ -147,11 +160,9 @@ function reduceCalc(value, opts) {
|
|
|
147
160
|
const text = serialize(replacement.node, {
|
|
148
161
|
precision: options.precision,
|
|
149
162
|
calcName: replacement.calcName,
|
|
163
|
+
unwrapSingleNegativeNumber: options.unwrapSingleNegativeNumber,
|
|
150
164
|
});
|
|
151
|
-
if (
|
|
152
|
-
options.warnWhenCannotResolve &&
|
|
153
|
-
text.startsWith(`${replacement.matchedName}(`)
|
|
154
|
-
) {
|
|
165
|
+
if (options.warnWhenCannotResolve && isUnresolvedResult(replacement.node)) {
|
|
155
166
|
options.onWarn?.('Could not reduce expression: ' + value);
|
|
156
167
|
}
|
|
157
168
|
output += value.slice(lastIndex, replacement.start) + text;
|
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
|
package/types/reduce.d.ts
CHANGED
|
@@ -2,6 +2,10 @@ import { hasPotentialMathFunction, QUICK_MATH_TEST } from './lib/simplify/call.j
|
|
|
2
2
|
export type ReduceCalcOptions = {
|
|
3
3
|
precision?: number | false;
|
|
4
4
|
warnWhenCannotResolve?: boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Serialize finite negative results without a `calc()` wrapper. Defaults to `false`.
|
|
7
|
+
*/
|
|
8
|
+
unwrapSingleNegativeNumber?: boolean;
|
|
5
9
|
/**
|
|
6
10
|
* Invoked when parse/simplify throws.
|
|
7
11
|
*/
|
|
@@ -23,7 +27,6 @@ export type Replacement = {
|
|
|
23
27
|
end: number;
|
|
24
28
|
node: import('./lib/node.js').Node;
|
|
25
29
|
calcName: string;
|
|
26
|
-
matchedName: string;
|
|
27
30
|
};
|
|
28
31
|
/**
|
|
29
32
|
* Simplify every supported CSS math function in a component-value string.
|