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.
@@ -1,9 +1,9 @@
1
1
  // Spec: https://www.w3.org/TR/css-values-4/#serialize-a-calculation-tree
2
- // Outer calc() is added only when the top-level result contains an
3
- // arithmetic operator. A Sum inside a Product is the only place parens
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';
6
+ import { getComponents, serializeComponents } from './opaque.js';
7
7
 
8
8
  /**
9
9
  * @typedef {import('./node.js').Node} Node
@@ -13,6 +13,7 @@ import { num, dim } from './node.js';
13
13
  * @typedef {object} SerializeOptions
14
14
  * @property {number | false} [precision] Decimal places for numbers. `false` disables rounding. Default 5.
15
15
  * @property {string} [calcName] Wrapper name to use when `calc()` is needed. Default `'calc'`.
16
+ * @property {boolean} [unwrapSingleNegativeNumber] Serialize finite negative scalars without a wrapper. Internal selector-only mode.
16
17
  */
17
18
 
18
19
  // Below this is float noise, not a value: `0.1 + 0.2 - 0.3` is 5.5e-17.
@@ -80,6 +81,20 @@ function serializeNumber(v) {
80
81
  return text;
81
82
  }
82
83
 
84
+ /**
85
+ * Round and serialize a finite scalar once so callers can use the same value
86
+ * to decide its syntactic context and render its text.
87
+ *
88
+ * @param {import('./node.js').Num | import('./node.js').Dim} node
89
+ * @param {number | false} prec
90
+ * @return {{value: number, text: string}}
91
+ */
92
+ function serializeScalar(node, prec) {
93
+ const value = round(node.value, prec);
94
+ const text = `${serializeNumber(value)}${node.type === 'Dim' ? (node.rawUnit ?? node.unit) : ''}`;
95
+ return { value, text };
96
+ }
97
+
83
98
  /**
84
99
  * @param {Node} node
85
100
  * @param {SerializeOptions} [opts]
@@ -95,7 +110,23 @@ function serialize(node, opts = {}) {
95
110
  return `${calcName}(${degenerateKeyword(node.value)})`;
96
111
  }
97
112
  if (node.type === 'Dim' && isDegenerate(node.value)) {
98
- return `${calcName}(${degenerateKeyword(node.value)} * 1${node.unit})`;
113
+ return `${calcName}(${degenerateKeyword(node.value)} * 1${node.rawUnit ?? node.unit})`;
114
+ }
115
+
116
+ if (node.type === 'Num' || node.type === 'Dim') {
117
+ const scalar = serializeScalar(node, prec);
118
+
119
+ // A finite negative scalar must stay inside calc() so CSS parses it as a
120
+ // calculation result (and can apply range clamping) rather than as an
121
+ // invalid bare value. Base this on the serialized value so tiny negative
122
+ // floating-point noise that rounds to zero does not get wrapped.
123
+ if (scalar.value < 0) {
124
+ return opts.unwrapSingleNegativeNumber
125
+ ? scalar.text
126
+ : `${calcName}(${scalar.text})`;
127
+ }
128
+
129
+ return scalar.text;
99
130
  }
100
131
 
101
132
  // A grouped sum with a leading negative term is the canonical result of
@@ -114,12 +145,7 @@ function serialize(node, opts = {}) {
114
145
  return `${calcName}(-(${serializeSumTerms(invertedTerms, prec)}))`;
115
146
  }
116
147
 
117
- if (
118
- node.type === 'Num' ||
119
- node.type === 'Dim' ||
120
- node.type === 'Ident' ||
121
- node.type === 'Call'
122
- ) {
148
+ if (node.type === 'Ident' || node.type === 'Call') {
123
149
  return serializeExpr(node, prec);
124
150
  }
125
151
 
@@ -145,20 +171,27 @@ function serializeExpr(node, prec) {
145
171
  if (isDegenerate(node.value)) {
146
172
  return degenerateKeyword(node.value);
147
173
  }
148
- return serializeNumber(round(node.value, prec));
174
+ return serializeScalar(node, prec).text;
149
175
  case 'Dim':
150
176
  if (isDegenerate(node.value)) {
151
177
  // Nested degenerate Dim wraps in calc() so the `<kw> * 1<unit>` form
152
178
  // parses back as one Dim factor. The bare form round-trips wrong
153
179
  // inside a Product — `0 * Dim(Infinity, px)` would re-fold as NaN.
154
- return `calc(${degenerateKeyword(node.value)} * 1${node.unit})`;
180
+ return `calc(${degenerateKeyword(node.value)} * 1${node.rawUnit ?? node.unit})`;
155
181
  }
156
- return `${serializeNumber(round(node.value, prec))}${node.unit}`;
182
+ return serializeScalar(node, prec).text;
157
183
  case 'Ident':
158
- return node.name;
184
+ return node.rawName ?? node.name;
159
185
  case 'Call': {
186
+ const components = getComponents(node);
187
+ if (components) {
188
+ const args = node.args
189
+ .map((arg) => serializeExpr(arg, prec))
190
+ .join(', ');
191
+ return `${node.rawName ?? node.name}(${args}${serializeComponents(components, (child) => serialize(child, { precision: prec }))})`;
192
+ }
160
193
  const args = node.args.map((a) => serializeExpr(a, prec)).join(', ');
161
- return `${node.name}(${args})`;
194
+ return `${node.rawName ?? node.name}(${args})`;
162
195
  }
163
196
  case 'Sum':
164
197
  return serializeSum(node, prec);
@@ -187,7 +220,7 @@ function displaySign(term) {
187
220
  if (node.type === 'Dim' && Number.isFinite(node.value) && node.value < 0) {
188
221
  return {
189
222
  sign: /** @type {1 | -1} */ (-sign),
