@thejaredwilcurt/csslop 0.0.39 → 0.0.40
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/package.json +9 -9
- package/src/rules/custom-properties.js +86 -9
- package/src/rules/optimize.js +88 -1
- package/src/value/math.js +268 -53
- package/src/value/minify.js +5 -3
- package/src/value/shared.js +52 -10
package/package.json
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
"name": "@thejaredwilcurt/csslop",
|
|
3
3
|
"main": "index.js",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.0.
|
|
5
|
+
"version": "0.0.40",
|
|
6
6
|
"description": "Experimental CSS minification",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"prestart": "node ./scripts/
|
|
8
|
+
"prestart": "node ./scripts/pre/start.js",
|
|
9
9
|
"start": "vite",
|
|
10
10
|
"preview": "vite preview",
|
|
11
|
-
"prebuild": "node ./scripts/
|
|
11
|
+
"prebuild": "node ./scripts/pre/build.js",
|
|
12
12
|
"build": "vite build",
|
|
13
|
-
"postbuild": "node ./scripts/
|
|
13
|
+
"postbuild": "node ./scripts/post/build.js",
|
|
14
14
|
"copy": "node ./scripts/copyTests.js",
|
|
15
15
|
"test": "node ./tests/index.test.js",
|
|
16
16
|
"real": "node ./tests/realworld.test.js",
|
|
@@ -28,17 +28,17 @@
|
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@codemirror/autocomplete": "^6.20.3",
|
|
30
30
|
"@codemirror/lang-css": "^6.3.1",
|
|
31
|
-
"@codemirror/view": "^6.43.
|
|
31
|
+
"@codemirror/view": "^6.43.13",
|
|
32
32
|
"@eslint/js": "^10.0.1",
|
|
33
33
|
"@node-projects/css-parser": "^5.2.0",
|
|
34
34
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
35
35
|
"codemirror": "^6.0.2",
|
|
36
|
-
"eslint": "^10.
|
|
36
|
+
"eslint": "^10.11.0",
|
|
37
37
|
"eslint-config-tjw-base": "^5.0.0",
|
|
38
38
|
"eslint-config-tjw-import-x": "^1.0.1",
|
|
39
39
|
"eslint-config-tjw-jsdoc": "^2.0.1",
|
|
40
40
|
"eslint-plugin-import-x": "^4.17.0",
|
|
41
|
-
"eslint-plugin-jsdoc": "^64.5.
|
|
41
|
+
"eslint-plugin-jsdoc": "^64.5.4",
|
|
42
42
|
"fflate": "^0.8.3",
|
|
43
43
|
"globals": "^17.12.0",
|
|
44
44
|
"pretty-ms": "^9.3.1",
|
|
@@ -60,11 +60,11 @@
|
|
|
60
60
|
"devEngines": {
|
|
61
61
|
"runtime": {
|
|
62
62
|
"name": "node",
|
|
63
|
-
"version": "26.
|
|
63
|
+
"version": "26.10.0"
|
|
64
64
|
},
|
|
65
65
|
"packageManager": {
|
|
66
66
|
"name": "npm",
|
|
67
|
-
"version": "12.0
|
|
67
|
+
"version": "12.1.0"
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
}
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
* @file Custom property value whitespace and comment processing for CSS minification.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { tokenizeCssValue } from '../parser/tokenizer.js';
|
|
6
|
+
import { formatCompactNumber } from '../value/shared.js';
|
|
7
|
+
|
|
5
8
|
/**
|
|
6
9
|
* Removes spaces after commas only inside parenthesized groups (function
|
|
7
10
|
* calls like `var()`, `calc()`), leaving top-level comma spacing intact.
|
|
@@ -34,15 +37,89 @@ function removeSpacesAfterCommasInsideFunctions (value) {
|
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
40
|
+
* The token types whose text begins with a number, and so may hold digits that
|
|
41
|
+
* are written out longer than they need to be.
|
|
42
|
+
*
|
|
43
|
+
* @type {Set<string>}
|
|
44
|
+
*/
|
|
45
|
+
const NUMERIC_TOKEN_TYPES = new Set(['number', 'dimension', 'percentage']);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Splits a numeric token into the decimal number it states and the unit
|
|
49
|
+
* written on it. A number carrying a scientific exponent does not match, since
|
|
50
|
+
* its digits cannot be restated without reading the exponent along with them,
|
|
51
|
+
* and neither does a whole number, which has no zeros to shed.
|
|
52
|
+
*
|
|
53
|
+
* @type {RegExp}
|
|
54
|
+
*/
|
|
55
|
+
const DECIMAL_NUMBER_TOKEN = /^([+-]?(?:\d+\.\d+|\.\d+))([a-zA-Z%]*)$/;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Restates a numeric token as the shortest way of writing the same number,
|
|
59
|
+
* dropping the zeros that lead the decimal point and the ones that trail the
|
|
60
|
+
* digits behind it (`-0.020em` becomes `-.02em`). A token that is not a plain
|
|
61
|
+
* decimal, or whose number is only expressible in scientific notation, is left
|
|
62
|
+
* exactly as it was written.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} tokenText The text of a number, dimension, or percentage token.
|
|
65
|
+
* @return {string} The shortest way of writing that same token.
|
|
66
|
+
*/
|
|
67
|
+
function shortenNumericToken (tokenText) {
|
|
68
|
+
const decimalNumber = tokenText.match(DECIMAL_NUMBER_TOKEN);
|
|
69
|
+
if (!decimalNumber) {
|
|
70
|
+
return tokenText;
|
|
71
|
+
}
|
|
72
|
+
const [, number, unit] = decimalNumber;
|
|
73
|
+
const shortened = formatCompactNumber(number);
|
|
74
|
+
// A number too small or too large to write out plainly comes back in
|
|
75
|
+
// scientific notation, which reads as a different token once a unit follows
|
|
76
|
+
if (shortened.includes('e')) {
|
|
77
|
+
return tokenText;
|
|
78
|
+
}
|
|
79
|
+
return shortened + unit;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Reports whether two token lists say the same thing, which is what tells a
|
|
84
|
+
* rewritten value apart from one whose tokens have run together. Shortening
|
|
85
|
+
* the numbers of `1.0.0` would leave `10`, a single number where two stood.
|
|
86
|
+
*
|
|
87
|
+
* @param {Array} tokens The tokens the value was read as.
|
|
88
|
+
* @param {Array} rewrittenTokens The tokens the rewritten value reads as.
|
|
89
|
+
* @return {boolean} Whether the rewrite left every token boundary in place.
|
|
90
|
+
*/
|
|
91
|
+
function hasSameTokenBoundaries (tokens, rewrittenTokens) {
|
|
92
|
+
if (tokens.length !== rewrittenTokens.length) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
return tokens.every((token, index) => {
|
|
96
|
+
return token.type === rewrittenTokens[index].type;
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Writes every number in a custom property value the shortest way it can be
|
|
102
|
+
* written. A custom property holds an arbitrary token sequence, so the value
|
|
103
|
+
* is walked token by token: only the tokens that state a number are rewritten,
|
|
104
|
+
* leaving strings, urls, and hash tokens exactly as the author wrote them.
|
|
39
105
|
*
|
|
40
106
|
* @param {string} value The custom property value string.
|
|
41
|
-
* @return {string} The value with
|
|
107
|
+
* @return {string} The value with its numbers shortened.
|
|
42
108
|
*/
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
|
|
109
|
+
function shortenNumbers (value) {
|
|
110
|
+
const tokens = tokenizeCssValue(value);
|
|
111
|
+
const rewritten = tokens
|
|
112
|
+
.map((token) => {
|
|
113
|
+
if (!NUMERIC_TOKEN_TYPES.has(token.type)) {
|
|
114
|
+
return token.text;
|
|
115
|
+
}
|
|
116
|
+
return shortenNumericToken(token.text);
|
|
117
|
+
})
|
|
118
|
+
.join('');
|
|
119
|
+
if (!hasSameTokenBoundaries(tokens, tokenizeCssValue(rewritten))) {
|
|
120
|
+
return value;
|
|
121
|
+
}
|
|
122
|
+
return rewritten;
|
|
46
123
|
}
|
|
47
124
|
|
|
48
125
|
/**
|
|
@@ -69,7 +146,7 @@ function processCustomPropertyComments (value) {
|
|
|
69
146
|
* Collapses whitespace in a custom property value while preserving
|
|
70
147
|
* token boundaries. Each whitespace sequence is reduced to a single
|
|
71
148
|
* space, spaces after commas inside function calls are removed, and
|
|
72
|
-
*
|
|
149
|
+
* every number is written the shortest way it can be.
|
|
73
150
|
*
|
|
74
151
|
* @param {string} value The raw custom property value string.
|
|
75
152
|
* @return {string} The minified custom property value.
|
|
@@ -79,8 +156,8 @@ function collapseCustomPropertyWhitespace (value) {
|
|
|
79
156
|
let collapsed = value.replace(/\s+/g, ' ');
|
|
80
157
|
// Remove spaces after commas only inside function calls (e.g. var(--bar, 1.5) → var(--bar,1.5))
|
|
81
158
|
collapsed = removeSpacesAfterCommasInsideFunctions(collapsed);
|
|
82
|
-
//
|
|
83
|
-
collapsed =
|
|
159
|
+
// Shorten decimals (e.g. 0.5 → .5, -0.020em → -.02em)
|
|
160
|
+
collapsed = shortenNumbers(collapsed);
|
|
84
161
|
return collapsed;
|
|
85
162
|
}
|
|
86
163
|
export {
|
package/src/rules/optimize.js
CHANGED
|
@@ -1351,6 +1351,74 @@ const MERGE_AT_LATER_RULE = 'later';
|
|
|
1351
1351
|
*/
|
|
1352
1352
|
const MERGE_AT_EARLIER_RULE = 'earlier';
|
|
1353
1353
|
|
|
1354
|
+
/**
|
|
1355
|
+
* Reports whether an entry of a rule's body is a declaration whose standing in
|
|
1356
|
+
* the cascade can be read off the property it writes. Anything else a body may
|
|
1357
|
+
* hold, such as a nested rule, is never shadowed by a declaration beside it.
|
|
1358
|
+
*
|
|
1359
|
+
* @param {object} entry An entry of a rule's declaration list.
|
|
1360
|
+
* @return {boolean} True when the entry is a property declaration.
|
|
1361
|
+
*/
|
|
1362
|
+
function isPropertyDeclaration (entry) {
|
|
1363
|
+
return entry.type === 'declaration' && Boolean(entry.property);
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
/**
|
|
1367
|
+
* Finds which declaration decides each leaf property once a list of
|
|
1368
|
+
* declarations is read as one rule's body. Within a rule, an important
|
|
1369
|
+
* declaration beats a non-important one, and between two of equal weight the
|
|
1370
|
+
* last one written wins.
|
|
1371
|
+
*
|
|
1372
|
+
* @param {Array} declarations The declarations of the combined rule, in the order they would be written.
|
|
1373
|
+
* @return {Map} Map of leaf property name to the declaration that decides it.
|
|
1374
|
+
*/
|
|
1375
|
+
function findDecidingDeclarations (declarations) {
|
|
1376
|
+
const decidingDeclarations = new Map();
|
|
1377
|
+
const importantDeclarations = new Set();
|
|
1378
|
+
for (const declaration of declarations) {
|
|
1379
|
+
if (!isPropertyDeclaration(declaration)) {
|
|
1380
|
+
continue;
|
|
1381
|
+
}
|
|
1382
|
+
const isImportant = isImportantDeclaration(declaration);
|
|
1383
|
+
if (isImportant) {
|
|
1384
|
+
importantDeclarations.add(declaration);
|
|
1385
|
+
}
|
|
1386
|
+
for (const leafProperty of expandToOverridableProperties(declaration.property)) {
|
|
1387
|
+
const decidingDeclaration = decidingDeclarations.get(leafProperty);
|
|
1388
|
+
const outranksIt = (
|
|
1389
|
+
!decidingDeclaration ||
|
|
1390
|
+
isImportant ||
|
|
1391
|
+
!importantDeclarations.has(decidingDeclaration)
|
|
1392
|
+
);
|
|
1393
|
+
if (outranksIt) {
|
|
1394
|
+
decidingDeclarations.set(leafProperty, declaration);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
return decidingDeclarations;
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
/**
|
|
1402
|
+
* Reports whether anything a rule brings to a merge still decides a property
|
|
1403
|
+
* of the combined rule. A declaration that loses every property it writes to
|
|
1404
|
+
* another declaration of the same rule is dropped from the merged rule, so
|
|
1405
|
+
* where the merged rule sits cannot change how it reads.
|
|
1406
|
+
*
|
|
1407
|
+
* @param {object} rule The rule contributing the entries.
|
|
1408
|
+
* @param {Map} decidingDeclarations The deciding declaration of each leaf property of the merged rule.
|
|
1409
|
+
* @return {boolean} True when the rule still decides something after the merge.
|
|
1410
|
+
*/
|
|
1411
|
+
function decidesAnythingAfterMerge (rule, decidingDeclarations) {
|
|
1412
|
+
return (rule.declarations || []).some((entry) => {
|
|
1413
|
+
if (!isPropertyDeclaration(entry)) {
|
|
1414
|
+
return entry.type !== 'whitespace' && entry.type !== 'comment';
|
|
1415
|
+
}
|
|
1416
|
+
return [...expandToOverridableProperties(entry.property)].some((leafProperty) => {
|
|
1417
|
+
return decidingDeclarations.get(leafProperty) === entry;
|
|
1418
|
+
});
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1354
1422
|
/**
|
|
1355
1423
|
* Decides where two rules with the same selector can be combined. Merging them
|
|
1356
1424
|
* always makes one set of declarations cross whatever separates the two rules,
|
|
@@ -1358,6 +1426,12 @@ const MERGE_AT_EARLIER_RULE = 'earlier';
|
|
|
1358
1426
|
* the merged rule goes wherever the set that moved keeps its old standing.
|
|
1359
1427
|
* Nothing separates adjacent rules, so those always merge.
|
|
1360
1428
|
*
|
|
1429
|
+
* The declarations one rule loses to the other are the exception: they are
|
|
1430
|
+
* dropped from the merged rule, so the merged rule belongs wherever the
|
|
1431
|
+
* declarations that outlive the merge already were, and nothing has to cross
|
|
1432
|
+
* anything at all. That is what lets `.a{color:red!important}` swallow a later
|
|
1433
|
+
* `.a{color:tan}` across a rule that also writes `color`.
|
|
1434
|
+
*
|
|
1361
1435
|
* @param {object} overrideIndex The index of what each position writes to.
|
|
1362
1436
|
* @param {object} earlierRule The first of the two rules with this selector.
|
|
1363
1437
|
* @param {object} laterRule The second of the two rules with this selector.
|
|
@@ -1367,7 +1441,20 @@ const MERGE_AT_EARLIER_RULE = 'earlier';
|
|
|
1367
1441
|
*/
|
|
1368
1442
|
function chooseMergePlacement (overrideIndex, earlierRule, laterRule, earlierPosition, laterPosition) {
|
|
1369
1443
|
const rulesAreAdjacent = earlierPosition === laterPosition - 1;
|
|
1370
|
-
if (rulesAreAdjacent
|
|
1444
|
+
if (rulesAreAdjacent) {
|
|
1445
|
+
return MERGE_AT_LATER_RULE;
|
|
1446
|
+
}
|
|
1447
|
+
const decidingDeclarations = findDecidingDeclarations([
|
|
1448
|
+
...(earlierRule.declarations || []),
|
|
1449
|
+
...(laterRule.declarations || [])
|
|
1450
|
+
]);
|
|
1451
|
+
if (!decidesAnythingAfterMerge(laterRule, decidingDeclarations)) {
|
|
1452
|
+
return MERGE_AT_EARLIER_RULE;
|
|
1453
|
+
}
|
|
1454
|
+
if (!decidesAnythingAfterMerge(earlierRule, decidingDeclarations)) {
|
|
1455
|
+
return MERGE_AT_LATER_RULE;
|
|
1456
|
+
}
|
|
1457
|
+
if (!overrideIndex.blocksRelocation(earlierRule, earlierPosition)) {
|
|
1371
1458
|
return MERGE_AT_LATER_RULE;
|
|
1372
1459
|
}
|
|
1373
1460
|
if (!overrideIndex.blocksRelocation(laterRule, earlierPosition)) {
|
package/src/value/math.js
CHANGED
|
@@ -8,7 +8,8 @@ import { findMatchingParenthesis } from '../parser/source-search.js';
|
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
10
|
convertAbsoluteLengthToPx,
|
|
11
|
-
|
|
11
|
+
formatResolvedDimension,
|
|
12
|
+
formatResolvedNumber,
|
|
12
13
|
roundCompactNumber
|
|
13
14
|
} from './shared.js';
|
|
14
15
|
|
|
@@ -245,6 +246,158 @@ function convertReciprocalMultiplicationToDivision (expression) {
|
|
|
245
246
|
});
|
|
246
247
|
}
|
|
247
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Matches a whole expression that divides a percentage by a plain number,
|
|
251
|
+
* capturing the percentage and the divisor it is shared out between.
|
|
252
|
+
*
|
|
253
|
+
* @type {RegExp}
|
|
254
|
+
*/
|
|
255
|
+
const PERCENTAGE_DIVISION = /^([+-]?(?:\d+|\d*\.\d+))%\s*\/\s*([+-]?(?:\d+|\d*\.\d+))$/;
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Picks between what a calculation resolved to and the calculation itself,
|
|
259
|
+
* keeping whichever takes fewer characters to write. A tie goes to the
|
|
260
|
+
* resolved value, which states the same thing without the arithmetic.
|
|
261
|
+
*
|
|
262
|
+
* @param {string} resolved The value the calculation resolves to.
|
|
263
|
+
* @param {string} calculation The calculation, written as compactly as it can be.
|
|
264
|
+
* @return {string} The shorter of the two.
|
|
265
|
+
*/
|
|
266
|
+
function shorterOfResolutionAndCalculation (resolved, calculation) {
|
|
267
|
+
if (resolved.length <= calculation.length) {
|
|
268
|
+
return resolved;
|
|
269
|
+
}
|
|
270
|
+
return calculation;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Resolves a percentage divided by a number into the single percentage it
|
|
275
|
+
* equals, which is how `calc(100%/16)` comes to be written as `6.25%`. The
|
|
276
|
+
* division stays as it was written when the result is no shorter, and when
|
|
277
|
+
* the divisor is zero, since that resolves to nothing a stylesheet can state.
|
|
278
|
+
*
|
|
279
|
+
* @param {string} expression The contents of a `calc()`, with its whitespace collapsed.
|
|
280
|
+
* @return {string|null} The shorter of the resolved percentage and the division, or null when the expression is not one.
|
|
281
|
+
*/
|
|
282
|
+
function resolvePercentageDivision (expression) {
|
|
283
|
+
const division = expression.match(PERCENTAGE_DIVISION);
|
|
284
|
+
if (!division) {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
const [, percentage, divisor] = division;
|
|
288
|
+
const calculation = 'calc(' + percentage + '%/' + divisor + ')';
|
|
289
|
+
if (Number(divisor) === 0) {
|
|
290
|
+
return calculation;
|
|
291
|
+
}
|
|
292
|
+
const resolved = formatResolvedNumber(Number(percentage) / Number(divisor)) + '%';
|
|
293
|
+
return shorterOfResolutionAndCalculation(resolved, calculation);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* The properties whose values every CSS specification defines as
|
|
298
|
+
* non-negative, so that a math function declared on one of them has its
|
|
299
|
+
* result clamped rather than read as written.
|
|
300
|
+
*
|
|
301
|
+
* @type {Set<string>}
|
|
302
|
+
*/
|
|
303
|
+
const NON_NEGATIVE_PROPERTIES = new Set([
|
|
304
|
+
'animation-duration',
|
|
305
|
+
'animation-iteration-count',
|
|
306
|
+
'background-size',
|
|
307
|
+
'block-size',
|
|
308
|
+
'border-block-end-width',
|
|
309
|
+
'border-block-start-width',
|
|
310
|
+
'border-block-width',
|
|
311
|
+
'border-bottom-left-radius',
|
|
312
|
+
'border-bottom-right-radius',
|
|
313
|
+
'border-bottom-width',
|
|
314
|
+
'border-end-end-radius',
|
|
315
|
+
'border-end-start-radius',
|
|
316
|
+
'border-image-outset',
|
|
317
|
+
'border-image-width',
|
|
318
|
+
'border-inline-end-width',
|
|
319
|
+
'border-inline-start-width',
|
|
320
|
+
'border-inline-width',
|
|
321
|
+
'border-left-width',
|
|
322
|
+
'border-radius',
|
|
323
|
+
'border-right-width',
|
|
324
|
+
'border-start-end-radius',
|
|
325
|
+
'border-start-start-radius',
|
|
326
|
+
'border-top-left-radius',
|
|
327
|
+
'border-top-right-radius',
|
|
328
|
+
'border-top-width',
|
|
329
|
+
'border-width',
|
|
330
|
+
'column-count',
|
|
331
|
+
'column-gap',
|
|
332
|
+
'column-rule-width',
|
|
333
|
+
'column-width',
|
|
334
|
+
'flex-basis',
|
|
335
|
+
'flex-grow',
|
|
336
|
+
'flex-shrink',
|
|
337
|
+
'font-size',
|
|
338
|
+
'gap',
|
|
339
|
+
'height',
|
|
340
|
+
'inline-size',
|
|
341
|
+
'line-height',
|
|
342
|
+
'mask-size',
|
|
343
|
+
'max-block-size',
|
|
344
|
+
'max-height',
|
|
345
|
+
'max-inline-size',
|
|
346
|
+
'max-width',
|
|
347
|
+
'min-block-size',
|
|
348
|
+
'min-height',
|
|
349
|
+
'min-inline-size',
|
|
350
|
+
'min-width',
|
|
351
|
+
'outline-width',
|
|
352
|
+
'padding',
|
|
353
|
+
'padding-block',
|
|
354
|
+
'padding-block-end',
|
|
355
|
+
'padding-block-start',
|
|
356
|
+
'padding-bottom',
|
|
357
|
+
'padding-inline',
|
|
358
|
+
'padding-inline-end',
|
|
359
|
+
'padding-inline-start',
|
|
360
|
+
'padding-left',
|
|
361
|
+
'padding-right',
|
|
362
|
+
'padding-top',
|
|
363
|
+
'perspective',
|
|
364
|
+
'row-gap',
|
|
365
|
+
'shape-margin',
|
|
366
|
+
'stroke-width',
|
|
367
|
+
'tab-size',
|
|
368
|
+
'transition-duration',
|
|
369
|
+
'width'
|
|
370
|
+
]);
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Matches a value written as a single negative number, with or without the
|
|
374
|
+
* unit or percent sign that follows its digits.
|
|
375
|
+
*
|
|
376
|
+
* @type {RegExp}
|
|
377
|
+
*/
|
|
378
|
+
const NEGATIVE_NUMBER_VALUE = /^-(?:\d+|\d*\.\d+)(?:[a-z]+|%)?$/i;
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Clamps what a math function resolved to into the range its property
|
|
382
|
+
* accepts. A negative length written out directly makes the declaration
|
|
383
|
+
* invalid and the CSS engine throws it away, but a math function is allowed
|
|
384
|
+
* to resolve to one: its result is clamped to the nearest value the property
|
|
385
|
+
* does take, which on a property that only accepts non-negative values is
|
|
386
|
+
* zero, as the range checking section of the CSS Values specification
|
|
387
|
+
* describes: https://drafts.csswg.org/css-values/#calc-range.
|
|
388
|
+
*
|
|
389
|
+
* @param {string} resolvedValue The value the math function resolved to.
|
|
390
|
+
* @param {string} property The property the math function is declared on.
|
|
391
|
+
* @return {string} The resolved value, held inside the property's range.
|
|
392
|
+
*/
|
|
393
|
+
function clampResolvedValueToPropertyRange (resolvedValue, property) {
|
|
394
|
+
const acceptsNegativeValues = !NON_NEGATIVE_PROPERTIES.has(String(property).toLowerCase());
|
|
395
|
+
if (acceptsNegativeValues || !NEGATIVE_NUMBER_VALUE.test(resolvedValue)) {
|
|
396
|
+
return resolvedValue;
|
|
397
|
+
}
|
|
398
|
+
return '0';
|
|
399
|
+
}
|
|
400
|
+
|
|
248
401
|
/**
|
|
249
402
|
* The units a folded calc() expression leads with, in the order they are
|
|
250
403
|
* written. Every other unit follows them alphabetically.
|
|
@@ -276,15 +429,15 @@ function tryFoldCalcExpression (expression) {
|
|
|
276
429
|
expr = expr.replace(/\(([^()]+)\)/g, '$1');
|
|
277
430
|
// Fold: <number> * <number><unit> → computed result in same unit
|
|
278
431
|
expr = expr.replace(/(-?(?:\d*\.\d+|\d+))\s*\*\s*(-?(?:\d*\.\d+|\d+))(px|pt|pc|in|cm|mm|q|%)/gi, (match, a, b, unit) => {
|
|
279
|
-
return
|
|
432
|
+
return formatResolvedDimension(parseFloat(a) * parseFloat(b), unit);
|
|
280
433
|
});
|
|
281
434
|
// Fold: <number><unit> * <number> → computed result in same unit
|
|
282
435
|
expr = expr.replace(/(-?(?:\d*\.\d+|\d+))(px|pt|pc|in|cm|mm|q|%)\s*\*\s*(-?(?:\d*\.\d+|\d+))/gi, (match, a, unit, b) => {
|
|
283
|
-
return
|
|
436
|
+
return formatResolvedDimension(parseFloat(a) * parseFloat(b), unit);
|
|
284
437
|
});
|
|
285
438
|
// Fold: <number><unit> / <number> → computed result in same unit
|
|
286
439
|
expr = expr.replace(/(-?(?:\d*\.\d+|\d+))(px|pt|pc|in|cm|mm|q)\s*\/\s*(-?(?:\d*\.\d+|\d+))/gi, (match, value, unit, divisor) => {
|
|
287
|
-
return
|
|
440
|
+
return formatResolvedDimension(parseFloat(value) / parseFloat(divisor), unit);
|
|
288
441
|
});
|
|
289
442
|
// Collapse zero-with-unit terms (e.g. 0px, 0%) to plain 0 or remove them
|
|
290
443
|
expr = expr.replace(/(^|[+-])\s*0(?:px|pt|pc|in|cm|mm|q|%)\b/g, (match, sign) => {
|
|
@@ -361,26 +514,100 @@ function tryFoldCalcExpression (expression) {
|
|
|
361
514
|
|
|
362
515
|
if (outputTerms.length === 1) {
|
|
363
516
|
const { unit, value } = outputTerms[0];
|
|
364
|
-
|
|
365
|
-
return roundCompactNumber(value) + unit;
|
|
366
|
-
}
|
|
367
|
-
return roundCompactNumber(value);
|
|
517
|
+
return formatResolvedDimension(value, unit);
|
|
368
518
|
}
|
|
369
519
|
|
|
370
520
|
const [first, ...rest] = outputTerms;
|
|
371
|
-
let result =
|
|
521
|
+
let result = formatResolvedDimension(first.value, first.unit);
|
|
372
522
|
for (const term of rest) {
|
|
373
523
|
const sign = term.value < 0 ? '-' : '+';
|
|
374
|
-
result += ' ' + sign + ' ' +
|
|
524
|
+
result += ' ' + sign + ' ' + formatResolvedDimension(Math.abs(term.value), term.unit);
|
|
375
525
|
}
|
|
376
526
|
return 'calc(' + result + ')';
|
|
377
527
|
}
|
|
378
528
|
|
|
529
|
+
/**
|
|
530
|
+
* Matches a whole expression that scales a percentage by a plain number,
|
|
531
|
+
* written either way around. Both operands are already as short as they can
|
|
532
|
+
* be, so the expression is only ever squeezed, never resolved.
|
|
533
|
+
*
|
|
534
|
+
* @type {RegExp}
|
|
535
|
+
*/
|
|
536
|
+
const PERCENTAGE_MULTIPLICATION = /^(?:-?(?:\d*\.\d+|\d+)%\s*\*\s*-?(?:\d*\.\d+|\d+)|-?(?:\d*\.\d+|\d+)\s*\*\s*-?(?:\d*\.\d+|\d+)%)$/;
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Matches a value that is a single percentage, which is all a calculation
|
|
540
|
+
* leaves behind once every term of it has resolved into one.
|
|
541
|
+
*
|
|
542
|
+
* @type {RegExp}
|
|
543
|
+
*/
|
|
544
|
+
const RESOLVED_PERCENTAGE = /^-?(?:\d+|\d*\.\d+)%$/;
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Chooses how a calculation that shares a percentage out resolves. The
|
|
548
|
+
* library states the result to the full precision a float carries, which is
|
|
549
|
+
* both longer than the calculation it replaces and finer than a stylesheet
|
|
550
|
+
* has any use for, so the result is rounded into the budget a resolved number
|
|
551
|
+
* is written within and kept only when it is the shorter of the two.
|
|
552
|
+
*
|
|
553
|
+
* @param {string} simplified The result the calculation library returned.
|
|
554
|
+
* @param {string} compactInner The contents of the `calc()`, with its whitespace collapsed.
|
|
555
|
+
* @param {string} match The whole `calc()` as it was written.
|
|
556
|
+
* @return {string} How the resolved calculation is written.
|
|
557
|
+
*/
|
|
558
|
+
function chooseResolvedPercentage (simplified, compactInner, match) {
|
|
559
|
+
// A division sign written after a percentage, which is what the resolved percentage was shared out by
|
|
560
|
+
const dividesAPercentage = RESOLVED_PERCENTAGE.test(simplified) && /%\s*\//.test(match);
|
|
561
|
+
if (!dividesAPercentage) {
|
|
562
|
+
return simplified;
|
|
563
|
+
}
|
|
564
|
+
// Remove whitespace around division operator
|
|
565
|
+
const calculation = 'calc(' + compactInner.replace(/\s*\/\s*/g, '/') + ')';
|
|
566
|
+
const resolved = formatResolvedNumber(parseFloat(simplified)) + '%';
|
|
567
|
+
return shorterOfResolutionAndCalculation(resolved, calculation);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Resolves the contents of a `calc()` into the shortest way of writing the
|
|
572
|
+
* same value, which is either the value the arithmetic works out to or the
|
|
573
|
+
* calculation itself with the whitespace squeezed out of it.
|
|
574
|
+
*
|
|
575
|
+
* @param {string} match The whole `calc()` as it was written.
|
|
576
|
+
* @param {string} compactInner The contents of the `calc()`, with its whitespace collapsed.
|
|
577
|
+
* @return {string} The shortest way of writing what the calculation states.
|
|
578
|
+
*/
|
|
579
|
+
function resolveCalcExpression (match, compactInner) {
|
|
580
|
+
if (PERCENTAGE_MULTIPLICATION.test(compactInner)) {
|
|
581
|
+
// Remove whitespace around multiplication/division operators
|
|
582
|
+
return 'calc(' + compactInner.replace(/\s*([*/])\s*/g, '$1') + ')';
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
const dividedPercentage = resolvePercentageDivision(compactInner);
|
|
586
|
+
if (dividedPercentage) {
|
|
587
|
+
return dividedPercentage;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const folded = tryFoldCalcExpression(compactInner);
|
|
591
|
+
if (folded) {
|
|
592
|
+
return folded;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
try {
|
|
596
|
+
const simplified = calc(match);
|
|
597
|
+
if (typeof simplified !== 'string') {
|
|
598
|
+
return match;
|
|
599
|
+
}
|
|
600
|
+
return chooseResolvedPercentage(simplified, compactInner, match);
|
|
601
|
+
} catch {
|
|
602
|
+
return match;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
379
606
|
/**
|
|
380
607
|
* Simplifies calc(), min(), and max() expressions within a CSS value string using the `@csstools`/css-calc library, falling back to the original value on failure.
|
|
381
608
|
*
|
|
382
609
|
* @param {string} value The CSS value string containing math functions to simplify.
|
|
383
|
-
* @param {string} property The CSS property name,
|
|
610
|
+
* @param {string} property The CSS property name, whose range every resolved result is clamped into.
|
|
384
611
|
* @param {string} originalValue The original unmodified value to fall back to if simplification produces an invalid result.
|
|
385
612
|
* @return {string} The value with math functions simplified where possible.
|
|
386
613
|
*/
|
|
@@ -404,7 +631,10 @@ function normalizeMathFunctions (value, property, originalValue = '') {
|
|
|
404
631
|
result = result.replace(/\b(min|max)\(([^()]+)\)/gi, keepSeparatorAfterDissolvedFunction((match) => {
|
|
405
632
|
try {
|
|
406
633
|
const simplified = calc(match);
|
|
407
|
-
|
|
634
|
+
if (typeof simplified !== 'string') {
|
|
635
|
+
return match;
|
|
636
|
+
}
|
|
637
|
+
return clampResolvedValueToPropertyRange(simplified, property);
|
|
408
638
|
} catch {
|
|
409
639
|
return match;
|
|
410
640
|
}
|
|
@@ -414,35 +644,8 @@ function normalizeMathFunctions (value, property, originalValue = '') {
|
|
|
414
644
|
result = result.replace(/calc\(([^()]+)\)/gi, keepSeparatorAfterDissolvedFunction((match, inner) => {
|
|
415
645
|
// Collapse whitespace inside calc expression
|
|
416
646
|
const compactInner = inner.replace(/\s+/g, ' ').trim();
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
// Remove whitespace around multiplication/division operators
|
|
420
|
-
return 'calc(' + compactInner.replace(/\s*([*/])\s*/g, '$1') + ')';
|
|
421
|
-
}
|
|
422
|
-
// Preserve percent/number division expressions (e.g. 100%/3) — just strip inner spaces
|
|
423
|
-
if (/^\d+(?:\.\d+)?%\s*\/\s*\d+(?:\.\d+)?$/i.test(compactInner)) {
|
|
424
|
-
// Remove whitespace around division operator
|
|
425
|
-
return 'calc(' + compactInner.replace(/\s*\/\s*/g, '/') + ')';
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const folded = tryFoldCalcExpression(compactInner);
|
|
429
|
-
if (folded) {
|
|
430
|
-
return folded;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
try {
|
|
434
|
-
const simplified = calc(match);
|
|
435
|
-
if (typeof simplified !== 'string') {
|
|
436
|
-
return match;
|
|
437
|
-
}
|
|
438
|
-
// If simplified to a bare percentage but original had division, preserve the calc form
|
|
439
|
-
if (/^-?(?:\d+|\d*\.\d+)%$/.test(simplified) && /%\s*\//.test(match)) {
|
|
440
|
-
return 'calc(' + compactInner.replace(/\s*\/\s*/g, '/') + ')';
|
|
441
|
-
}
|
|
442
|
-
return simplified;
|
|
443
|
-
} catch {
|
|
444
|
-
return match;
|
|
445
|
-
}
|
|
647
|
+
const resolved = resolveCalcExpression(match, compactInner);
|
|
648
|
+
return clampResolvedValueToPropertyRange(resolved, property);
|
|
446
649
|
}));
|
|
447
650
|
|
|
448
651
|
// When calc() folded to an absolute-length unit (pt, pc, in, cm, mm, q), convert to pixels
|
|
@@ -456,9 +659,9 @@ function normalizeMathFunctions (value, property, originalValue = '') {
|
|
|
456
659
|
}
|
|
457
660
|
}
|
|
458
661
|
|
|
459
|
-
// Round
|
|
662
|
+
// Round a result whose decimal places run past the budget a resolved number is written within
|
|
460
663
|
result = result.replace(/(-?(?:\d+|\d*\.\d+)\.\d{4,})([a-z%]+)/gi, (match, number, unit) => {
|
|
461
|
-
return
|
|
664
|
+
return formatResolvedNumber(number) + unit;
|
|
462
665
|
});
|
|
463
666
|
return result;
|
|
464
667
|
}
|
|
@@ -466,18 +669,14 @@ function normalizeMathFunctions (value, property, originalValue = '') {
|
|
|
466
669
|
/**
|
|
467
670
|
* Simplifies a standalone calc() value by flattening nested calc expressions, converting absolute length units to pixels, and folding constant terms.
|
|
468
671
|
*
|
|
469
|
-
* @param {string} value The CSS value string
|
|
672
|
+
* @param {string} value The CSS value string, already known to be a single `calc()`.
|
|
470
673
|
* @return {string} The simplified value, or the original value if simplification is not applicable.
|
|
471
674
|
*/
|
|
472
|
-
function
|
|
473
|
-
// Check if value starts with calc( and ends with )
|
|
474
|
-
if (!/^calc\(/i.test(value) || !value.endsWith(')')) {
|
|
475
|
-
return value;
|
|
476
|
-
}
|
|
675
|
+
function foldStandaloneCalc (value) {
|
|
477
676
|
let inner = value.slice(5, -1).trim();
|
|
478
677
|
|
|
479
678
|
// Preserve percent-times-number expressions, only stripping whitespace around operators
|
|
480
|
-
if (
|
|
679
|
+
if (PERCENTAGE_MULTIPLICATION.test(inner.replace(/\s+/g, ' ').trim())) {
|
|
481
680
|
return 'calc(' + inner.replace(/\s*([*/])\s*/g, '$1') + ')';
|
|
482
681
|
}
|
|
483
682
|
|
|
@@ -516,15 +715,31 @@ function simplifyStandaloneCalc (value) {
|
|
|
516
715
|
|
|
517
716
|
// Collapse whitespace and check for percent-division expressions
|
|
518
717
|
const compactInner = inner.replace(/\s+/g, ' ').trim();
|
|
519
|
-
|
|
520
|
-
if (
|
|
521
|
-
return
|
|
718
|
+
const dividedPercentage = resolvePercentageDivision(compactInner);
|
|
719
|
+
if (dividedPercentage) {
|
|
720
|
+
return dividedPercentage;
|
|
522
721
|
}
|
|
523
722
|
|
|
524
723
|
// Default: strip whitespace around multiplication/division operators
|
|
525
724
|
return 'calc(' + compactInner.replace(/\s*([*/])\s*/g, '$1') + ')';
|
|
526
725
|
}
|
|
527
726
|
|
|
727
|
+
/**
|
|
728
|
+
* Simplifies a value that is a single `calc()`, holding whatever it resolves
|
|
729
|
+
* to inside the range the property it is declared on accepts.
|
|
730
|
+
*
|
|
731
|
+
* @param {string} value The CSS value string that may be a standalone calc() expression.
|
|
732
|
+
* @param {string} property The CSS property the value is declared on.
|
|
733
|
+
* @return {string} The simplified value, or the original value if simplification is not applicable.
|
|
734
|
+
*/
|
|
735
|
+
function simplifyStandaloneCalc (value, property = '') {
|
|
736
|
+
// Check if value starts with calc( and ends with )
|
|
737
|
+
if (!/^calc\(/i.test(value) || !value.endsWith(')')) {
|
|
738
|
+
return value;
|
|
739
|
+
}
|
|
740
|
+
return clampResolvedValueToPropertyRange(foldStandaloneCalc(value), property);
|
|
741
|
+
}
|
|
742
|
+
|
|
528
743
|
export {
|
|
529
744
|
normalizeMathFunctions,
|
|
530
745
|
simplifyStandaloneCalc
|
package/src/value/minify.js
CHANGED
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
import {
|
|
38
38
|
collapseShorthandParts,
|
|
39
39
|
convertAbsoluteLengthToPx,
|
|
40
|
+
formatResolvedNumber,
|
|
40
41
|
hasSubstitutedParts,
|
|
41
42
|
normalizeScaleComponent,
|
|
42
43
|
parseAlphaString,
|
|
@@ -1376,7 +1377,7 @@ function computeMinifiedValue (declaration) {
|
|
|
1376
1377
|
// Remove whitespace around * and / operators (safe outside calc context)
|
|
1377
1378
|
val = val.replace(/\s*([*/])\s*/g, '$1');
|
|
1378
1379
|
val = normalizeMathFunctions(val, declaration.property, declaration.value || '');
|
|
1379
|
-
val = simplifyStandaloneCalc(val);
|
|
1380
|
+
val = simplifyStandaloneCalc(val, declaration.property);
|
|
1380
1381
|
// Simplify calc() expressions containing zero-percent additive terms
|
|
1381
1382
|
val = val.replace(/calc\(([^()]+)\)/gi, (match, inner) => {
|
|
1382
1383
|
// Collapse whitespace inside calc expression
|
|
@@ -1400,10 +1401,11 @@ function computeMinifiedValue (declaration) {
|
|
|
1400
1401
|
}
|
|
1401
1402
|
val = val.replace(/(^|\s|,|\()(-?)0+(\.\d+)/g, '$1$2$3'); // e.g. 0.5 -> .5, -0.5 -> -.5
|
|
1402
1403
|
|
|
1403
|
-
// If value is a standalone number with optional unit, round it
|
|
1404
|
+
// If value is a standalone number with optional unit, round it into the
|
|
1405
|
+
// character budget a number is written within
|
|
1404
1406
|
if (/^[+-]?(?:\d+|\d*\.\d+)([a-z%]+)?$/i.test(val)) {
|
|
1405
1407
|
const [, rawNumber, rawUnit = ''] = val.match(/^([+-]?(?:\d+|\d*\.\d+))([a-z%]+)?$/i);
|
|
1406
|
-
val =
|
|
1408
|
+
val = formatResolvedNumber(rawNumber) + rawUnit;
|
|
1407
1409
|
}
|
|
1408
1410
|
|
|
1409
1411
|
val = lowercaseHexColors(val);
|
package/src/value/shared.js
CHANGED
|
@@ -47,8 +47,10 @@ function roundCompactNumber (value, precision = 3) {
|
|
|
47
47
|
if (!Number.isFinite(number)) {
|
|
48
48
|
return String(value);
|
|
49
49
|
}
|
|
50
|
-
// Strip
|
|
51
|
-
|
|
50
|
+
// Strip the zeros trailing the fraction, then the decimal point they leave
|
|
51
|
+
// behind. Only zeros written after a decimal point are droppable, so the
|
|
52
|
+
// match has to start at one: the zeros of a whole number are digits.
|
|
53
|
+
let result = number.toFixed(precision).replace(/(\.\d*?)0+$/, '$1').replace(/\.$/, '');
|
|
52
54
|
if (result.startsWith('0.')) {
|
|
53
55
|
result = result.slice(1);
|
|
54
56
|
}
|
|
@@ -58,6 +60,44 @@ function roundCompactNumber (value, precision = 3) {
|
|
|
58
60
|
return result;
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
/**
|
|
64
|
+
* The most characters the result of a resolved calculation is written with,
|
|
65
|
+
* counting the digits and the decimal point but not the sign. A calculation is
|
|
66
|
+
* only replaced by its result to save characters, and a result that repeats
|
|
67
|
+
* can always be written out longer, so its digits are cut off where the
|
|
68
|
+
* accuracy they add stops paying for the characters they cost.
|
|
69
|
+
*
|
|
70
|
+
* @type {number}
|
|
71
|
+
*/
|
|
72
|
+
const MAXIMUM_RESOLVED_NUMBER_CHARACTERS = 7;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Formats what a calculation resolved to as the number a stylesheet states it
|
|
76
|
+
* with: rounded to however many decimal places are left over once the whole
|
|
77
|
+
* part and the decimal point have taken their share of the character budget a
|
|
78
|
+
* resolved number is written within.
|
|
79
|
+
*
|
|
80
|
+
* @param {number|string} value The number the calculation resolved to.
|
|
81
|
+
* @param {number} maximumCharacters The most characters the written number may take.
|
|
82
|
+
* @return {string} The number as it is written into the stylesheet.
|
|
83
|
+
*/
|
|
84
|
+
function formatResolvedNumber (value, maximumCharacters = MAXIMUM_RESOLVED_NUMBER_CHARACTERS) {
|
|
85
|
+
const number = typeof value === 'number' ? value : parseFloat(value);
|
|
86
|
+
if (!Number.isFinite(number)) {
|
|
87
|
+
return String(value);
|
|
88
|
+
}
|
|
89
|
+
const wholePart = Math.floor(Math.abs(number));
|
|
90
|
+
// A magnitude below one is written without its leading zero, so nothing of
|
|
91
|
+
// its whole part is spent from the budget
|
|
92
|
+
let wholeDigitCount = 0;
|
|
93
|
+
if (wholePart !== 0) {
|
|
94
|
+
wholeDigitCount = String(wholePart).length;
|
|
95
|
+
}
|
|
96
|
+
// The decimal point itself takes one of the characters the digits could have
|
|
97
|
+
const decimalPlaces = Math.max(maximumCharacters - wholeDigitCount - 1, 0);
|
|
98
|
+
return roundCompactNumber(number, decimalPlaces);
|
|
99
|
+
}
|
|
100
|
+
|
|
61
101
|
/**
|
|
62
102
|
* Converts an absolute CSS length value (pt, pc, in, cm, mm, q) to its pixel equivalent using standard conversion factors.
|
|
63
103
|
*
|
|
@@ -84,15 +124,16 @@ function convertAbsoluteLengthToPx (value, unit) {
|
|
|
84
124
|
}
|
|
85
125
|
|
|
86
126
|
/**
|
|
87
|
-
* Formats a
|
|
127
|
+
* Formats what a calculation resolved to as the dimension a stylesheet states
|
|
128
|
+
* it with, rounding the number into the character budget a resolved number is
|
|
129
|
+
* written within.
|
|
88
130
|
*
|
|
89
|
-
* @param {number} value
|
|
90
|
-
* @param {string} unit
|
|
91
|
-
* @
|
|
92
|
-
* @return {string} The formatted dimension string.
|
|
131
|
+
* @param {number} value The numeric result of the calculation.
|
|
132
|
+
* @param {string} unit The CSS unit suffix the result carries (e.g. "px", "%").
|
|
133
|
+
* @return {string} The dimension as it is written into the stylesheet.
|
|
93
134
|
*/
|
|
94
|
-
function
|
|
95
|
-
return
|
|
135
|
+
function formatResolvedDimension (value, unit) {
|
|
136
|
+
return formatResolvedNumber(value) + unit;
|
|
96
137
|
}
|
|
97
138
|
|
|
98
139
|
/**
|
|
@@ -197,7 +238,8 @@ export {
|
|
|
197
238
|
collapseShorthandParts,
|
|
198
239
|
convertAbsoluteLengthToPx,
|
|
199
240
|
formatCompactNumber,
|
|
200
|
-
|
|
241
|
+
formatResolvedDimension,
|
|
242
|
+
formatResolvedNumber,
|
|
201
243
|
hasSubstitutedParts,
|
|
202
244
|
normalizeScaleComponent,
|
|
203
245
|
parseAlphaString,
|