@thejaredwilcurt/csslop 0.0.49 → 0.0.51
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 +0 -1
- package/package.json +2 -2
- package/src/declarations/border.js +1 -1
- package/src/declarations/process.js +155 -1
- package/src/index.js +7 -7
- package/src/rules/optimize/at-rules.js +113 -0
- package/src/rules/optimize/declaration-writes.js +70 -0
- package/src/rules/optimize/empty-rules.js +51 -0
- package/src/rules/optimize/expand-nesting.js +236 -0
- package/src/rules/optimize/factor-parents.js +343 -0
- package/src/rules/optimize/index.js +71 -0
- package/src/rules/optimize/merge-declarations.js +199 -0
- package/src/rules/optimize/merge-nested-rules.js +117 -0
- package/src/rules/optimize/merge-selectors.js +252 -0
- package/src/rules/optimize/nest-rules.js +202 -0
- package/src/rules/optimize/override-index.js +264 -0
- package/src/rules/optimize/prune-overridden.js +267 -0
- package/src/rules/optimize/selector-normalization.js +60 -0
- package/src/rules/optimize/specificity.js +280 -0
- package/src/{position-try.js → rules/position-try.js} +1 -1
- package/src/rules/stringify.js +13 -0
- package/src/utilities.js +14 -0
- package/src/value/border.js +85 -0
- package/src/value/{colors.js → color/conversion.js} +3 -2
- package/src/value/color/functions.js +463 -0
- package/src/value/{light-dark.js → color/light-dark.js} +1 -1
- package/src/value/{color-mix.js → color/mix.js} +3 -3
- package/src/value/{relative-colors.js → color/relative.js} +3 -3
- package/src/value/color/shorten.js +93 -0
- package/src/value/gradients.js +1 -1
- package/src/value/hyphenate-limit-chars.js +72 -0
- package/src/value/initial-keywords.js +128 -0
- package/src/value/lengths.js +55 -0
- package/src/value/math/evaluate.js +36 -0
- package/src/value/math/fold.js +315 -0
- package/src/value/math/format.js +230 -0
- package/src/value/math/functions.js +302 -0
- package/src/value/math/index.js +219 -0
- package/src/value/math/parser.js +301 -0
- package/src/value/math/property-ranges.js +174 -0
- package/src/value/math/rewrite.js +222 -0
- package/src/value/math/tokenizer.js +160 -0
- package/src/value/math/unit-totals.js +194 -0
- package/src/value/minify.js +127 -1669
- package/src/value/numbers.js +28 -0
- package/src/value/positions.js +152 -0
- package/src/value/property-optimizations.js +485 -0
- package/src/value/segments.js +74 -0
- package/src/value/{svg-data-uri.js → svg/data-uri.js} +1 -1
- package/src/value/{svg-markup.js → svg/markup.js} +1 -1
- package/src/value/time.js +75 -0
- package/src/value/urls.js +242 -0
- package/src/value/whitespace.js +88 -0
- package/src/rules/optimize.js +0 -2314
- package/src/value/math-expression.js +0 -1156
- package/src/value/math.js +0 -886
- /package/src/value/{named-colors.js → color/named.js} +0 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Resolves the CSS math functions whose arguments are arithmetic, such as `min()`, `clamp()`, and `round()`, once their arguments are known.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
createUnitTotals,
|
|
7
|
+
readSingleQuantity,
|
|
8
|
+
UNITLESS
|
|
9
|
+
} from './unit-totals.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* How each rounding strategy of `round()` turns the number of intervals it is
|
|
13
|
+
* given into the whole number of them it keeps.
|
|
14
|
+
*
|
|
15
|
+
* @type {Map<string, function(number): number>}
|
|
16
|
+
*/
|
|
17
|
+
const ROUNDING_STRATEGIES = new Map([
|
|
18
|
+
['nearest', Math.round],
|
|
19
|
+
['up', Math.ceil],
|
|
20
|
+
['down', Math.floor],
|
|
21
|
+
['to-zero', Math.trunc]
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Reads the arguments of a function as the numbers of one shared unit, which
|
|
26
|
+
* is what a function comparing or rounding its arguments needs them to be.
|
|
27
|
+
* Arguments carrying different units are only comparable once they are
|
|
28
|
+
* resolved against the element, which is long after minification.
|
|
29
|
+
*
|
|
30
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
31
|
+
* @return {object|null} The shared unit and the numbers the arguments state, or null when they do not share one.
|
|
32
|
+
*/
|
|
33
|
+
function readComparableArguments (argumentList) {
|
|
34
|
+
const numbers = [];
|
|
35
|
+
let sharedUnit = null;
|
|
36
|
+
for (const argument of argumentList) {
|
|
37
|
+
if (!argument.value) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const quantity = readSingleQuantity(argument.value);
|
|
41
|
+
if (!quantity) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
if (sharedUnit !== null && quantity.unit !== sharedUnit) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
sharedUnit = quantity.unit;
|
|
48
|
+
numbers.push(quantity.number);
|
|
49
|
+
}
|
|
50
|
+
if (!numbers.length) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return { numbers, unit: sharedUnit };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Reads the single value argument a function takes, which no keyword may
|
|
58
|
+
* stand in for.
|
|
59
|
+
*
|
|
60
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
61
|
+
* @return {Map|null} The unit totals of the lone argument, or null when the function was called with anything else.
|
|
62
|
+
*/
|
|
63
|
+
function readLoneArgument (argumentList) {
|
|
64
|
+
if (argumentList.length !== 1) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return argumentList[0].value;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Resolves `calc()`, which states the value its one argument works out to.
|
|
72
|
+
*
|
|
73
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
74
|
+
* @return {Map|null} The unit totals the calculation states, or null when it cannot be resolved.
|
|
75
|
+
*/
|
|
76
|
+
function evaluateCalculation (argumentList) {
|
|
77
|
+
return readLoneArgument(argumentList);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Resolves `min()`, which states the smallest of the values it compares.
|
|
82
|
+
*
|
|
83
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
84
|
+
* @return {Map|null} The unit totals of the smallest argument, or null when the arguments are not comparable.
|
|
85
|
+
*/
|
|
86
|
+
function evaluateMinimum (argumentList) {
|
|
87
|
+
const comparable = readComparableArguments(argumentList);
|
|
88
|
+
if (!comparable) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const winningIndex = comparable.numbers.indexOf(Math.min(...comparable.numbers));
|
|
92
|
+
return argumentList[winningIndex].value;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Resolves `max()`, which states the largest of the values it compares.
|
|
97
|
+
*
|
|
98
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
99
|
+
* @return {Map|null} The unit totals of the largest argument, or null when the arguments are not comparable.
|
|
100
|
+
*/
|
|
101
|
+
function evaluateMaximum (argumentList) {
|
|
102
|
+
const comparable = readComparableArguments(argumentList);
|
|
103
|
+
if (!comparable) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const winningIndex = comparable.numbers.indexOf(Math.max(...comparable.numbers));
|
|
107
|
+
return argumentList[winningIndex].value;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Reports whether an end of a `clamp()` range is left open, which the `none`
|
|
112
|
+
* keyword does, leaving that end to bound nothing.
|
|
113
|
+
*
|
|
114
|
+
* @param {object} argument The argument written at that end of the range.
|
|
115
|
+
* @return {boolean} Whether that end of the range is open.
|
|
116
|
+
*/
|
|
117
|
+
function boundsNothing (argument) {
|
|
118
|
+
return argument.keyword === 'none';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Resolves `clamp()`, which holds its preferred value inside the range its
|
|
123
|
+
* other two arguments bound. The smallest value wins a range written back to
|
|
124
|
+
* front, since the preferred value is held below the maximum before it is
|
|
125
|
+
* held above the minimum.
|
|
126
|
+
*
|
|
127
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
128
|
+
* @return {Map|null} The unit totals of the clamped value, or null when the arguments are not comparable.
|
|
129
|
+
*/
|
|
130
|
+
function evaluateClamp (argumentList) {
|
|
131
|
+
if (argumentList.length !== 3) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
const [lowerBound, preferred, upperBound] = argumentList;
|
|
135
|
+
const boundedArguments = argumentList.filter((argument) => {
|
|
136
|
+
return !boundsNothing(argument);
|
|
137
|
+
});
|
|
138
|
+
const comparable = readComparableArguments(boundedArguments);
|
|
139
|
+
if (!comparable || !preferred.value) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
let clamped = readSingleQuantity(preferred.value).number;
|
|
143
|
+
if (!boundsNothing(upperBound)) {
|
|
144
|
+
clamped = Math.min(clamped, readSingleQuantity(upperBound.value).number);
|
|
145
|
+
}
|
|
146
|
+
if (!boundsNothing(lowerBound)) {
|
|
147
|
+
clamped = Math.max(clamped, readSingleQuantity(lowerBound.value).number);
|
|
148
|
+
}
|
|
149
|
+
return createUnitTotals(clamped, comparable.unit);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Resolves `abs()`, which states its argument's distance from zero.
|
|
154
|
+
*
|
|
155
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
156
|
+
* @return {Map|null} The unit totals of the absolute value, or null when the argument states more than one quantity.
|
|
157
|
+
*/
|
|
158
|
+
function evaluateAbsolute (argumentList) {
|
|
159
|
+
const comparable = readComparableArguments(argumentList);
|
|
160
|
+
if (!comparable || comparable.numbers.length !== 1) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
return createUnitTotals(Math.abs(comparable.numbers[0]), comparable.unit);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Resolves `sign()`, which states which side of zero its argument lies on as
|
|
168
|
+
* a plain number.
|
|
169
|
+
*
|
|
170
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
171
|
+
* @return {Map|null} The unit totals of the sign, or null when the argument states more than one quantity.
|
|
172
|
+
*/
|
|
173
|
+
function evaluateSign (argumentList) {
|
|
174
|
+
const comparable = readComparableArguments(argumentList);
|
|
175
|
+
if (!comparable || comparable.numbers.length !== 1) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
return createUnitTotals(Math.sign(comparable.numbers[0]), UNITLESS);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Resolves `hypot()`, which states the length of the vector its arguments
|
|
183
|
+
* measure the sides of.
|
|
184
|
+
*
|
|
185
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
186
|
+
* @return {Map|null} The unit totals of the hypotenuse, or null when the arguments are not comparable.
|
|
187
|
+
*/
|
|
188
|
+
function evaluateHypotenuse (argumentList) {
|
|
189
|
+
const comparable = readComparableArguments(argumentList);
|
|
190
|
+
if (!comparable) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
return createUnitTotals(Math.hypot(...comparable.numbers), comparable.unit);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Reads the two comparable numbers a function divides one of by the other,
|
|
198
|
+
* refusing a divisor of zero, which no division resolves against.
|
|
199
|
+
*
|
|
200
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
201
|
+
* @return {object|null} The shared unit with the dividend and the divisor, or null when the arguments cannot be divided.
|
|
202
|
+
*/
|
|
203
|
+
function readDivisionArguments (argumentList) {
|
|
204
|
+
const comparable = readComparableArguments(argumentList);
|
|
205
|
+
if (!comparable || comparable.numbers.length !== 2 || comparable.numbers[1] === 0) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
dividend: comparable.numbers[0],
|
|
210
|
+
divisor: comparable.numbers[1],
|
|
211
|
+
unit: comparable.unit
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Resolves `mod()`, which states what is left of its first argument after
|
|
217
|
+
* taking out whole multiples of its second, carrying the sign of the divisor.
|
|
218
|
+
*
|
|
219
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
220
|
+
* @return {Map|null} The unit totals of the modulus, or null when the arguments cannot be divided.
|
|
221
|
+
*/
|
|
222
|
+
function evaluateModulus (argumentList) {
|
|
223
|
+
const division = readDivisionArguments(argumentList);
|
|
224
|
+
if (!division) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
const { dividend, divisor, unit } = division;
|
|
228
|
+
return createUnitTotals(((dividend % divisor) + divisor) % divisor, unit);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Resolves `rem()`, which states what is left of its first argument after
|
|
233
|
+
* taking out whole multiples of its second, carrying the sign of the dividend.
|
|
234
|
+
*
|
|
235
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
236
|
+
* @return {Map|null} The unit totals of the remainder, or null when the arguments cannot be divided.
|
|
237
|
+
*/
|
|
238
|
+
function evaluateRemainder (argumentList) {
|
|
239
|
+
const division = readDivisionArguments(argumentList);
|
|
240
|
+
if (!division) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
const { dividend, divisor, unit } = division;
|
|
244
|
+
return createUnitTotals(dividend % divisor, unit);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Resolves `round()`, which states the multiple of its rounding interval that
|
|
249
|
+
* the strategy it is given rounds its value to. A call written without a
|
|
250
|
+
* strategy rounds to the nearest multiple.
|
|
251
|
+
*
|
|
252
|
+
* @param {Array} argumentList The arguments the function was called with.
|
|
253
|
+
* @return {Map|null} The unit totals of the rounded value, or null when the arguments cannot be rounded.
|
|
254
|
+
*/
|
|
255
|
+
function evaluateRound (argumentList) {
|
|
256
|
+
let strategyName = 'nearest';
|
|
257
|
+
let valueArguments = argumentList;
|
|
258
|
+
if (argumentList.length === 3) {
|
|
259
|
+
strategyName = argumentList[0].keyword;
|
|
260
|
+
valueArguments = argumentList.slice(1);
|
|
261
|
+
}
|
|
262
|
+
const roundIntervals = ROUNDING_STRATEGIES.get(strategyName);
|
|
263
|
+
const division = readDivisionArguments(valueArguments);
|
|
264
|
+
if (!roundIntervals || !division) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
const { dividend, divisor, unit } = division;
|
|
268
|
+
return createUnitTotals(roundIntervals(dividend / divisor) * divisor, unit);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* How each math function resolves the arguments it is called with. The
|
|
273
|
+
* functions listed here are the ones whose arguments are arithmetic, so that
|
|
274
|
+
* `*` and `/` between them are operators rather than characters of some other
|
|
275
|
+
* syntax.
|
|
276
|
+
*
|
|
277
|
+
* @type {Map<string, function(Array): (Map|null)>}
|
|
278
|
+
*/
|
|
279
|
+
const MATH_FUNCTION_EVALUATORS = new Map([
|
|
280
|
+
['abs', evaluateAbsolute],
|
|
281
|
+
['calc', evaluateCalculation],
|
|
282
|
+
['clamp', evaluateClamp],
|
|
283
|
+
['hypot', evaluateHypotenuse],
|
|
284
|
+
['max', evaluateMaximum],
|
|
285
|
+
['min', evaluateMinimum],
|
|
286
|
+
['mod', evaluateModulus],
|
|
287
|
+
['rem', evaluateRemainder],
|
|
288
|
+
['round', evaluateRound],
|
|
289
|
+
['sign', evaluateSign]
|
|
290
|
+
]);
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* The names of the functions whose arguments are a math expression.
|
|
294
|
+
*
|
|
295
|
+
* @type {Set<string>}
|
|
296
|
+
*/
|
|
297
|
+
const MATH_FUNCTION_NAMES = new Set(MATH_FUNCTION_EVALUATORS.keys());
|
|
298
|
+
|
|
299
|
+
export {
|
|
300
|
+
MATH_FUNCTION_EVALUATORS,
|
|
301
|
+
MATH_FUNCTION_NAMES
|
|
302
|
+
};
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Simplifies CSS math functions (calc, min, max) by folding constant expressions, flattening nested calcs, and converting absolute lengths to pixels.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
convertAbsoluteLengthToPx,
|
|
7
|
+
formatResolvedNumber,
|
|
8
|
+
roundCompactNumber
|
|
9
|
+
} from '../shared.js';
|
|
10
|
+
|
|
11
|
+
import { simplifyCalc } from './evaluate.js';
|
|
12
|
+
import {
|
|
13
|
+
foldStandaloneCalc,
|
|
14
|
+
resolveCalcExpression
|
|
15
|
+
} from './fold.js';
|
|
16
|
+
import { MATH_FUNCTION_NAMES } from './functions.js';
|
|
17
|
+
import { clampResolvedValueToPropertyRange } from './property-ranges.js';
|
|
18
|
+
import {
|
|
19
|
+
convertReciprocalMultiplicationToDivision,
|
|
20
|
+
rewriteMathFunctionContents,
|
|
21
|
+
shortenDecimalNumbers
|
|
22
|
+
} from './rewrite.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Characters that start a token able to sit directly against a value that
|
|
26
|
+
* precedes them, so no whitespace is needed to keep the two tokens apart:
|
|
27
|
+
* whitespace already separates, and openers like `#`, `!`, or any bracket or
|
|
28
|
+
* punctuation mark their own token's start. Mirrors the tokenizer's notion of
|
|
29
|
+
* tokens marking their own start, with `/` on top since it delimits a size
|
|
30
|
+
* from a position in layered shorthands.
|
|
31
|
+
*
|
|
32
|
+
* @type {Set<string>}
|
|
33
|
+
*/
|
|
34
|
+
const SELF_DELIMITING_FOLLOWERS = new Set([
|
|
35
|
+
' ',
|
|
36
|
+
'\t',
|
|
37
|
+
'\n',
|
|
38
|
+
'\r',
|
|
39
|
+
'\f',
|
|
40
|
+
'#',
|
|
41
|
+
'!',
|
|
42
|
+
'"',
|
|
43
|
+
'\'',
|
|
44
|
+
'(',
|
|
45
|
+
')',
|
|
46
|
+
'[',
|
|
47
|
+
']',
|
|
48
|
+
'{',
|
|
49
|
+
'}',
|
|
50
|
+
',',
|
|
51
|
+
':',
|
|
52
|
+
';',
|
|
53
|
+
'/'
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Reports whether a math function's replacement glues itself to the character
|
|
58
|
+
* following the matched function, merging two values into one token. A
|
|
59
|
+
* replacement that keeps its function form ends with a parenthesis, which
|
|
60
|
+
* closes itself; only a bare resolved value needs the whitespace that the
|
|
61
|
+
* parenthesis padding removal took away.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} replacement The string the math function resolved to.
|
|
64
|
+
* @param {string} followingCharacter The character directly after the matched function.
|
|
65
|
+
* @return {boolean} Whether a separating space must be restored.
|
|
66
|
+
*/
|
|
67
|
+
function mergesWithFollowingCharacter (replacement, followingCharacter) {
|
|
68
|
+
if (replacement.endsWith(')') || !followingCharacter) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
return !SELF_DELIMITING_FOLLOWERS.has(followingCharacter);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Wraps a math-function replacer so a function that dissolves into a bare
|
|
76
|
+
* value does not lose the whitespace separating it from the value behind it.
|
|
77
|
+
* The values were once told apart by where the function's parentheses sat, so
|
|
78
|
+
* when the parentheses go, the separator has to stay.
|
|
79
|
+
*
|
|
80
|
+
* @param {function(string, ...(string|number|undefined)): string} replacer The replacement callback for `String.prototype.replace`.
|
|
81
|
+
* @return {function(string, ...(string|number|undefined)): string} A replacer that keeps the trailing separator intact.
|
|
82
|
+
*/
|
|
83
|
+
function keepSeparatorAfterDissolvedFunction (replacer) {
|
|
84
|
+
return (match, ...rest) => {
|
|
85
|
+
const replacement = replacer(match, ...rest);
|
|
86
|
+
// A replace callback's trailing arguments are the match offset and the full string
|
|
87
|
+
const fullText = rest[rest.length - 1];
|
|
88
|
+
const matchOffset = rest[rest.length - 2];
|
|
89
|
+
const followingCharacter = fullText[matchOffset + match.length];
|
|
90
|
+
if (mergesWithFollowingCharacter(replacement, followingCharacter)) {
|
|
91
|
+
return replacement + ' ';
|
|
92
|
+
}
|
|
93
|
+
return replacement;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Matches a math function other than `calc()` whose arguments hold no further
|
|
99
|
+
* parentheses, so that the whole of the function is matched. A `calc()` is
|
|
100
|
+
* left to its own pass, which weighs what the calculation resolves to against
|
|
101
|
+
* the calculation itself.
|
|
102
|
+
*
|
|
103
|
+
* @type {RegExp}
|
|
104
|
+
*/
|
|
105
|
+
const RESOLVABLE_MATH_FUNCTION = new RegExp(
|
|
106
|
+
'\\b(?:' +
|
|
107
|
+
[...MATH_FUNCTION_NAMES].filter((name) => {
|
|
108
|
+
return name !== 'calc';
|
|
109
|
+
}).join('|') +
|
|
110
|
+
')\\([^()]+\\)',
|
|
111
|
+
'gi'
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Runs one pass of the math simplifications over a value: the rewrites that
|
|
116
|
+
* state a calculation more compactly, and the resolutions that replace a math
|
|
117
|
+
* function with the value it works out to.
|
|
118
|
+
*
|
|
119
|
+
* @param {string} value The CSS value string containing math functions to simplify.
|
|
120
|
+
* @param {string} property The CSS property name, whose range every resolved result is clamped into.
|
|
121
|
+
* @return {string} The value with its math functions simplified where possible.
|
|
122
|
+
*/
|
|
123
|
+
function simplifyMathFunctionsOnce (value, property) {
|
|
124
|
+
let result = rewriteMathFunctionContents(shortenDecimalNumbers, value);
|
|
125
|
+
result = rewriteMathFunctionContents(convertReciprocalMultiplicationToDivision, result);
|
|
126
|
+
|
|
127
|
+
// Unwrap calc(1 / (1 / x)) → x (double-reciprocal identity)
|
|
128
|
+
result = result.replace(/calc\(\s*1\s*\/\s*\(\s*1\s*\/\s*([^()]+)\s*\)\s*\)/gi, keepSeparatorAfterDissolvedFunction((match, inner) => {
|
|
129
|
+
return inner.trim();
|
|
130
|
+
}));
|
|
131
|
+
// Flatten calc(calc(a) ± b) → calc(a ± b)
|
|
132
|
+
result = result.replace(/calc\(\s*calc\(([^()]+)\)\s*([+-])\s*([^()]+)\s*\)/gi, keepSeparatorAfterDissolvedFunction((match, inner, operator, tail) => {
|
|
133
|
+
return 'calc(' + inner + ' ' + operator + ' ' + tail + ')';
|
|
134
|
+
}));
|
|
135
|
+
// Unwrap calc(calc(x)) → calc(x)
|
|
136
|
+
result = result.replace(/calc\(\s*calc\(([^()]+)\)\s*\)/gi, keepSeparatorAfterDissolvedFunction((match, inner) => {
|
|
137
|
+
return 'calc(' + inner + ')';
|
|
138
|
+
}));
|
|
139
|
+
|
|
140
|
+
// Resolve every math function that compares or rounds its arguments
|
|
141
|
+
result = result.replace(RESOLVABLE_MATH_FUNCTION, keepSeparatorAfterDissolvedFunction((match) => {
|
|
142
|
+
const simplified = simplifyCalc(match);
|
|
143
|
+
if (simplified === null) {
|
|
144
|
+
return match;
|
|
145
|
+
}
|
|
146
|
+
return clampResolvedValueToPropertyRange(simplified, property);
|
|
147
|
+
}));
|
|
148
|
+
|
|
149
|
+
// Simplify calc() expressions using constant folding and expression resolution
|
|
150
|
+
result = result.replace(/calc\(([^()]+)\)/gi, keepSeparatorAfterDissolvedFunction((match, inner) => {
|
|
151
|
+
// Collapse whitespace inside calc expression
|
|
152
|
+
const compactInner = inner.replace(/\s+/g, ' ').trim();
|
|
153
|
+
const resolved = resolveCalcExpression(match, compactInner);
|
|
154
|
+
return clampResolvedValueToPropertyRange(resolved, property);
|
|
155
|
+
}));
|
|
156
|
+
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Simplifies every math function within a CSS value string, resolving the ones
|
|
162
|
+
* whose operands are all known and stating the rest as compactly as they can
|
|
163
|
+
* be written.
|
|
164
|
+
*
|
|
165
|
+
* @param {string} value The CSS value string containing math functions to simplify.
|
|
166
|
+
* @param {string} property The CSS property name, whose range every resolved result is clamped into.
|
|
167
|
+
* @param {string} originalValue The value as it was authored, which tells whether a result was ever part of a calculation.
|
|
168
|
+
* @return {string} The value with math functions simplified where possible.
|
|
169
|
+
*/
|
|
170
|
+
function normalizeMathFunctions (value, property, originalValue = '') {
|
|
171
|
+
let result = value;
|
|
172
|
+
let previous;
|
|
173
|
+
|
|
174
|
+
do {
|
|
175
|
+
previous = result;
|
|
176
|
+
// Resolving a math function leaves a plain value where it stood, which can
|
|
177
|
+
// bring the function it was nested in within reach of the next pass, so
|
|
178
|
+
// the value is simplified until it settles
|
|
179
|
+
result = simplifyMathFunctionsOnce(result, property);
|
|
180
|
+
} while (result !== previous);
|
|
181
|
+
|
|
182
|
+
// When calc() folded to an absolute-length unit (pt, pc, in, cm, mm, q), convert to pixels
|
|
183
|
+
if (originalValue.includes('calc(') && /^-?(?:\d+|\d*\.\d+)(pt|pc|in|cm|mm|q)$/i.test(result)) {
|
|
184
|
+
// Extract the absolute-length unit from the folded result
|
|
185
|
+
const [, unit] = result.match(/^-?(?:\d+|\d*\.\d+)(pt|pc|in|cm|mm|q)$/i);
|
|
186
|
+
const numeric = parseFloat(result);
|
|
187
|
+
const pxValue = convertAbsoluteLengthToPx(numeric, unit);
|
|
188
|
+
if (pxValue !== null) {
|
|
189
|
+
result = roundCompactNumber(pxValue) + 'px';
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Round a result whose decimal places run past the budget a resolved number is written within
|
|
194
|
+
result = result.replace(/(-?(?:\d+|\d*\.\d+)\.\d{4,})([a-z%]+)/gi, (match, number, unit) => {
|
|
195
|
+
return formatResolvedNumber(number) + unit;
|
|
196
|
+
});
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Simplifies a value that is a single `calc()`, holding whatever it resolves
|
|
202
|
+
* to inside the range the property it is declared on accepts.
|
|
203
|
+
*
|
|
204
|
+
* @param {string} value The CSS value string that may be a standalone calc() expression.
|
|
205
|
+
* @param {string} property The CSS property the value is declared on.
|
|
206
|
+
* @return {string} The simplified value, or the original value if simplification is not applicable.
|
|
207
|
+
*/
|
|
208
|
+
function simplifyStandaloneCalc (value, property = '') {
|
|
209
|
+
// Check if value starts with calc( and ends with )
|
|
210
|
+
if (!/^calc\(/i.test(value) || !value.endsWith(')')) {
|
|
211
|
+
return value;
|
|
212
|
+
}
|
|
213
|
+
return clampResolvedValueToPropertyRange(foldStandaloneCalc(value), property);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export {
|
|
217
|
+
normalizeMathFunctions,
|
|
218
|
+
simplifyStandaloneCalc
|
|
219
|
+
};
|