190
- magnitude: dim(-node.value, node.unit),
223
+ magnitude: dim(-node.value, node.unit, node.rawUnit),
191
224
  };
192
225
  }
193
226
  return { sign, magnitude: node };
@@ -15,7 +15,7 @@ function simplifyAbs(args) {
15
15
  return num(Math.abs(a.value));
16
16
  }
17
17
  if (a.type === 'Dim' && a.unit !== '%') {
18
- return dim(Math.abs(a.value), a.unit);
18
+ return dim(Math.abs(a.value), a.unit, a.rawUnit);
19
19
  }
20
20
  return call('abs', [a]);
21
21
  }
@@ -9,10 +9,10 @@ import { convert } from '../convertUnits.js';
9
9
  /**
10
10
  * @typedef {object} UnitBucket
11
11
  * @property {string} unit
12
+ * @property {string} [rawUnit]
12
13
  * @property {number} total
13
14
  * @property {number} scale largest |term| accumulated into `total`, for noise detection
14
15
  * @property {import('../convertUnits.js').BaseType | null} base
15
- * @property {number} order
16
16
  */
17
17
 
18
18
  /** Mutates `buckets` in place — totals of survivor buckets accumulate the
@@ -21,32 +21,27 @@ import { convert } from '../convertUnits.js';
21
21
  * @return {UnitBucket[]}
22
22
  */
