postcss-calc 11.2.1 → 11.2.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.
Files changed (49) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +47 -35
  3. package/package.json +15 -15
  4. package/src/index.js +2 -1
  5. package/src/lib/analyze.js +0 -6
  6. package/src/lib/compile.js +1 -1
  7. package/src/lib/function-analyzers.js +119 -0
  8. package/src/lib/functions.js +25 -175
  9. package/src/lib/node.js +61 -4
  10. package/src/lib/parser/opaque.js +152 -0
  11. package/src/lib/parser/tokens.js +242 -0
  12. package/src/lib/parser.js +32 -377
  13. package/src/lib/serialize/expression.js +329 -0
  14. package/src/lib/serialize/precedence.js +47 -0
  15. package/src/lib/serialize/precision.js +244 -0
  16. package/src/lib/serialize.js +24 -561
  17. package/src/lib/simplify/bucket.js +22 -5
  18. package/src/lib/simplify/cancel.js +35 -11
  19. package/src/lib/simplify/clamp.js +2 -2
  20. package/src/lib/simplify/exact.js +18 -0
  21. package/src/lib/simplify/fold.js +15 -1
  22. package/src/lib/simplify/min-max.js +2 -2
  23. package/src/lib/simplify/product-barrier.js +55 -0
  24. package/src/lib/simplify/product-quotient.js +141 -0
  25. package/src/lib/simplify/product.js +92 -48
  26. package/src/lib/simplify/sum.js +6 -2
  27. package/src/lib/simplify.js +22 -5
  28. package/src/lib/types.js +94 -0
  29. package/src/reduce.js +2 -1
  30. package/types/lib/function-analyzers.d.ts +27 -0
  31. package/types/lib/functions.d.ts +3 -28
  32. package/types/lib/node.d.ts +17 -2
  33. package/types/lib/parser/opaque.d.ts +12 -0
  34. package/types/lib/parser/tokens.d.ts +83 -0
  35. package/types/lib/parser.d.ts +4 -44
  36. package/types/lib/serialize/expression.d.ts +55 -0
  37. package/types/lib/serialize/precedence.d.ts +18 -0
  38. package/types/lib/serialize/precision.d.ts +102 -0
  39. package/types/lib/simplify/bucket.d.ts +4 -1
  40. package/types/lib/simplify/cancel.d.ts +11 -7
  41. package/types/lib/simplify/exact.d.ts +9 -0
  42. package/types/lib/simplify/fold.d.ts +14 -1
  43. package/types/lib/simplify/product-barrier.d.ts +26 -0
  44. package/types/lib/simplify/product-quotient.d.ts +48 -0
  45. package/types/lib/simplify/product.d.ts +3 -7
  46. package/types/lib/simplify/sum.d.ts +2 -1
  47. package/types/lib/simplify.d.ts +3 -8
  48. package/types/lib/types.d.ts +48 -0
  49. package/types/lib/calculation-type.d.ts +0 -18
