postcss-calc 11.0.0-rc.1 → 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/README.md +23 -30
- package/package.json +25 -20
- package/src/index.js +39 -18
- package/src/lib/convertUnits.js +58 -21
- package/src/lib/node.js +10 -9
- package/src/lib/parser.js +17 -13
- package/src/lib/serialize.js +46 -16
- package/src/lib/simplify/abs.js +8 -6
- package/src/lib/simplify/atan2.js +14 -10
- package/src/lib/simplify/bucket.js +16 -11
- package/src/lib/simplify/call.js +57 -31
- package/src/lib/simplify/cancel.js +2 -4
- package/src/lib/simplify/clamp.js +3 -5
- package/src/lib/simplify/exp.js +2 -4
- package/src/lib/simplify/fold.js +14 -12
- package/src/lib/simplify/hypot.js +6 -6
- package/src/lib/simplify/inverse-trig.js +11 -7
- package/src/lib/simplify/log.js +3 -9
- package/src/lib/simplify/min-max.js +3 -5
- package/src/lib/simplify/mod-rem.js +23 -13
- package/src/lib/simplify/pow.js +2 -4
- package/src/lib/simplify/product.js +11 -9
- package/src/lib/simplify/round.js +24 -12
- package/src/lib/simplify/sign.js +8 -6
- package/src/lib/simplify/sqrt.js +2 -5
- package/src/lib/simplify/sum.js +26 -8
- package/src/lib/simplify/trig.js +12 -8
- package/src/lib/simplify.js +4 -8
- package/src/lib/tokenizer.js +11 -13
- package/types/index.d.ts +17 -17
- package/types/lib/convertUnits.d.ts +4 -3
- package/types/lib/node.d.ts +16 -20
- package/types/lib/parser.d.ts +13 -13
- package/types/lib/serialize.d.ts +8 -7
- package/types/lib/simplify/abs.d.ts +3 -2
- package/types/lib/simplify/atan2.d.ts +3 -2
- package/types/lib/simplify/bucket.d.ts +3 -2
- package/types/lib/simplify/call.d.ts +5 -4
- package/types/lib/simplify/cancel.d.ts +2 -1
- package/types/lib/simplify/clamp.d.ts +3 -2
- package/types/lib/simplify/exp.d.ts +3 -2
- package/types/lib/simplify/fold.d.ts +4 -3
- package/types/lib/simplify/hypot.d.ts +3 -2
- package/types/lib/simplify/inverse-trig.d.ts +3 -2
- package/types/lib/simplify/log.d.ts +3 -2
- package/types/lib/simplify/min-max.d.ts +3 -2
- package/types/lib/simplify/mod-rem.d.ts +3 -2
- package/types/lib/simplify/pow.d.ts +3 -2
- package/types/lib/simplify/product.d.ts +6 -5
- package/types/lib/simplify/round.d.ts +4 -3
- package/types/lib/simplify/sign.d.ts +3 -2
- package/types/lib/simplify/sqrt.d.ts +3 -2
- package/types/lib/simplify/sum.d.ts +7 -6
- package/types/lib/simplify/trig.d.ts +3 -2
- package/types/lib/simplify.d.ts +3 -6
- package/types/lib/tokenizer.d.ts +4 -3
- package/types/lib/type.d.ts +8 -1
package/README.md
CHANGED
|
@@ -17,18 +17,15 @@ npm install postcss-calc
|
|
|
17
17
|
|
|
18
18
|
```js
|
|
19
19
|
// dependencies
|
|
20
|
-
var fs = require(
|
|
21
|
-
var postcss = require(
|
|
22
|
-
var calc = require(
|
|
20
|
+
var fs = require('fs');
|
|
21
|
+
var postcss = require('postcss');
|
|
22
|
+
var calc = require('postcss-calc');
|
|
23
23
|
|
|
24
24
|
// css to be processed
|
|
25
|
-
var css = fs.readFileSync(
|
|
25
|
+
var css = fs.readFileSync('input.css', 'utf8');
|
|
26
26
|
|
|
27
27
|
// process css
|
|
28
|
-
var output = postcss()
|
|
29
|
-
.use(calc())
|
|
30
|
-
.process(css)
|
|
31
|
-
.css
|
|
28
|
+
var output = postcss().use(calc()).process(css).css;
|
|
32
29
|
```
|
|
33
30
|
|
|
34
31
|
Using this `input.css`:
|
|
@@ -37,7 +34,7 @@ Using this `input.css`:
|
|
|
37
34
|
h1 {
|
|
38
35
|
font-size: calc(16px * 2);
|
|
39
36
|
height: calc(100px - 2em);
|
|
40
|
-
width: calc(2*var(--base-width));
|
|
37
|
+
width: calc(2 * var(--base-width));
|
|
41
38
|
margin-bottom: calc(16px * 1.5);
|
|
42
39
|
}
|
|
43
40
|
```
|
|
@@ -48,10 +45,11 @@ you will get:
|
|
|
48
45
|
h1 {
|
|
49
46
|
font-size: 32px;
|
|
50
47
|
height: calc(100px - 2em);
|
|
51
|
-
width: calc(2*var(--base-width));
|
|
52
|
-
margin-bottom: 24px
|
|
48
|
+
width: calc(2 * var(--base-width));
|
|
49
|
+
margin-bottom: 24px;
|
|
53
50
|
}
|
|
54
51
|
```
|
|
52
|
+
|
|
55
53
|
Checkout [tests] for more examples.
|
|
56
54
|
|
|
57
55
|
### Options
|
|
@@ -62,9 +60,8 @@ Allow you to define the precision for decimal numbers.
|
|
|
62
60
|
|
|
63
61
|
```js
|
|
64
62
|
var out = postcss()
|
|
65
|
-
.use(calc({precision: 10}))
|
|
66
|
-
.process(css)
|
|
67
|
-
.css
|
|
63
|
+
.use(calc({ precision: 10 }))
|
|
64
|
+
.process(css).css;
|
|
68
65
|
```
|
|
69
66
|
|
|
70
67
|
#### `preserve` (default: `false`)
|
|
@@ -74,9 +71,8 @@ precision themselves.
|
|
|
74
71
|
|
|
75
72
|
```js
|
|
76
73
|
var out = postcss()
|
|
77
|
-
.use(calc({preserve: true}))
|
|
78
|
-
.process(css)
|
|
79
|
-
.css
|
|
74
|
+
.use(calc({ preserve: true }))
|
|
75
|
+
.process(css).css;
|
|
80
76
|
```
|
|
81
77
|
|
|
82
78
|
#### `warnWhenCannotResolve` (default: `false`)
|
|
@@ -85,9 +81,8 @@ Adds warnings when calc() are not reduced to a single value.
|
|
|
85
81
|
|
|
86
82
|
```js
|
|
87
83
|
var out = postcss()
|
|
88
|
-
.use(calc({warnWhenCannotResolve: true}))
|
|
89
|
-
.process(css)
|
|
90
|
-
.css
|
|
84
|
+
.use(calc({ warnWhenCannotResolve: true }))
|
|
85
|
+
.process(css).css;
|
|
91
86
|
```
|
|
92
87
|
|
|
93
88
|
#### `mediaQueries` (default: `false`)
|
|
@@ -96,9 +91,8 @@ Allows calc() usage as part of media query declarations.
|
|
|
96
91
|
|
|
97
92
|
```js
|
|
98
93
|
var out = postcss()
|
|
99
|
-
.use(calc({mediaQueries: true}))
|
|
100
|
-
.process(css)
|
|
101
|
-
.css
|
|
94
|
+
.use(calc({ mediaQueries: true }))
|
|
95
|
+
.process(css).css;
|
|
102
96
|
```
|
|
103
97
|
|
|
104
98
|
#### `selectors` (default: `false`)
|
|
@@ -107,15 +101,14 @@ Allows calc() usage as part of selectors.
|
|
|
107
101
|
|
|
108
102
|
```js
|
|
109
103
|
var out = postcss()
|
|
110
|
-
.use(calc({selectors: true}))
|
|
111
|
-
.process(css)
|
|
112
|
-
.css
|
|
104
|
+
.use(calc({ selectors: true }))
|
|
105
|
+
.process(css).css;
|
|
113
106
|
```
|
|
114
107
|
|
|
115
108
|
Example:
|
|
116
109
|
|
|
117
110
|
```css
|
|
118
|
-
div[data-size=
|
|
111
|
+
div[data-size='calc(3*3)'] {
|
|
119
112
|
width: 100px;
|
|
120
113
|
}
|
|
121
114
|
```
|
|
@@ -129,8 +122,8 @@ Callback invoked when a `calc()` body fails to parse or simplify. Matches
|
|
|
129
122
|
calc({
|
|
130
123
|
onParseError: (err, input) => {
|
|
131
124
|
throw err; // or log, route to a different channel, etc.
|
|
132
|
-
}
|
|
133
|
-
})
|
|
125
|
+
},
|
|
126
|
+
});
|
|
134
127
|
```
|
|
135
128
|
|
|
136
129
|
When omitted, errors are reported via PostCSS `result.warn()` so the
|
|
@@ -178,6 +171,7 @@ canonical-form decisions:
|
|
|
178
171
|
---
|
|
179
172
|
|
|
180
173
|
## Related PostCSS plugins
|
|
174
|
+
|
|
181
175
|
To replace the value of CSS custom properties at build time, try [PostCSS Custom Properties].
|
|
182
176
|
|
|
183
177
|
## Contributing
|
|
@@ -200,7 +194,6 @@ npm test
|
|
|
200
194
|
[git-url]: https://gitter.im/postcss/postcss
|
|
201
195
|
[npm-img]: https://img.shields.io/npm/v/postcss-calc.svg
|
|
202
196
|
[npm-url]: https://www.npmjs.com/package/postcss-calc
|
|
203
|
-
|
|
204
197
|
[PostCSS]: https://github.com/postcss
|
|
205
198
|
[PostCSS Calc]: https://github.com/postcss/postcss-calc
|
|
206
199
|
[PostCSS Custom Properties]: https://github.com/postcss/postcss-custom-properties
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postcss-calc",
|
|
3
|
-
"version": "11.0.0-rc.
|
|
3
|
+
"version": "11.0.0-rc.3",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"description": "PostCSS plugin to reduce calc()",
|
|
5
6
|
"keywords": [
|
|
6
7
|
"css",
|
|
@@ -14,8 +15,12 @@
|
|
|
14
15
|
"type": "git",
|
|
15
16
|
"url": "https://github.com/postcss/postcss-calc.git"
|
|
16
17
|
},
|
|
17
|
-
"
|
|
18
|
-
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./types/index.d.ts",
|
|
21
|
+
"default": "./src/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
19
24
|
"files": [
|
|
20
25
|
"src",
|
|
21
26
|
"types",
|
|
@@ -24,34 +29,34 @@
|
|
|
24
29
|
"author": "Andy Jansson",
|
|
25
30
|
"license": "MIT",
|
|
26
31
|
"engines": {
|
|
27
|
-
"node": "^22.
|
|
32
|
+
"node": "^22.18 || ^24.11 || >=26.0"
|
|
33
|
+
},
|
|
34
|
+
"devEngines": {
|
|
35
|
+
"packageManager": {
|
|
36
|
+
"name": "pnpm",
|
|
37
|
+
"version": "11.21.0"
|
|
38
|
+
}
|
|
28
39
|
},
|
|
29
40
|
"devDependencies": {
|
|
30
|
-
"@csstools/css-calc": "^3.
|
|
31
|
-
"@eslint/js": "^10.0.1",
|
|
41
|
+
"@csstools/css-calc": "^3.3.0",
|
|
32
42
|
"@rmenke/css-tokenizer-tests": "^1.2.0",
|
|
33
|
-
"@
|
|
34
|
-
"@stryker-mutator/tap-runner": "^9.6.1",
|
|
35
|
-
"@stryker-mutator/typescript-checker": "^9.6.1",
|
|
36
|
-
"@types/node": "^26.1.1",
|
|
37
|
-
"eslint": "^10.7.0",
|
|
38
|
-
"eslint-config-prettier": "^10.1.8",
|
|
39
|
-
"eslint-plugin-sonarjs": "^4.2.0",
|
|
43
|
+
"@types/node": "^26.2.0",
|
|
40
44
|
"fast-check": "^4.9.0",
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
45
|
+
"oxfmt": "^0.63.0",
|
|
46
|
+
"oxlint": "^1.78.0",
|
|
47
|
+
"postcss": "^8.5.26",
|
|
48
|
+
"typescript": "~7.0.2"
|
|
44
49
|
},
|
|
45
50
|
"dependencies": {
|
|
46
51
|
"@csstools/css-tokenizer": "^4.0.0",
|
|
47
52
|
"postcss-value-parser": "^4.2.0"
|
|
48
53
|
},
|
|
49
54
|
"peerDependencies": {
|
|
50
|
-
"postcss": "^8.5.
|
|
55
|
+
"postcss": "^8.5.25"
|
|
51
56
|
},
|
|
52
57
|
"scripts": {
|
|
53
|
-
"lint": "
|
|
54
|
-
"
|
|
55
|
-
"test
|
|
58
|
+
"lint": "oxlint . && tsc && oxfmt --check",
|
|
59
|
+
"fmt": "oxfmt",
|
|
60
|
+
"test": "node --test"
|
|
56
61
|
}
|
|
57
62
|
}
|
package/src/index.js
CHANGED
|
@@ -1,26 +1,37 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
// PostCSS adapter. Walks declaration values (and optionally @rule params
|
|
4
2
|
// and selectors), feeds calc() bodies through tokenize → parse → simplify
|
|
5
3
|
// → serialize, and writes the result back.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const { simplify } = require('./lib/simplify.js');
|
|
12
|
-
const { serialize } = require('./lib/serialize.js');
|
|
4
|
+
import valueParser from 'postcss-value-parser';
|
|
5
|
+
import { tokenize } from './lib/tokenizer.js';
|
|
6
|
+
import { parse } from './lib/parser.js';
|
|
7
|
+
import { simplify } from './lib/simplify.js';
|
|
8
|
+
import { serialize } from './lib/serialize.js';
|
|
13
9
|
|
|
14
10
|
const MATCH_CALC = /^(?:-(?:moz|webkit)-)?calc$/i;
|
|
15
11
|
|
|
16
12
|
// Bare math-function calls (no calc() wrapper) — fed to the same pipeline.
|
|
17
13
|
// Mirrors the dispatch in lib/simplify/call.js.
|
|
18
14
|
const MATH_FUNCTIONS = new Set([
|
|
19
|
-
'min',
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'
|
|
15
|
+
'min',
|
|
16
|
+
'max',
|
|
17
|
+
'clamp',
|
|
18
|
+
'abs',
|
|
19
|
+
'sign',
|
|
20
|
+
'mod',
|
|
21
|
+
'rem',
|
|
22
|
+
'round',
|
|
23
|
+
'sin',
|
|
24
|
+
'cos',
|
|
25
|
+
'tan',
|
|
26
|
+
'asin',
|
|
27
|
+
'acos',
|
|
28
|
+
'atan',
|
|
29
|
+
'atan2',
|
|
30
|
+
'pow',
|
|
31
|
+
'sqrt',
|
|
32
|
+
'hypot',
|
|
33
|
+
'log',
|
|
34
|
+
'exp',
|
|
24
35
|
]);
|
|
25
36
|
|
|
26
37
|
/**
|
|
@@ -45,10 +56,14 @@ const MATH_FUNCTIONS = new Set([
|
|
|
45
56
|
function transformValue(value, options, result, item) {
|
|
46
57
|
return valueParser(value)
|
|
47
58
|
.walk((node) => {
|
|
48
|
-
if (node.type !== 'function') {
|
|
59
|
+
if (node.type !== 'function') {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
49
62
|
const isCalc = MATCH_CALC.test(node.value);
|
|
50
63
|
const isMath = !isCalc && MATH_FUNCTIONS.has(node.value.toLowerCase());
|
|
51
|
-
if (!isCalc && !isMath) {
|
|
64
|
+
if (!isCalc && !isMath) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
52
67
|
|
|
53
68
|
// calc(): feed the body. Bare math: feed the whole call.
|
|
54
69
|
const inner = valueParser.stringify(node.nodes);
|
|
@@ -85,7 +100,6 @@ function transformValue(value, options, result, item) {
|
|
|
85
100
|
}
|
|
86
101
|
|
|
87
102
|
/**
|
|
88
|
-
* @type {import('postcss').PluginCreator<PluginOptions>}
|
|
89
103
|
* @param {PluginOptions} [opts]
|
|
90
104
|
* @return {import('postcss').Plugin}
|
|
91
105
|
*/
|
|
@@ -102,6 +116,10 @@ function pluginCreator(opts) {
|
|
|
102
116
|
|
|
103
117
|
return {
|
|
104
118
|
postcssPlugin: 'postcss-calc',
|
|
119
|
+
/**
|
|
120
|
+
* @param {import('postcss').Root} css
|
|
121
|
+
* @param {import('postcss').Helpers} helpers
|
|
122
|
+
*/
|
|
105
123
|
OnceExit(css, { result }) {
|
|
106
124
|
css.walk((node) => {
|
|
107
125
|
if (node.type === 'decl') {
|
|
@@ -143,4 +161,7 @@ function pluginCreator(opts) {
|
|
|
143
161
|
|
|
144
162
|
pluginCreator.postcss = true;
|
|
145
163
|
|
|
146
|
-
|
|
164
|
+
export default /** @type import('postcss').PluginCreator<PluginOptions>*/ (
|
|
165
|
+
pluginCreator
|
|
166
|
+
);
|
|
167
|
+
export { pluginCreator as 'module.exports' };
|
package/src/lib/convertUnits.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
// Spec: https://www.w3.org/TR/css-values-4/#calc-type-checking
|
|
4
2
|
|
|
5
3
|
/**
|
|
@@ -8,28 +6,67 @@
|
|
|
8
6
|
|
|
9
7
|
/** @type {Record<string, BaseType>} */
|
|
10
8
|
const UNIT_TO_BASE = {
|
|
11
|
-
px: 'length',
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
9
|
+
px: 'length',
|
|
10
|
+
cm: 'length',
|
|
11
|
+
mm: 'length',
|
|
12
|
+
q: 'length',
|
|
13
|
+
in: 'length',
|
|
14
|
+
pt: 'length',
|
|
15
|
+
pc: 'length',
|
|
16
|
+
em: 'length',
|
|
17
|
+
ex: 'length',
|
|
18
|
+
ch: 'length',
|
|
19
|
+
rem: 'length',
|
|
20
|
+
lh: 'length',
|
|
21
|
+
rlh: 'length',
|
|
22
|
+
ic: 'length',
|
|
23
|
+
cap: 'length',
|
|
24
|
+
vw: 'length',
|
|
25
|
+
vh: 'length',
|
|
26
|
+
vmin: 'length',
|
|
27
|
+
vmax: 'length',
|
|
28
|
+
vb: 'length',
|
|
29
|
+
vi: 'length',
|
|
30
|
+
svw: 'length',
|
|
31
|
+
svh: 'length',
|
|
32
|
+
svmin: 'length',
|
|
33
|
+
svmax: 'length',
|
|
34
|
+
svb: 'length',
|
|
35
|
+
svi: 'length',
|
|
36
|
+
lvw: 'length',
|
|
37
|
+
lvh: 'length',
|
|
38
|
+
lvmin: 'length',
|
|
39
|
+
lvmax: 'length',
|
|
40
|
+
lvb: 'length',
|
|
41
|
+
lvi: 'length',
|
|
42
|
+
dvw: 'length',
|
|
43
|
+
dvh: 'length',
|
|
44
|
+
dvmin: 'length',
|
|
45
|
+
dvmax: 'length',
|
|
46
|
+
dvb: 'length',
|
|
47
|
+
dvi: 'length',
|
|
48
|
+
cqw: 'length',
|
|
49
|
+
cqh: 'length',
|
|
50
|
+
cqi: 'length',
|
|
51
|
+
cqb: 'length',
|
|
52
|
+
cqmin: 'length',
|
|
53
|
+
cqmax: 'length',
|
|
25
54
|
|
|
26
|
-
deg: 'angle',
|
|
55
|
+
deg: 'angle',
|
|
56
|
+
grad: 'angle',
|
|
57
|
+
rad: 'angle',
|
|
58
|
+
turn: 'angle',
|
|
27
59
|
|
|
28
|
-
s: 'time',
|
|
60
|
+
s: 'time',
|
|
61
|
+
ms: 'time',
|
|
29
62
|
|
|
30
|
-
hz: 'frequency',
|
|
63
|
+
hz: 'frequency',
|
|
64
|
+
khz: 'frequency',
|
|
31
65
|
|
|
32
|
-
dpi: 'resolution',
|
|
66
|
+
dpi: 'resolution',
|
|
67
|
+
dpcm: 'resolution',
|
|
68
|
+
dppx: 'resolution',
|
|
69
|
+
x: 'resolution',
|
|
33
70
|
|
|
34
71
|
fr: 'flex',
|
|
35
72
|
|
|
@@ -102,4 +139,4 @@ function convert(value, from, to) {
|
|
|
102
139
|
return (value * f) / t;
|
|
103
140
|
}
|
|
104
141
|
|
|
105
|
-
|
|
142
|
+
export { baseOf, convert };
|
package/src/lib/node.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
// Canonical AST. N-ary Sum and Product with signed numeric leaves.
|
|
4
2
|
// Invariants enforced by the constructors below:
|
|
5
3
|
//
|
|
@@ -7,7 +5,9 @@
|
|
|
7
5
|
// is `Num(-5)`, never `Sum([{sign:-1, Num(5)}])`. One form per value.
|
|
8
6
|
// - In a SumTerm with Num/Dim node, sign is always +1; the sign slot is
|
|
9
7
|
// reserved for opaque nodes (Ident, Call, Product, multi-term Sum).
|
|
10
|
-
// - 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.
|
|
11
11
|
// - No Product directly contains another Product (flattened).
|
|
12
12
|
// - A Sum/Product with one positive element collapses to that element.
|
|
13
13
|
// - A Sum/Product with no elements collapses to Num(0) / Num(1).
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* @typedef {{type: 'Ident', name: string}} Ident
|
|
21
21
|
* @typedef {{type: 'Call', name: string, args: Node[]}} Call
|
|
22
22
|
* @typedef {{sign: 1 | -1, node: Node}} SumTerm Sign is always +1 when node is Num or Dim.
|
|
23
|
-
* @typedef {{type: 'Sum', terms: SumTerm[]}} Sum
|
|
23
|
+
* @typedef {{type: 'Sum', terms: SumTerm[], grouped?: boolean}} Sum
|
|
24
24
|
* @typedef {{exponent: 1 | -1, node: Node}} ProductFactor exponent +1 = numerator, -1 = denominator.
|
|
25
25
|
* @typedef {{type: 'Product', factors: ProductFactor[]}} Product
|
|
26
26
|
* @typedef {Num | Dim | Ident | Call | Sum | Product} Node
|
|
@@ -87,7 +87,7 @@ function mkSum(rawTerms) {
|
|
|
87
87
|
function pushSumTerm(out, term) {
|
|
88
88
|
let { sign, node } = term;
|
|
89
89
|
|
|
90
|
-
if (node.type === 'Sum') {
|
|
90
|
+
if (node.type === 'Sum' && !node.grouped) {
|
|
91
91
|
for (const inner of node.terms) {
|
|
92
92
|
pushSumTerm(out, {
|
|
93
93
|
sign: /** @type {1 | -1} */ (sign * inner.sign),
|
|
@@ -172,18 +172,19 @@ function negate(node) {
|
|
|
172
172
|
return dim(-node.value, node.unit);
|
|
173
173
|
}
|
|
174
174
|
if (node.type === 'Sum') {
|
|
175
|
-
|
|
175
|
+
const result = mkSum(
|
|
176
176
|
node.terms.map((t) => ({
|
|
177
177
|
sign: /** @type {1 | -1} */ (-t.sign),
|
|
178
178
|
node: t.node,
|
|
179
179
|
}))
|
|
180
180
|
);
|
|
181
|
+
return node.grouped && result.type === 'Sum'
|
|
182
|
+
? { ...result, grouped: true }
|
|
183
|
+
: result;
|
|
181
184
|
}
|
|
182
185
|
// Opaque (Ident, Call, Product): wrap as a single negative-sign term —
|
|
183
186
|
// the only case where sign=-1 remains on a SumTerm.
|
|
184
187
|
return { type: 'Sum', terms: [{ sign: -1, node }] };
|
|
185
188
|
}
|
|
186
189
|
|
|
187
|
-
|
|
188
|
-
// cjs-module-lexer named-export detection for .mjs `import { x } from` consumers.
|
|
189
|
-
module.exports = { num, dim, ident, call, mkSum, mkProduct, negate };
|
|
190
|
+
export { num, dim, ident, call, mkSum, mkProduct, negate };
|
package/src/lib/parser.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
// Pratt parser. +/- emit Sum nodes; */÷ emit Product nodes. node.js
|
|
4
|
-
// constructors flatten and normalize on construction,
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
const { mkSum, mkProduct, negate } = require('./node.js');
|
|
2
|
+
// constructors flatten and normalize on construction, while parenthesized
|
|
3
|
+
// sums retain a grouping marker for the opaque-subtraction invariant.
|
|
4
|
+
import { mkSum, mkProduct, negate } from './node.js';
|
|
8
5
|
|
|
9
6
|
/**
|
|
10
7
|
* @typedef {import('./tokenizer.js').Token} Token
|
|
@@ -157,7 +154,9 @@ const OPAQUE_ARG_FUNCTIONS = new Set(['anchor', 'anchor-size']);
|
|
|
157
154
|
* @return {string}
|
|
158
155
|
*/
|
|
159
156
|
function tokenText(t) {
|
|
160
|
-
if (t.type === 'dimension') {
|
|
157
|
+
if (t.type === 'dimension') {
|
|
158
|
+
return `${t.value}${t.unit ?? ''}`;
|
|
159
|
+
}
|
|
161
160
|
return t.value;
|
|
162
161
|
}
|
|
163
162
|
|
|
@@ -174,7 +173,9 @@ function parseOpaqueCall(p, name) {
|
|
|
174
173
|
let depth = 1;
|
|
175
174
|
const flush = () => {
|
|
176
175
|
const trimmed = buf.trim();
|
|
177
|
-
if (trimmed) {
|
|
176
|
+
if (trimmed) {
|
|
177
|
+
args.push({ type: 'Ident', name: trimmed });
|
|
178
|
+
}
|
|
178
179
|
buf = '';
|
|
179
180
|
};
|
|
180
181
|
while (true) {
|
|
@@ -183,8 +184,9 @@ function parseOpaqueCall(p, name) {
|
|
|
183
184
|
throw new Error(`Unclosed ${name}( at position ${tk.pos}`);
|
|
184
185
|
}
|
|
185
186
|
if (tk.type === 'punct') {
|
|
186
|
-
if (tk.value === '(') {
|
|
187
|
-
|
|
187
|
+
if (tk.value === '(') {
|
|
188
|
+
depth++;
|
|
189
|
+
} else if (tk.value === ')') {
|
|
188
190
|
depth--;
|
|
189
191
|
if (depth === 0) {
|
|
190
192
|
p.next();
|
|
@@ -197,7 +199,9 @@ function parseOpaqueCall(p, name) {
|
|
|
197
199
|
continue;
|
|
198
200
|
}
|
|
199
201
|
}
|
|
200
|
-
if (tk.ws && buf) {
|
|
202
|
+
if (tk.ws && buf) {
|
|
203
|
+
buf += ' ';
|
|
204
|
+
}
|
|
201
205
|
buf += tokenText(tk);
|
|
202
206
|
p.next();
|
|
203
207
|
}
|
|
@@ -262,7 +266,7 @@ const PREFIX = {
|
|
|
262
266
|
'(': (p) => {
|
|
263
267
|
const e = p.parseExpr(0);
|
|
264
268
|
p.expect('punct', ')');
|
|
265
|
-
return e;
|
|
269
|
+
return e.type === 'Sum' ? { ...e, grouped: true } : e;
|
|
266
270
|
},
|
|
267
271
|
|
|
268
272
|
'-': (p) => negate(p.parseExpr(UNARY_BP)),
|
|
@@ -311,4 +315,4 @@ function parse(tokens) {
|
|
|
311
315
|
return ast;
|
|
312
316
|
}
|
|
313
317
|
|
|
314
|
-
|
|
318
|
+
export { parse };
|
package/src/lib/serialize.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
// Spec: https://www.w3.org/TR/css-values-4/#serialize-a-calculation-tree
|
|
4
2
|
// Outer calc() is added only when the top-level result contains an
|
|
5
3
|
// arithmetic operator. A Sum inside a Product is the only place parens
|
|
@@ -55,7 +53,9 @@ function isDegenerate(v) {
|
|
|
55
53
|
* @return {string}
|
|
56
54
|
*/
|
|
57
55
|
function degenerateKeyword(v) {
|
|
58
|
-
if (isNaN(v)) {
|
|
56
|
+
if (isNaN(v)) {
|
|
57
|
+
return 'NaN';
|
|
58
|
+
}
|
|
59
59
|
return v > 0 ? 'infinity' : '-infinity';
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -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' ||
|
|
@@ -105,7 +124,9 @@ function serialize(node, opts = {}) {
|
|
|
105
124
|
function serializeExpr(node, prec) {
|
|
106
125
|
switch (node.type) {
|
|
107
126
|
case 'Num':
|
|
108
|
-
if (isDegenerate(node.value)) {
|
|
127
|
+
if (isDegenerate(node.value)) {
|
|
128
|
+
return degenerateKeyword(node.value);
|
|
129
|
+
}
|
|
109
130
|
return String(round(node.value, prec));
|
|
110
131
|
case 'Dim':
|
|
111
132
|
if (isDegenerate(node.value)) {
|
|
@@ -161,18 +182,27 @@ function displaySign(term) {
|
|
|
161
182
|
*/
|
|
162
183
|
function serializeSum(sum, prec) {
|
|
163
184
|
let out = '';
|
|
164
|
-
sum.terms.
|
|
185
|
+
for (const [i, t] of sum.terms.entries()) {
|
|
165
186
|
const { sign, magnitude } = displaySign(t);
|
|
166
187
|
if (i === 0) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
:
|
|
188
|
+
if (magnitude.type === 'Sum' && magnitude.grouped) {
|
|
189
|
+
const body = `(${serializeExpr(magnitude, prec)})`;
|
|
190
|
+
out = sign === 1 ? body : `-${body}`;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
out =
|
|
194
|
+
sign === 1
|
|
195
|
+
? serializeExpr(magnitude, prec)
|
|
196
|
+
: serializeLeadingNeg(magnitude, prec);
|
|
170
197
|
} else {
|
|
171
198
|
// `-` binds looser than `*`/`/` so the right side never needs parens.
|
|
172
|
-
|
|
199
|
+
let body = serializeExpr(magnitude, prec);
|
|
200
|
+
if (magnitude.type === 'Sum' && magnitude.grouped) {
|
|
201
|
+
body = `(${body})`;
|
|
202
|
+
}
|
|
173
203
|
out += sign === 1 ? ` + ${body}` : ` - ${body}`;
|
|
174
204
|
}
|
|
175
|
-
}
|
|
205
|
+
}
|
|
176
206
|
return out;
|
|
177
207
|
}
|
|
178
208
|
|
|
@@ -207,7 +237,9 @@ function serializeLeadingNeg(node, prec) {
|
|
|
207
237
|
return serializeProduct({ type: 'Product', factors: negatedFactors }, prec);
|
|
208
238
|
}
|
|
209
239
|
const body = serializeExpr(node, prec);
|
|
210
|
-
return node.type === 'Sum' || node.type === 'Product'
|
|
240
|
+
return node.type === 'Sum' || node.type === 'Product'
|
|
241
|
+
? `-(${body})`
|
|
242
|
+
: `-${body}`;
|
|
211
243
|
}
|
|
212
244
|
|
|
213
245
|
/**
|
|
@@ -217,7 +249,7 @@ function serializeLeadingNeg(node, prec) {
|
|
|
217
249
|
*/
|
|
218
250
|
function serializeProduct(product, prec) {
|
|
219
251
|
let out = '';
|
|
220
|
-
product.factors.
|
|
252
|
+
for (const [i, f] of product.factors.entries()) {
|
|
221
253
|
let body = serializeExpr(f.node, prec);
|
|
222
254
|
// A Sum factor needs parens: `a * (b + c)`. Flat canonical form means
|
|
223
255
|
// this is the only place parens are required.
|
|
@@ -230,10 +262,8 @@ function serializeProduct(product, prec) {
|
|
|
230
262
|
} else {
|
|
231
263
|
out += f.exponent === 1 ? ` * ${body}` : ` / ${body}`;
|
|
232
264
|
}
|
|
233
|
-
}
|
|
265
|
+
}
|
|
234
266
|
return out;
|
|
235
267
|
}
|
|
236
268
|
|
|
237
|
-
|
|
238
|
-
// cjs-module-lexer named-export detection for .mjs `import { x } from` consumers.
|
|
239
|
-
module.exports = { serialize };
|
|
269
|
+
export { serialize };
|
package/src/lib/simplify/abs.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const { num, dim } = require('../node.js');
|
|
1
|
+
import { num, dim } from '../node.js';
|
|
4
2
|
|
|
5
3
|
/** @typedef {import('../node.js').Node} Node */
|
|
6
4
|
|
|
@@ -13,9 +11,13 @@ function simplifyAbs(args) {
|
|
|
13
11
|
return { type: 'Call', name: 'abs', args };
|
|
14
12
|
}
|
|
15
13
|
const a = args[0];
|
|
16
|
-
if (a.type === 'Num') {
|
|
17
|
-
|
|
14
|
+
if (a.type === 'Num') {
|
|
15
|
+
return num(Math.abs(a.value));
|
|
16
|
+
}
|
|
17
|
+
if (a.type === 'Dim' && a.unit !== '%') {
|
|
18
|
+
return dim(Math.abs(a.value), a.unit);
|
|
19
|
+
}
|
|
18
20
|
return { type: 'Call', name: 'abs', args: [a] };
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
export { simplifyAbs };
|