23
23
  function mergeConvertibleBuckets(buckets) {
24
- const ordered = buckets.sort((a, b) => a.order - b.order);
25
- /** @type {Set<string>} */ const merged = new Set();
24
+ /** @type {Map<import('../convertUnits.js').BaseType, UnitBucket>} */ const representative =
25
+ new Map();
26
26
  /** @type {UnitBucket[]} */ const out = [];
27
- for (const b of ordered) {
28
- const keyB = b.unit.toLowerCase();
29
- if (merged.has(keyB)) {
27
+ for (const b of buckets) {
28
+ if (b.base === null) {
29
+ out.push(b);
30
30
  continue;
31
31
  }
32
- merged.add(keyB);
33
- if (b.base !== null) {
34
- for (const other of ordered) {
35
- const keyO = other.unit.toLowerCase();
36
- if (merged.has(keyO)) {
37
- continue;
38
- }
39
- if (other.base !== b.base) {
40
- continue;
41
- }
42
- const converted = convert(other.total, other.unit, b.unit);
43
- if (converted !== null) {
44
- b.total += converted;
45
- merged.add(keyO);
46
- }
47
- }
32
+ const first = representative.get(b.base);
33
+ if (!first) {
34
+ representative.set(b.base, b);
35
+ out.push(b);
36
+ continue;
37
+ }
38
+ const converted = convert(b.total, b.unit, first.unit);
39
+ if (converted === null) {
40
+ out.push(b);
41
+ continue;
48
42
  }
49
- out.push(b);
43
+ first.total += converted;
44
+ first.scale = Math.max(first.scale, Math.abs(converted));
50
45
  }
51
46
  return out;
52
47
  }
@@ -17,6 +17,7 @@ import { simplifyLog } from './log.js';
17
17
  import { simplifyHypot } from './hypot.js';
18
18
 
19
19
  import { call } from '../node.js';
20
+ import { getComponents, setComponents, simplifyComponents } from '../opaque.js';
20
21
 
21
22
  /** @typedef {import('../node.js').Node} Node */
22
23
  /** @typedef {import('../simplify.js').SimplifyFn} SimplifyFn */
@@ -101,6 +102,12 @@ function simplifyCall(node, simplify) {
101
102
 
102
103
  const args = node.args.map((a) => simplify(a));
103
104
 
105
+ const components = getComponents(node);
106
+ if (components) {
107
+ const result = call(node.name, args, node.rawName);
108
+ return setComponents(result, simplifyComponents(components, simplify));
109
+ }
110
+
104
111
  const simplifier = MATH_SIMPLIFIERS.get(name);
105
112
  if (simplifier) {
106
113
  // min/max preserve the call's original casing in their opaque-args
@@ -111,7 +118,7 @@ function simplifyCall(node, simplify) {
111
118
  );
112
119
  }
113
120
 
114
- return call(node.name, args);
121
+ return call(node.name, args, node.rawName);
115
122
  }
116
123
 
117
124
  export {
@@ -15,7 +15,7 @@ import { tryCancelPair } from './cancel.js';
15
15
  */
16
16
  function simplifyProduct(product, simplify) {
17
17
  let coeff = 1;
18
- /** @type {{exponent: 1 | -1, value: number, unit: string}[]} */
18
+ /** @type {{exponent: 1 | -1, value: number, unit: string, rawUnit?: string}[]} */
19
19
  const dims = [];
20
20
  /** @type {ProductFactor[]} */
21
21
  const opaque = [];
@@ -54,7 +54,7 @@ function simplifyProduct(product, simplify) {
54
54
  return;
55
55
  }
56
56
  if (n.type === 'Dim') {
57
- dims.push({ exponent, value: n.value, unit: n.unit });
57
+ dims.push({ exponent, value: n.value, unit: n.unit, rawUnit: n.rawUnit });
58
58
  scalarChain.push({ exponent, value: n.value });
59
59
  return;
60
60
  }
@@ -112,7 +112,7 @@ function simplifyProduct(product, simplify) {
112
112
  value = value / f.value;
113
113
  }
114
114
  }
115
- return dim(value, d.unit);
115
+ return dim(value, d.unit, d.rawUnit);
116
116
  }
117
117
 
118
118
  if (remainingDims.length === 0 && opaque.length === 0) {
@@ -125,7 +125,10 @@ function simplifyProduct(product, simplify) {
125
125
  factors.push({ exponent: 1, node: num(coeff) });
126
126
  }
127
127
  for (const d of remainingDims) {
128
- factors.push({ exponent: d.exponent, node: dim(d.value, d.unit) });
128
+ factors.push({
129
+ exponent: d.exponent,
130
+ node: dim(d.value, d.unit, d.rawUnit),
131
+ });
129
132
  }
130
133
  factors.push(...opaque);
131
134
 
@@ -1,5 +1,5 @@
1
1
  import { mkSum, num, dim } from '../node.js';
2
- import { baseOf } from '../convertUnits.js';
2
+ import { staticBaseOf } from '../convertUnits.js';
3
3
  import { mergeConvertibleBuckets } from './bucket.js';
4
4
 
5
5
  /**
@@ -39,7 +39,6 @@ function simplifySum(sum, simplify) {
39
39
  const byUnit = new Map();
40
40
  /** @type {SumTerm[]} */
41
41
  const opaque = [];
42
- let bucketOrder = 0;
43
42
 
44
43
  /**
45
44
  * @param {1 | -1} sign
@@ -78,10 +77,10 @@ function simplifySum(sum, simplify) {
78
77
  } else {
79
78
  byUnit.set(key, {
80
79
  unit: n.unit,
80
+ rawUnit: n.rawUnit,
81
81
  total: sign * n.value,
82
82
  scale: Math.abs(n.value),
83
- base: baseOf(n.unit),
84
- order: bucketOrder++,
83
+ base: staticBaseOf(n.unit),
85
84
  });
86
85
  }
87
86
  return;
@@ -101,7 +100,11 @@ function simplifySum(sum, simplify) {
101
100
  for (const bucket of mergeConvertibleBuckets([...byUnit.values()])) {
102
101
  terms.push({
103
102
  sign: 1,
104
- node: dim(denoise(bucket.total, bucket.scale), bucket.unit),
103
+ node: dim(
104
+ denoise(bucket.total, bucket.scale),
105
+ bucket.unit,
106
+ bucket.rawUnit
107
+ ),
105
108
  });
106
109
  }
107
110
  terms.push(...opaque);
@@ -1,139 +1,10 @@
1
- // Folds @csstools/css-tokenizer output into the calc() token subset.
1
+ // Thin project-local entry point for the CSS Syntax tokenizer. The parser
2
+ // consumes native tokens directly so decoded values and source spelling remain available.
3
+ import { tokenize as tokenizeCss } from '@csstools/css-tokenizer';
2
4
 
3
- import {
4
- tokenize as tokenizeCss,
5
- TokenType as CssType,
6
- } from '@csstools/css-tokenizer';
7
- /**
8
- * @typedef {'number' | 'dimension' | 'ident' | 'punct' | 'eof'} TokenType
9
- * @typedef {object} Token
10
- * @property {TokenType} type
11
- * @property {string} value
12
- * @property {string} [unit] Present on `dimension` tokens; `%` for percentages.
13
- * @property {number} pos
14
- * @property {boolean} ws Whitespace immediately before — drives the §10.1 `+`/`-` rule.
15
- */
16
-
17
- const PUNCT_DELIMS = new Set(['+', '-', '*', '/']);
18
-
19
- const NUMERIC_RAW = /^[+-]?(?:\d+(?:\.\d+)?|\.\d+)(?:[eE][+-]?\d+)?/;
20
-
21
- /**
22
- * @param {string} input
23
- * @return {Token[]}
24
- */
5
+ /** @param {string} input @return {import('@csstools/css-tokenizer').CSSToken[]} */
25
6
  function tokenize(input) {
26
- return tokenizeTokens(tokenizeCss({ css: input }), input.length);
27
- }
28
-
29
- /**
30
- * CSS absorbs leading signs (`-5px` is one token); the parser expects
31
- * punct sign + unsigned numeric, so split them back out.
32
- * @param {Token[]} tokens
33
- * @param {string} raw
34
- * @param {string | undefined} unit
35
- * @param {number} pos
36
- * @param {boolean} ws
37
- * @return {void}
38
- */
39
- function pushNumeric(tokens, raw, unit, pos, ws) {
40
- let value = /** @type {RegExpExecArray} */ (NUMERIC_RAW.exec(raw))[0];
41
- const sign = value[0];
42
- if (sign === '+' || sign === '-') {
43
- tokens.push({ type: 'punct', value: sign, pos, ws });
44
- value = value.slice(1);
45
- pos += 1;
46
- ws = false;
47
- }
48
- if (unit === undefined) {
49
- tokens.push({ type: 'number', value, pos, ws });
50
- } else {
51
- tokens.push({ type: 'dimension', value, unit, pos, ws });
52
- }
53
- }
54
-
55
- /**
56
- * Convert a slice of an existing CSS token stream into the token subset used
57
- * by the calculation parser. Token positions remain relative to the original
58
- * source text, which keeps parse errors useful to adapter callers.
59
- *
60
- * @param {import('@csstools/css-tokenizer').CSSToken[]} cssTokens
61
- * @param {number} eofPosition
62
- * @param {number} [start]
63
- * @param {number} [end]
64
- * @return {Token[]}
65
- */
66
- function tokenizeTokens(
67
- cssTokens,
68
- eofPosition,
69
- start = 0,
70
- end = cssTokens.length
71
- ) {
72
- /** @type {Token[]} */
73
- const tokens = [];
74
- let ws = true;
75
-
76
- for (let i = start; i < end; i++) {
77
- const t = cssTokens[i];
78
- switch (t[0]) {
79
- case CssType.Whitespace:
80
- case CssType.Comment:
81
- ws = true;
82
- continue;
83
- case CssType.Number:
84
- pushNumeric(tokens, t[1], undefined, t[2], ws);
85
- ws = false;
86
- continue;
87
- case CssType.Dimension:
88
- pushNumeric(tokens, t[1], t[4].unit, t[2], ws);
89
- ws = false;
90
- continue;
91
- case CssType.Percentage:
92
- pushNumeric(tokens, t[1], '%', t[2], ws);
93
- ws = false;
94
- continue;
95
- case CssType.Ident:
96
- tokens.push({ type: 'ident', value: t[4].value, pos: t[2], ws });
97
- break;
98
- case CssType.Function:
99
- tokens.push({ type: 'ident', value: t[4].value, pos: t[2], ws });
100
- tokens.push({
101
- type: 'punct',
102
- value: '(',
103
- pos: t[2] + t[1].length - 1,
104
- ws: false,
105
- });
106
- break;
107
- case CssType.OpenParen:
108
- tokens.push({ type: 'punct', value: '(', pos: t[2], ws });
109
- break;
110
- case CssType.CloseParen:
111
- tokens.push({ type: 'punct', value: ')', pos: t[2], ws });
112
- break;
113
- case CssType.Comma:
114
- tokens.push({ type: 'punct', value: ',', pos: t[2], ws });
115
- break;
116
- case CssType.Delim:
117
- if (!PUNCT_DELIMS.has(t[4].value)) {
118
- throw new Error(
119
- `Unexpected character "${t[4].value}" at position ${t[2]}`
120
- );
121
- }
122
- tokens.push({ type: 'punct', value: t[4].value, pos: t[2], ws });
123
- break;
124
- case CssType.EOF:
125
- continue;
126
- default:
127
- throw new Error(
128
- `Unexpected character "${t[1][0] ?? ''}" at position ${t[2]}`
129
- );
130
- }
131
- ws = false;
132
- }
133
-
134
- tokens.push({ type: 'eof', value: '', pos: eofPosition, ws });
135
-
136
- return tokens;
7
+ return tokenizeCss({ css: input });
137
8
  }
138
9
 
139
- export { tokenize, tokenizeTokens };
10
+ export { tokenize };
package/src/reduce.js CHANGED
@@ -5,7 +5,6 @@ import {
5
5
  tokenize as cssTokenize,
6
6
  TokenType as CssType,
7
7
  } from '@csstools/css-tokenizer';
8
- import { tokenizeTokens } from './lib/tokenizer.js';
9
8
  import { parse } from './lib/parser.js';
10
9
  import { simplify } from './lib/simplify.js';
11
10
  import {
@@ -27,6 +26,7 @@ const BLOCK_CLOSE = new Map([
27
26
  * @typedef {object} ReduceCalcOptions
28
27
  * @property {number | false} [precision]
29
28
  * @property {boolean} [warnWhenCannotResolve]
29
+ * @property {boolean} [unwrapSingleNegativeNumber] Serialize finite negative results without a `calc()` wrapper. Defaults to `false`.
30
30
  * @property {(error: Error, input: string) => void} [onParseError] Invoked when parse/simplify throws.
31
31
  * @property {(message: string) => void} [onWarn] Invoked when `warnWhenCannotResolve` is set and an expression cannot be reduced to a single value.
32
32
  */
@@ -34,9 +34,7 @@ const BLOCK_CLOSE = new Map([
34
34
  /** @typedef {Required<Omit<ReduceCalcOptions, 'onParseError' | 'onWarn'>> & Pick<ReduceCalcOptions, 'onParseError' | 'onWarn'>} ResolvedReduceCalcOptions */
35
35
 
36
36
  /**
37
- * Fields threaded unchanged through the token-range walk.
38
- * `value` is the original full property text, used only for the
39
- * warnWhenCannotResolve message.
37
+ * Fields threaded through the internal token-range walk.
40
38
  *
41
39
  * @typedef {object} TransformContext
42
40
  * @property {ResolvedReduceCalcOptions} options
@@ -51,7 +49,6 @@ const BLOCK_CLOSE = new Map([
51
49
  * @property {number} end
52
50
  * @property {import('./lib/node.js').Node} node
53
51
  * @property {string} calcName
54
- * @property {string} matchedName
55
52
  */
56
53
 
57
54
  /**
@@ -97,15 +94,12 @@ function walkTokens(start, expectedClose, ctx, transform) {
97
94
  const inputEnd = closed && isCalc ? ctx.tokens[close][2] : end;
98
95
  const contents = ctx.value.slice(inputStart, inputEnd);
99
96
  try {
100
- const node = simplify(
101
- parse(tokenizeTokens(ctx.tokens, end, sliceStart, sliceEnd))
102
- );
97
+ const node = simplify(parse(ctx.tokens, sliceStart, sliceEnd));
103
98
  ctx.replacements.push({
104
99
  start: token[2],
105
100
  end,
106
101
  node,
107
102
  calcName: isCalc ? name : 'calc',
108
- matchedName: name,
109
103
  });
110
104
  } catch (error) {
111
105
  const err = error instanceof Error ? error : new Error('Error');
@@ -117,6 +111,17 @@ function walkTokens(start, expectedClose, ctx, transform) {
117
111
  return ctx.tokens.length - 1;
118
112
  }
119
113
 
114
+ /**
115
+ * @param {import('./lib/node.js').Node} node
116
+ * @return {boolean}
117
+ */
118
+ function isUnresolvedResult(node) {
119
+ if (node.type === 'Sum' || node.type === 'Product') {
120
+ return true;
121
+ }
122
+ return node.type === 'Call' && isSupportedMathFunction(node.name);
123
+ }
124
+
120
125
  /**
121
126
  * Simplify every supported CSS math function in a component-value string.
122
127
  * Text outside those functions is preserved byte-for-byte.
@@ -131,7 +136,12 @@ function reduceCalc(value, opts) {
131
136
  }
132
137
 
133
138
  /** @type {ResolvedReduceCalcOptions} */
134
- const options = { precision: 5, warnWhenCannotResolve: false, ...opts };
139
+ const options = {
140
+ precision: 5,
141
+ warnWhenCannotResolve: false,
142
+ unwrapSingleNegativeNumber: false,
143
+ ...opts,
144
+ };
135
145
  const tokens = cssTokenize({ css: value });
136
146
  /** @type {Replacement[]} */
137
147
  const replacements = [];
@@ -147,11 +157,9 @@ function reduceCalc(value, opts) {
147
157
  const text = serialize(replacement.node, {
148
158
  precision: options.precision,
149
159
  calcName: replacement.calcName,
160
+ unwrapSingleNegativeNumber: options.unwrapSingleNegativeNumber,
150
161
  });
151
- if (
152
- options.warnWhenCannotResolve &&
153
- text.startsWith(`${replacement.matchedName}(`)
154
- ) {
162
+ if (options.warnWhenCannotResolve && isUnresolvedResult(replacement.node)) {
155
163
  options.onWarn?.('Could not reduce expression: ' + value);
156
164
  }
157
165
  output += value.slice(lastIndex, replacement.start) + text;
@@ -14,4 +14,11 @@ declare function baseOf(unit: string): BaseType | null;
14
14
  * @return {number | null}
15
15
  */
16
16
  declare function convert(value: number, from: string, to: string): number | null;
17
- export { baseOf, convert };
17
+ /**
18
+ * Return a base type only for units present in the static conversion table.
19
+ * Units with context-dependent values (em, rem, vw, etc.) return null.
20
+ * @param {string} unit
21
+ * @return {BaseType | null}
22
+ */
23
+ declare function staticBaseOf(unit: string): BaseType | null;
24
+ export { baseOf, staticBaseOf, convert };
@@ -6,15 +6,18 @@ export type Dim = {
6
6
  type: 'Dim';
7
7
  value: number;
8
8
  unit: string;
9
+ rawUnit?: string;
9
10
  };
10
11
  export type Ident = {
11
12
  type: 'Ident';
12
13
  name: string;
14
+ rawName?: string;
13
15
  };
14
16
  export type Call = {
15
17
  type: 'Call';
16
18
  name: string;
17
19
  args: Node[];
20
+ rawName?: string;
18
21
  };
19
22
  export type SumTerm = {
20
23
  sign: 1 | -1;
@@ -36,9 +39,9 @@ export type Product = {
36
39
  export type Node = Num | Dim | Ident | Call | Sum | Product;
37
40
  /**
38
41
  * @typedef {{type: 'Num', value: number}} Num
39
- * @typedef {{type: 'Dim', value: number, unit: string}} Dim
40
- * @typedef {{type: 'Ident', name: string}} Ident
41
- * @typedef {{type: 'Call', name: string, args: Node[]}} Call
42
+ * @typedef {{type: 'Dim', value: number, unit: string, rawUnit?: string}} Dim
43
+ * @typedef {{type: 'Ident', name: string, rawName?: string}} Ident
44
+ * @typedef {{type: 'Call', name: string, args: Node[], rawName?: string}} Call
42
45
  * @typedef {{sign: 1 | -1, node: Node}} SumTerm Sign is always +1 when node is Num or Dim.
43
46
  * @typedef {{type: 'Sum', terms: SumTerm[], grouped?: boolean}} Sum
44
47
  * @typedef {{exponent: 1 | -1, node: Node}} ProductFactor exponent +1 = numerator, -1 = denominator.
@@ -53,20 +56,23 @@ declare function num(value: number): Num;
53
56
  /**
54
57
  * @param {number} value
55
58
  * @param {string} unit
59
+ * @param {string} [rawUnit]
56
60
  * @return {Dim}
57
61
  */
58
- declare function dim(value: number, unit: string): Dim;
62
+ declare function dim(value: number, unit: string, rawUnit?: string): Dim;
59
63
  /**
60
64
  * @param {string} name
65
+ * @param {string} [rawName]
61
66
  * @return {Ident}
62
67
  */
63
- declare function ident(name: string): Ident;
68
+ declare function ident(name: string, rawName?: string): Ident;
64
69
  /**
65
70
  * @param {string} name
66
71
  * @param {Node[]} args
72
+ * @param {string} [rawName]
67
73
  * @return {Call}
68
74
  */
69
- declare function call(name: string, args: Node[]): Call;
75
+ declare function call(name: string, args: Node[], rawName?: string): Call;
70
76
  /**
71
77
  * @param {SumTerm[]} rawTerms
72
78
  * @return {Node}
@@ -0,0 +1,15 @@
1
+ export type Node = import('./node.js').Node;
2
+ export type Component = string | Node | Component[];
3
+ /** @param {Extract<Node, {type: 'Call'}>} node @param {Component[]} tree */
4
+ declare function setComponents(node: Extract<Node, {
5
+ type: 'Call';
6
+ }>, tree: Component[]): import("./node.js").Call;
7
+ /** @param {Extract<Node, {type: 'Call'}>} node */
8
+ declare function getComponents(node: Extract<Node, {
9
+ type: 'Call';
10
+ }>): Component[] | undefined;
11
+ /** @param {Component[]} tree @param {(node: Node) => Node} simplify @return {Component[]} */
12
+ declare function simplifyComponents(tree: Component[], simplify: (node: Node) => Node): Component[];
13
+ /** @param {Component[]} tree @param {(node: Node) => string} serialize @return {string} */
14
+ declare function serializeComponents(tree: Component[], serialize: (node: Node) => string): string;
15
+ export { getComponents, setComponents, simplifyComponents, serializeComponents, };