@@ -0,0 +1,94 @@
1
+ /**
2
+ * `percent` marks a value that resolves in the same percentage context as its
3
+ * peers (a pure percentage). It is only produced at leaves, by abs(), and by
4
+ * homogeneous sums and calls — never by a product, where an unpaired
5
+ * percentage could no longer cancel against anything.
6
+ * @typedef {{kind: 'number'} | {kind: 'dimension', base: string | null} | {kind: 'unknown', percent?: true} | {kind: 'failure'}} CalculationType
7
+ */
8
+
9
+ /** @type {CalculationType} */ const numberType = { kind: 'number' };
10
+ /** @type {CalculationType} */ const unknownType = { kind: 'unknown' };
11
+ /** @type {CalculationType} */ const percentageType = {
12
+ kind: 'unknown',
13
+ percent: true,
14
+ };
15
+ /** @type {CalculationType} */ const failureType = { kind: 'failure' };
16
+
17
+ /** @param {CalculationType} type @return {boolean} */
18
+ function isFailure(type) {
19
+ return type.kind === 'failure';
20
+ }
21
+
22
+ /** @param {CalculationType} type @return {boolean} */
23
+ function isPercentage(type) {
24
+ return type.kind === 'unknown' && type.percent === true;
25
+ }
26
+
27
+ /** @param {CalculationType} a @param {CalculationType} b @return {CalculationType} */
28
+ function addTypes(a, b) {
29
+ if (isFailure(a) || isFailure(b)) return failureType;
30
+ if (a.kind === 'unknown' || b.kind === 'unknown') {
31
+ // A pure percentage keeps its contextual type so a surrounding product
32
+ // can cancel `% / %`; mixing it with any other opaque operand loses the
33
+ // guarantee that it resolves in the same context as its peers. A known
34
+ // number + percentage sum is likewise kept unknown and valid: the spec
35
+ // resolves the percentage against its surrounding context, which this
36
+ // coarse type model does not track.
37
+ return isPercentage(a) && isPercentage(b) ? percentageType : unknownType;
38
+ }
39
+ if (a.kind === 'number' && b.kind === 'number') return numberType;
40
+ if (a.kind === 'dimension' && b.kind === 'dimension') {
41
+ if (a.base === null || b.base === null) return unknownType;
42
+ return a.base === b.base ? a : failureType;
43
+ }
44
+ return failureType;
45
+ }
46
+
47
+ /**
48
+ * Check an all-number function without assuming anything about an unresolved
49
+ * operand. A concrete dimension can never become a number, so it is still a
50
+ * definite error when another argument is opaque.
51
+ * @param {CalculationType[]} args
52
+ * @param {number} min
53
+ * @param {number} max
54
+ * @return {CalculationType}
55
+ */
56
+ function numberArguments(args, min, max) {
57
+ if (args.length < min || args.length > max) return failureType;
58
+ let hasUnknown = false;
59
+ for (const arg of args) {
60
+ if (arg.kind === 'dimension') return failureType;
61
+ hasUnknown = hasUnknown || arg.kind === 'unknown';
62
+ }
63
+ return hasUnknown ? unknownType : numberType;
64
+ }
65
+
66
+ /**
67
+ * Check values that must share a calculation type. Unknown operands remain
68
+ * unknown: they might resolve to the concrete type required by their peers.
69
+ * @param {CalculationType[]} args
70
+ * @param {number} min
71
+ * @param {number} max
72
+ * @return {CalculationType}
73
+ */
74
+ function matchingArguments(args, min, max) {
75
+ if (args.length < min || args.length > max) return failureType;
76
+ let result = args[0];
77
+ for (let index = 1; index < args.length; index++) {
78
+ result = addTypes(result, args[index]);
79
+ if (isFailure(result)) return failureType;
80
+ }
81
+ return result;
82
+ }
83
+
84
+ export {
85
+ addTypes,
86
+ failureType,
87
+ isFailure,
88
+ isPercentage,
89
+ matchingArguments,
90
+ numberArguments,
91
+ numberType,
92
+ percentageType,
93
+ unknownType,
94
+ };
package/src/reduce.js CHANGED
@@ -61,11 +61,12 @@ function reduceCalc(value, opts) {
61
61
 
62
62
  /** @type {ResolvedReduceCalcOptions} */
63
63
  const options = {
64
- precision: 5,
65
64
  warnWhenCannotResolve: false,
66
65
  unwrapSingleNegativeNumber: false,
67
66
  unwrapSingleValue: false,
68
67
  ...opts,
68
+ // An explicit `precision: undefined` keeps the default.
69
+ precision: opts?.precision ?? 5,
69
70
  };
70
71
  /** @type {import('@csstools/css-tokenizer').CSSToken[]} */
71
72
  let tokens;
@@ -0,0 +1,27 @@
1
+ export type Node = import('./node.js').Node;
2
+ export type CalculationType = import('./types.js').CalculationType;
3
+ /**
4
+ * @typedef {import('./node.js').Node} Node
5
+ * @typedef {import('./types.js').CalculationType} CalculationType
6
+ */
7
+ /** @param {CalculationType[]} args @return {CalculationType} */
8
+ declare function analyzeTrig(args: CalculationType[]): CalculationType;
9
+ /** @param {CalculationType[]} args @return {CalculationType} */
10
+ declare function analyzeInverseTrig(args: CalculationType[]): CalculationType;
11
+ /** @param {CalculationType[]} args @return {CalculationType} */
12
+ declare function analyzeIdentity(args: CalculationType[]): CalculationType;
13
+ /** @param {CalculationType[]} args @return {CalculationType} */
14
+ declare function analyzeSign(args: CalculationType[]): CalculationType;
15
+ /** @param {CalculationType[]} args @param {Node[]} nodes @return {CalculationType} */
16
+ declare function analyzeRound(args: CalculationType[], nodes: Node[]): CalculationType;
17
+ /** @param {CalculationType[]} args @return {CalculationType} */
18
+ declare function analyzeAtan2(args: CalculationType[]): CalculationType;
19
+ /** @param {CalculationType[]} args @return {CalculationType} */
20
+ declare function analyzeCalc(args: CalculationType[]): CalculationType;
21
+ /** @param {Node} node @return {boolean} */
22
+ declare function isRoundStrategy(node: Node): boolean;
23
+ /** @param {Node} node @param {number} index @return {boolean} */
24
+ declare function isClampKeyword(node: Node, index: number): boolean;
25
+ /** @param {CalculationType[]} args @param {Node[]} nodes @return {CalculationType} */
26
+ declare function analyzeClamp(args: CalculationType[], nodes: Node[]): CalculationType;
27
+ export { analyzeAtan2, analyzeCalc, analyzeClamp, analyzeIdentity, analyzeInverseTrig, analyzeRound, analyzeSign, analyzeTrig, isClampKeyword, isRoundStrategy, };
@@ -1,15 +1,6 @@
1
+ import { addTypes, failureType, isFailure, isPercentage, numberType, percentageType, unknownType } from './types.js';
1
2
  export type Node = import('./node.js').Node;
2
- export type CalculationType = {
3
- kind: 'number';
4
- } | {
5
- kind: 'dimension';
6
- base: string | null;
7
- } | {
8
- kind: 'unknown';
9
- percent?: true;
10
- } | {
11
- kind: 'failure';
12
- };
3
+ export type CalculationType = import('./types.js').CalculationType;
13
4
  export type MathSimplifier = (name: string, args: Node[]) => Node;
14
5
  export type TypeAnalyzer = (args: CalculationType[], nodes: Node[]) => CalculationType;
15
6
  export type MathFunction = {
@@ -19,26 +10,10 @@ export type MathFunction = {
19
10
  calculation?: boolean;
20
11
  };
21
12
  /** @typedef {import('./node.js').Node} Node */
22
- /**
23
- * `percent` marks a value that resolves in the same percentage context as its
24
- * peers (a pure percentage). It is only produced at leaves, by abs(), and by
25
- * homogeneous sums and calls — never by a product, where an unpaired
26
- * percentage could no longer cancel against anything.
27
- * @typedef {{kind: 'number'} | {kind: 'dimension', base: string | null} | {kind: 'unknown', percent?: true} | {kind: 'failure'}} CalculationType
28
- */
13
+ /** @typedef {import('./types.js').CalculationType} CalculationType */
29
14
  /** @typedef {(name: string, args: Node[]) => Node} MathSimplifier */
30
15
  /** @typedef {(args: CalculationType[], nodes: Node[]) => CalculationType} TypeAnalyzer */
31
16
  /** @typedef {{analyze: TypeAnalyzer, simplify?: MathSimplifier, isKeyword?: (node: Node, index: number) => boolean, calculation?: boolean}} MathFunction */
32
- /** @type {CalculationType} */ declare const numberType: CalculationType;
33
- /** @type {CalculationType} */ declare const unknownType: CalculationType;
34
- /** @type {CalculationType} */ declare const percentageType: CalculationType;
35
- /** @type {CalculationType} */ declare const failureType: CalculationType;
36
- /** @param {CalculationType} type @return {boolean} */
37
- declare function isFailure(type: CalculationType): boolean;
38
- /** @param {CalculationType} type @return {boolean} */
39
- declare function isPercentage(type: CalculationType): boolean;
40
- /** @param {CalculationType} a @param {CalculationType} b @return {CalculationType} */
41
- declare function addTypes(a: CalculationType, b: CalculationType): CalculationType;
42
17
  declare const mathFunctions: Map<string, MathFunction>;
43
18
  /**
44
19
  * @param {string} name
@@ -42,6 +42,7 @@ export type ProductFactor = {
42
42
  export type Product = {
43
43
  type: 'Product';
44
44
  factors: ProductFactor[];
45
+ grouped?: boolean;
45
46
  };
46
47
  export type Node = Num | Dim | Ident | Call | OpaqueCall | Sum | Product;
47
48
  /**
@@ -54,7 +55,7 @@ export type Node = Num | Dim | Ident | Call | OpaqueCall | Sum | Product;
54
55
  * @typedef {{sign: 1 | -1, node: Node}} SumTerm Sign is always +1 when node is Num or Dim.
55
56
  * @typedef {{type: 'Sum', terms: SumTerm[], grouped?: boolean}} Sum
56
57
  * @typedef {{exponent: 1 | -1, node: Node}} ProductFactor exponent +1 = numerator, -1 = denominator.
57
- * @typedef {{type: 'Product', factors: ProductFactor[]}} Product
58
+ * @typedef {{type: 'Product', factors: ProductFactor[], grouped?: boolean}} Product
58
59
  * @typedef {Num | Dim | Ident | Call | OpaqueCall | Sum | Product} Node
59
60
  */
60
61
  /**
@@ -99,10 +100,24 @@ declare function mkSum(rawTerms: SumTerm[]): Node;
99
100
  * @return {Node}
100
101
  */
101
102
  declare function mkProduct(rawFactors: ProductFactor[]): Node;
103
+ /**
104
+ * Mark a node as a source parenthesized group. A Sum or Product keeps its
105
+ * structure; any other node becomes a single-factor grouped Product.
106
+ * @param {Node} node
107
+ * @return {Node}
108
+ */
109
+ declare function mkGroup(node: Node): Node;
110
+ /**
111
+ * Group a parenthesized expression when its tokens must stay bound: a sum, or
112
+ * a product around a substitution function.
113
+ * @param {Node} node
114
+ * @return {Node}
115
+ */
116
+ declare function groupSubstitution(node: Node): Node;
102
117
  /**
103
118
  * Negate any node, preserving canonical form.
104
119
  * @param {Node} node
105
120
  * @return {Node}
106
121
  */
107
122
  declare function negate(node: Node): Node;
108
- export { num, dim, ident, call, opaqueCall, mkSum, mkProduct, negate };
123
+ export { num, dim, ident, call, opaqueCall, mkSum, mkProduct, mkGroup, groupSubstitution, negate, };
@@ -0,0 +1,12 @@
1
+ export type CSSToken = import('@csstools/css-tokenizer').CSSToken;
2
+ export type Node = import('../node.js').Node;
3
+ export type OpaqueComponent = import('../node.js').OpaqueComponent;
4
+ export type ParseInput = import('./tokens.js').ParseInput;
5
+ export type Cursor = import('./tokens.js').Cursor;
6
+ export type FunctionToken = import('./tokens.js').FunctionToken;
7
+ export type ParseRange = (input: ParseInput, start: number, end: number) => Node;
8
+ /** @param {ParseInput} input @param {Cursor} cursor @param {FunctionToken} token @param {string} name @param {string} rawName @param {ParseRange} parseRange @return {Node} */
9
+ declare function parseOpaqueCall(input: ParseInput, cursor: Cursor, token: FunctionToken, name: string, rawName: string, parseRange: ParseRange): Node;
10
+ /** @param {ParseInput} input @param {Cursor} cursor @param {FunctionToken} token @param {string} name @param {string} rawName @param {ParseRange} parseRange @return {Node} */
11
+ declare function parseVar(input: ParseInput, cursor: Cursor, token: FunctionToken, name: string, rawName: string, parseRange: ParseRange): Node;
12
+ export { parseOpaqueCall, parseVar };
@@ -0,0 +1,83 @@
1
+ export type CSSToken = import('@csstools/css-tokenizer').CSSToken;
2
+ export type BlockIndex = ReturnType<typeof import('../block-index.js').indexBlocks>;
3
+ export type TokenBase = {
4
+ raw: string;
5
+ pos: number;
6
+ ws: boolean;
7
+ index: number;
8
+ };
9
+ export type NumberToken = TokenBase & {
10
+ type: 'number';
11
+ value: number;
12
+ signCharacter?: '+' | '-';
13
+ };
14
+ export type DimensionToken = TokenBase & {
15
+ type: 'dimension';
16
+ value: number;
17
+ unit: string;
18
+ rawUnit: string;
19
+ signCharacter?: '+' | '-';
20
+ };
21
+ export type IdentToken = TokenBase & {
22
+ type: 'ident';
23
+ value: string;
24
+ };
25
+ export type FunctionToken = TokenBase & {
26
+ type: 'function';
27
+ value: string;
28
+ };
29
+ export type Punctuator = '(' | ')' | ',' | '+' | '-' | '*' | '/';
30
+ export type PunctToken = TokenBase & {
31
+ type: 'punct';
32
+ value: Punctuator;
33
+ };
34
+ export type EofToken = TokenBase & {
35
+ type: 'eof';
36
+ value: '';
37
+ raw: '';
38
+ };
39
+ export type Token = NumberToken | DimensionToken | IdentToken | FunctionToken | PunctToken | EofToken;
40
+ export type ParseInput = Readonly<{
41
+ tokens: CSSToken[];
42
+ end: number;
43
+ index: BlockIndex;
44
+ }>;
45
+ /** @param {string} raw @param {string} decoded */
46
+ declare function sourceSpelling(raw: string, decoded: string): string | undefined;
47
+ /**
48
+ * Mutable navigation state only. `index` is always the next native token
49
+ * position; trivia is intentionally left visible to `scanToken`.
50
+ */
51
+ declare class Cursor {
52
+ /** @type {number} */
53
+ index: number;
54
+ /** @type {boolean} */
55
+ firstToken: boolean;
56
+ /** @type {Token | null} */
57
+ lookahead: Token | null;
58
+ /** @type {number} */
59
+ lookaheadNextIndex: number;
60
+ /** @param {number} start */
61
+ constructor(start: number);
62
+ /** @param {number} index @return {void} */
63
+ skipTo(index: number): void;
64
+ }
65
+ /** @param {ParseInput} input @param {number} index @return {number} */
66
+ declare function eofPositionAt(input: ParseInput, index: number): number;
67
+ /** @param {ParseInput} input @param {Cursor} cursor @return {Token} */
68
+ declare function peekToken(input: ParseInput, cursor: Cursor): Token;
69
+ /**
70
+ * Consume the cached token and advance to its native next index. This is one
71
+ * of the only two operations allowed to advance `cursor.index`.
72
+ * @param {ParseInput} input
73
+ * @param {Cursor} cursor
74
+ * @return {Token}
75
+ */
76
+ declare function takeToken(input: ParseInput, cursor: Cursor): Token;
77
+ /** @param {ParseInput} input @param {Cursor} cursor @param {Punctuator} value @param {Punctuator} [value2] @return {boolean} */
78
+ declare function isPunct(input: ParseInput, cursor: Cursor, value: Punctuator, value2?: Punctuator): boolean;
79
+ /** @param {ParseInput} input @param {Cursor} cursor @param {Punctuator} value @return {boolean} */
80
+ declare function matchPunct(input: ParseInput, cursor: Cursor, value: Punctuator): boolean;
81
+ /** @param {ParseInput} input @param {Cursor} cursor @param {Punctuator} value @return {PunctToken} */
82
+ declare function expectPunct(input: ParseInput, cursor: Cursor, value: Punctuator): PunctToken;
83
+ export { Cursor, eofPositionAt, expectPunct, isPunct, matchPunct, peekToken, sourceSpelling, takeToken, };
@@ -1,49 +1,9 @@
1
1
  export type CSSToken = import('@csstools/css-tokenizer').CSSToken;
2
2
  export type Node = import('./node.js').Node;
3
- export type OpaqueComponent = import('./node.js').OpaqueComponent;
4
- export type BlockIndex = ReturnType<typeof import('./block-index.js').indexBlocks>;
5
- export type TokenBase = {
6
- raw: string;
7
- pos: number;
8
- ws: boolean;
9
- index: number;
10
- };
11
- export type NumberToken = TokenBase & {
12
- type: 'number';
13
- value: number;
14
- signCharacter?: '+' | '-';
15
- };
16
- export type DimensionToken = TokenBase & {
17
- type: 'dimension';
18
- value: number;
19
- unit: string;
20
- rawUnit: string;
21
- signCharacter?: '+' | '-';
22
- };
23
- export type IdentToken = TokenBase & {
24
- type: 'ident';
25
- value: string;
26
- };
27
- export type FunctionToken = TokenBase & {
28
- type: 'function';
29
- value: string;
30
- };
31
- export type Punctuator = '(' | ')' | ',' | '+' | '-' | '*' | '/';
32
- export type PunctToken = TokenBase & {
33
- type: 'punct';
34
- value: Punctuator;
35
- };
36
- export type EofToken = TokenBase & {
37
- type: 'eof';
38
- value: '';
39
- raw: '';
40
- };
41
- export type Token = NumberToken | DimensionToken | IdentToken | FunctionToken | PunctToken | EofToken;
42
- export type ParseInput = Readonly<{
43
- tokens: CSSToken[];
44
- end: number;
45
- index: BlockIndex;
46
- }>;
3
+ export type BlockIndex = import('./parser/tokens.js').BlockIndex;
4
+ export type ParseInput = import('./parser/tokens.js').ParseInput;
5
+ export type Token = import('./parser/tokens.js').Token;
6
+ export type FunctionToken = import('./parser/tokens.js').FunctionToken;
47
7
  /** @param {CSSToken[]} tokens @param {number} start @param {number} end @param {BlockIndex} index @return {Node} */
48
8
  declare function parse(tokens: CSSToken[], start: number, end: number, index: BlockIndex): Node;
49
9
  export { parse };
@@ -0,0 +1,55 @@
1
+ export type Node = import('../node.js').Node;
2
+ export type Sum = import('../node.js').Sum;
3
+ export type SumTerm = import('../node.js').SumTerm;
4
+ export type ProductFactor = import('../node.js').ProductFactor;
5
+ export type SerializeSession = import('./precision.js').SerializeSession;
6
+ /**
7
+ * @typedef {import('../node.js').Node} Node
8
+ * @typedef {import('../node.js').Sum} Sum
9
+ * @typedef {import('../node.js').SumTerm} SumTerm
10
+ * @typedef {import('../node.js').ProductFactor} ProductFactor
11
+ * @typedef {import('./precision.js').SerializeSession} SerializeSession
12
+ */
13
+ /**
14
+ * @param {Node} node
15
+ * @param {SerializeSession} session
16
+ * @param {number} [parentPrecedence]
17
+ * @param {boolean} [groupedRequired]
18
+ * @return {void}
19
+ */
20
+ declare function emitNode(node: Node, session: SerializeSession, parentPrecedence?: number, groupedRequired?: boolean): void;
21
+ /**
22
+ * @param {Node} node
23
+ * @param {SerializeSession} session
24
+ * @return {void}
25
+ */
26
+ declare function emitLeadingNeg(node: Node, session: SerializeSession): void;
27
+ /**
28
+ * @param {import('../node.js').Call} node
29
+ * @param {SerializeSession} session
30
+ * @param {string} [callNameOverride]
31
+ * @return {void}
32
+ */
33
+ declare function emitCall(node: import('../node.js').Call, session: SerializeSession, callNameOverride?: string): void;
34
+ /**
35
+ * @param {import('../node.js').OpaqueCall} node
36
+ * @param {SerializeSession} session
37
+ * @param {string} [callNameOverride]
38
+ * @return {void}
39
+ */
40
+ declare function emitOpaqueCall(node: import('../node.js').OpaqueCall, session: SerializeSession, callNameOverride?: string): void;
41
+ /**
42
+ * @param {Node} node
43
+ * @param {SerializeSession} session
44
+ * @param {string} wrapper
45
+ * @return {void}
46
+ */
47
+ declare function emitMathResult(node: Node, session: SerializeSession, wrapper: string): void;
48
+ /**
49
+ * @param {Node} node
50
+ * @param {SerializeSession} session
51
+ * @param {string[]} [buffer]
52
+ * @return {void}
53
+ */
54
+ declare function emitNestedMathResult(node: Node, session: SerializeSession, buffer?: string[]): void;
55
+ export { emitNode, emitLeadingNeg, emitCall, emitOpaqueCall, emitMathResult, emitNestedMathResult, };
@@ -0,0 +1,18 @@
1
+ export type Node = import('../node.js').Node;
2
+ /**
3
+ * @typedef {import('../node.js').Node} Node
4
+ */
5
+ declare const SUM_PRECEDENCE = 1;
6
+ declare const PRODUCT_PRECEDENCE = 2;
7
+ declare const ATOMIC_PRECEDENCE = 3;
8
+ declare const UNARY_PRECEDENCE = 3;
9
+ /** @param {Node} node @return {number} */
10
+ declare function precedence(node: Node): number;
11
+ /**
12
+ * @param {Node} node
13
+ * @param {number} parentPrecedence
14
+ * @param {boolean} groupedRequired
15
+ * @return {boolean}
16
+ */
17
+ declare function needsParentheses(node: Node, parentPrecedence: number, groupedRequired: boolean): boolean;
18
+ export { SUM_PRECEDENCE, PRODUCT_PRECEDENCE, ATOMIC_PRECEDENCE, UNARY_PRECEDENCE, precedence, needsParentheses, };
@@ -0,0 +1,102 @@
1
+ export type Node = import('../node.js').Node;
2
+ export type Num = import('../node.js').Num;
3
+ export type Dim = import('../node.js').Dim;
4
+ export type SerializeSession = {
5
+ buffer: string[];
6
+ precision: number | false;
7
+ scalarPolicy: 'standard' | 'unwrap-all';
8
+ };
9
+ /**
10
+ * @typedef {import('../node.js').Node} Node
11
+ * @typedef {import('../node.js').Num} Num
12
+ * @typedef {import('../node.js').Dim} Dim
13
+ * @typedef {object} SerializeSession
14
+ * @property {string[]} buffer
15
+ * @property {number | false} precision
16
+ * @property {'standard' | 'unwrap-all'} scalarPolicy
17
+ */
18
+ declare const NOISE_FLOOR = 1e-12;
19
+ /**
20
+ * Divide a decimal digit string by 10^k, rounding half away from zero, and
21
+ * return the resulting integer digit string. `digits` has no leading zeros.
22
+ * @param {string} digits
23
+ * @param {number} k
24
+ * @return {string}
25
+ */
26
+ declare function divideByPowerOfTen(digits: string, k: number): string;
27
+ /**
28
+ * Round the shortest decimal representation of a non-negative double to `p`
29
+ * fractional digits, half away from zero.
30
+ *
31
+ * `Number(text + 'e' + p)` reads the exact intended decimal (so `1.005` at
32
+ * precision 2 becomes `1.01`), but it is only exact while the shifted value
33
+ * fits in `Number.MAX_SAFE_INTEGER`; beyond that the intermediate double
34
+ * rounds and can move the rounding boundary (e.g. `312834450754803.44` at
35
+ * precision 1 or 6 drifted to `312834450754803.5`). Round the decimal digits
36
+ * directly instead.
37
+ *
38
+ * @param {number} abs
39
+ * @param {number} p
40
+ * @return {number}
41
+ */
42
+ declare function roundDecimal(abs: number, p: number): number;
43
+ /**
44
+ * @param {number} v
45
+ * @param {number | false} prec
46
+ * @return {number}
47
+ */
48
+ declare function round(v: number, prec: number | false): number;
49
+ /** @param {number} v @return {boolean} */
50
+ declare function isDegenerate(v: number): boolean;
51
+ /** @param {number} v @return {string} */
52
+ declare function degenerateKeyword(v: number): string;
53
+ /** @param {number} v @return {string} */
54
+ declare function serializeNumber(v: number): string;
55
+ /**
56
+ * @param {import('../node.js').Num | import('../node.js').Dim} node
57
+ * @param {number | false} precision
58
+ * @param {number} [value]
59
+ * @return {number}
60
+ */
61
+ declare function roundedScalarValue(node: import('../node.js').Num | import('../node.js').Dim, precision: number | false, value?: number): number;
62
+ /**
63
+ * @param {import('../node.js').Num | import('../node.js').Dim} node
64
+ * @param {string[]} buffer
65
+ * @param {number} value
66
+ * @return {void}
67
+ */
68
+ declare function emitRoundedScalar(node: import('../node.js').Num | import('../node.js').Dim, buffer: string[], value: number): void;
69
+ /**
70
+ * @param {import('../node.js').Num | import('../node.js').Dim} node
71
+ * @param {SerializeSession} session
72
+ * @param {number} [value]
73
+ * @return {number}
74
+ */
75
+ declare function emitFiniteScalar(node: import('../node.js').Num | import('../node.js').Dim, session: SerializeSession, value?: number): number;
76
+ /**
77
+ * @param {import('../node.js').Num | import('../node.js').Dim} node
78
+ * @param {SerializeSession} session
79
+ * @param {number} [value]
80
+ * @param {boolean} [bare] emit negative zero without its calc() wrapper
81
+ * @return {void}
82
+ */
83
+ declare function emitScalar(node: import('../node.js').Num | import('../node.js').Dim, session: SerializeSession, value?: number, bare?: boolean): void;
84
+ /**
85
+ * @param {string[]} buffer
86
+ * @param {import('../node.js').Num | import('../node.js').Dim} node
87
+ * @return {void}
88
+ */
89
+ declare function emitSignedZero(buffer: string[], node: import('../node.js').Num | import('../node.js').Dim): void;
90
+ /**
91
+ * Negative zero as an unwrapped product, valid only where a `<calc-product>`
92
+ * may appear without changing how the surrounding expression parses.
93
+ * @param {string[]} buffer
94
+ * @param {import('../node.js').Num | import('../node.js').Dim} node
95
+ * @return {void}
96
+ */
97
+ declare function emitBareSignedZero(buffer: string[], node: import('../node.js').Num | import('../node.js').Dim): void;
98
+ /** @param {Node} node @return {node is import('../node.js').Num | import('../node.js').Dim} */
99
+ declare function isScalar(node: Node): node is import('../node.js').Num | import('../node.js').Dim;
100
+ /** @param {Node} node @return {node is import('../node.js').Num | import('../node.js').Dim} */
101
+ declare function isSignedZero(node: Node): node is import('../node.js').Num | import('../node.js').Dim;
102
+ export { NOISE_FLOOR, divideByPowerOfTen, roundDecimal, round, isDegenerate, degenerateKeyword, serializeNumber, roundedScalarValue, emitRoundedScalar, emitFiniteScalar, emitScalar, emitSignedZero, emitBareSignedZero, isScalar, isSignedZero, };
@@ -19,7 +19,10 @@ export type UnitBucket = {
19
19
  /** Mutates `buckets` in place — totals of survivor buckets accumulate the
20
20
  * converted values of merged neighbors. Caller must not reuse the input.
21
21
  * @param {UnitBucket[]} buckets
22
+ * @param {number | false} [precision] A conversion that is not exact at this
23
+ * precision is not merged. When only the reverse direction is exact, the
24
+ * survivor switches to the other bucket's unit.
22
25
  * @return {UnitBucket[]}
23
26
  */
24
- declare function mergeConvertibleBuckets(buckets: UnitBucket[]): UnitBucket[];
27
+ declare function mergeConvertibleBuckets(buckets: UnitBucket[], precision?: number | false): UnitBucket[];
25
28
  export { mergeConvertibleBuckets };
@@ -1,20 +1,24 @@
1
1
  /**
2
2
  * If `dims` contain exactly one numerator / one denominator pair with the
3
- * same base type and convertible units, return the numeric factor produced
4
- * by cancelling them and the list of remaining (uncancelled) dims.
5
- * Otherwise return null. Used by `simplifyProduct` for typed division
6
- * (§10.2). More complex cancellation (e.g. `px^2 / px`) is left
3
+ * same base type and convertible units, return the two sides expressed in a
4
+ * common unit, so the caller can fold them into its own numerator and
5
+ * denominator. Otherwise return null. Used by `simplifyProduct` for typed
6
+ * division (§10.2). More complex cancellation (e.g. `px^2 / px`) is left
7
7
  * unreduced — consumers rarely rely on it and the spec doesn't require it.
8
8
  * @template {{ exponent: 1 | -1, value: number, unit: string }} D
9
9
  * @param {D[]} dims
10
- * @return {{ factor: number, remaining: D[] } | null}
10
+ * @param {number | false} [precision] The denominator is converted into the
11
+ * numerator's unit when that is exact at this precision (`1px / 1in` →
12
+ * `1 / 96`); otherwise the numerator is converted (`1in / 1px` → `96 / 1`).
13
+ * @return {{ numerator: number, denominator: number, remaining: D[] } | null}
11
14
  */
12
15
  declare function tryCancelPair<D extends {
13
16
  exponent: 1 | -1;
14
17
  value: number;
15
18
  unit: string;
16
- }>(dims: D[]): {
17
- factor: number;
19
+ }>(dims: D[], precision?: number | false): {
20
+ numerator: number;
21
+ denominator: number;
18
22
  remaining: D[];
19
23
  } | null;
20
24
  export { tryCancelPair };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * A value is exact at `precision` when rounding it for output loses nothing
3
+ * beyond float noise.
4
+ * @param {number} value
5
+ * @param {number | false} precision
6
+ * @return {boolean}
7
+ */
8
+ declare function isExact(value: number, precision: number | false): boolean;
9
+ export { isExact };
@@ -15,6 +15,19 @@ export type BaseType = import('../convertUnits.js').BaseType;
15
15
  declare function foldResult(fold: {
16
16
  unit: string;
17
17
  }, value: number): Num | Dim;
18
+ /**
19
+ * Return the argument that produced a folded min/max/clamp value, so the
20
+ * result keeps its own unit, spelling and zero sign. Falls back to a fresh
21
+ * node when no argument matches (NaN).
22
+ * @param {Node[]} args
23
+ * @param {{ values: number[], unit: string }} fold
24
+ * @param {number} value
25
+ * @return {Node}
26
+ */
27
+ declare function chosenArg(args: Node[], fold: {
28
+ values: number[];
29
+ unit: string;
30
+ }, value: number): Node;
18
31
  /**
19
32
  * @param {Node[]} args
20
33
  * @return {{ values: number[], unit: string } | null}
@@ -23,4 +36,4 @@ declare function foldConstArgs(args: Node[]): {
23
36
  values: number[];
24
37
  unit: string;
25
38
  } | null;
26
- export { foldConstArgs, foldResult };
39
+ export { foldConstArgs, foldResult, chosenArg };
@@ -0,0 +1,26 @@
1
+ export type Node = import('../node.js').Node;
2
+ export type ProductFactor = import('../node.js').ProductFactor;
3
+ /**
4
+ * @typedef {import('../node.js').Node} Node
5
+ * @typedef {import('../node.js').ProductFactor} ProductFactor
6
+ */
7
+ /**
8
+ * A substitution function (`var()`, `env()`, `attr()`, ...) is replaced by
9
+ * tokens, not by a value, so factors cannot move or cancel across it. Any
10
+ * function the parser does not recognise (`anchor()`, `foo()`, ...) is also an
11
+ * OpaqueCall and is treated the same, conservatively. A grouped Product is a
12
+ * parenthesized substitution and is equally opaque.
13
+ * @param {Node} node
14
+ * @return {boolean}
15
+ */
16
+ declare function isBarrier(node: Node): boolean;
17
+ /**
18
+ * Flatten a simplified factor through nested ungrouped Products and the
19
+ * canonical negation form, so barriers are visible at the top level.
20
+ * @param {ProductFactor[]} out
21
+ * @param {1 | -1} exponent
22
+ * @param {Node} node
23
+ * @return {boolean} Whether a barrier was pushed.
24
+ */
25
+ declare function flattenFactor(out: ProductFactor[], exponent: 1 | -1, node: Node): boolean;
26
+ export { flattenFactor, isBarrier };