@thejaredwilcurt/csslop 0.0.37 → 0.0.39
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 +7 -2
- package/package.json +8 -6
- package/src/declarations/background.js +29 -37
- package/src/declarations/border.js +1 -1
- package/src/declarations/fold.js +1 -1
- package/src/declarations/process.js +17 -8
- package/src/declarations/shorthand-values.js +1 -1
- package/src/index.js +19 -46
- package/src/parser/at-rules.js +291 -0
- package/src/parser/index.js +826 -0
- package/src/parser/positions.js +71 -0
- package/src/parser/scanner.js +229 -0
- package/src/parser/source-search.js +287 -0
- package/src/parser/tokenizer.js +396 -0
- package/src/preprocess.js +20 -228
- package/src/rules/lang.js +330 -0
- package/src/rules/normalize.js +80 -0
- package/src/rules/optimize.js +260 -120
- package/src/rules/selectors.js +67 -223
- package/src/rules/stringify.js +18 -13
- package/src/value/color-mix.js +22 -47
- package/src/value/gradients.js +44 -71
- package/src/value/light-dark.js +9 -94
- package/src/value/math.js +163 -1
- package/src/value/minify.js +139 -52
- package/src/value/relative-colors.js +15 -78
- package/src/value/shared.js +1 -0
- package/src/value/tokens.js +4 -387
- package/src/value/transforms.js +20 -53
- package/src/value/syntax.js +0 -144
package/src/value/gradients.js
CHANGED
|
@@ -2,40 +2,16 @@
|
|
|
2
2
|
* @file Parses and minifies CSS gradient function calls by splitting arguments, normalizing default directions, and removing redundant stop positions.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import {
|
|
6
|
+
findMatchingParenthesis,
|
|
7
|
+
splitTopLevelCommaList
|
|
8
|
+
} from '../parser/source-search.js';
|
|
9
|
+
|
|
5
10
|
import {
|
|
6
11
|
parseHex,
|
|
7
12
|
shortestColor
|
|
8
13
|
} from './colors.js';
|
|
9
14
|
|
|
10
|
-
/**
|
|
11
|
-
* Splits a gradient function's argument string at top-level commas, correctly handling nested parentheses.
|
|
12
|
-
*
|
|
13
|
-
* @param {string} argumentString The raw gradient arguments string.
|
|
14
|
-
* @return {Array} An array of trimmed argument strings.
|
|
15
|
-
*/
|
|
16
|
-
function splitGradientArgs (argumentString) {
|
|
17
|
-
const parts = [];
|
|
18
|
-
let depth = 0;
|
|
19
|
-
let current = '';
|
|
20
|
-
for (const character of argumentString) {
|
|
21
|
-
if (character === '(') {
|
|
22
|
-
depth++;
|
|
23
|
-
} else if (character === ')') {
|
|
24
|
-
depth--;
|
|
25
|
-
}
|
|
26
|
-
if (character === ',' && depth === 0) {
|
|
27
|
-
parts.push(current.trim());
|
|
28
|
-
current = '';
|
|
29
|
-
} else {
|
|
30
|
-
current += character;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
if (current.trim() || parts.length) {
|
|
34
|
-
parts.push(current.trim());
|
|
35
|
-
}
|
|
36
|
-
return parts;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
15
|
/**
|
|
40
16
|
* Checks whether a string is a valid gradient stop position consisting of one
|
|
41
17
|
* or two numeric tokens with optional CSS units.
|
|
@@ -435,45 +411,45 @@ function normalizeRadialGradientShape (args) {
|
|
|
435
411
|
/**
|
|
436
412
|
* Optimizes gradient arguments by removing default direction or shape keywords, combining adjacent identical color stops, and trimming redundant 0% or 100% stop positions from the first and last stops.
|
|
437
413
|
*
|
|
438
|
-
* @param {string}
|
|
439
|
-
* @param {string}
|
|
440
|
-
* @return {string}
|
|
414
|
+
* @param {string} functionName The gradient function name (e.g. "linear-gradient").
|
|
415
|
+
* @param {string} argumentText The raw comma-separated gradient arguments string.
|
|
416
|
+
* @return {string} The optimized gradient arguments string.
|
|
441
417
|
*/
|
|
442
|
-
function
|
|
443
|
-
const
|
|
444
|
-
const
|
|
418
|
+
function processGradientArguments (functionName, argumentText) {
|
|
419
|
+
const argumentList = splitTopLevelCommaList(argumentText);
|
|
420
|
+
const lowercaseName = functionName.toLowerCase();
|
|
445
421
|
|
|
446
|
-
let
|
|
422
|
+
let directionArgumentCount = 0;
|
|
447
423
|
|
|
448
|
-
if (
|
|
449
|
-
if (
|
|
450
|
-
|
|
451
|
-
} else if (
|
|
452
|
-
|
|
424
|
+
if (argumentList.length > 1) {
|
|
425
|
+
if (lowercaseName.includes('linear')) {
|
|
426
|
+
directionArgumentCount = normalizeLinearGradientDirection(argumentList);
|
|
427
|
+
} else if (lowercaseName.includes('radial')) {
|
|
428
|
+
directionArgumentCount = normalizeRadialGradientShape(argumentList);
|
|
453
429
|
}
|
|
454
430
|
}
|
|
455
431
|
|
|
456
|
-
// Extract color stop
|
|
457
|
-
const
|
|
458
|
-
return normalizeColorStop(
|
|
432
|
+
// Extract color stop arguments (everything after the direction/shape argument)
|
|
433
|
+
const colorStopArguments = argumentList.slice(directionArgumentCount).map((argument) => {
|
|
434
|
+
return normalizeColorStop(argument);
|
|
459
435
|
});
|
|
460
|
-
if (
|
|
461
|
-
const normalizedStops =
|
|
462
|
-
combineAdjacentIdenticalStops(
|
|
463
|
-
|
|
464
|
-
|
|
436
|
+
if (colorStopArguments.length > 0) {
|
|
437
|
+
const normalizedStops = colorStopArguments.length >= 2 ?
|
|
438
|
+
combineAdjacentIdenticalStops(colorStopArguments) :
|
|
439
|
+
colorStopArguments;
|
|
440
|
+
argumentList.splice(directionArgumentCount, colorStopArguments.length, ...normalizedStops);
|
|
465
441
|
}
|
|
466
442
|
|
|
467
|
-
if (
|
|
468
|
-
const firstStopIndex =
|
|
469
|
-
const lastStopIndex =
|
|
443
|
+
if (argumentList.length > directionArgumentCount) {
|
|
444
|
+
const firstStopIndex = directionArgumentCount;
|
|
445
|
+
const lastStopIndex = argumentList.length - 1;
|
|
470
446
|
// Remove default 0% stop position from the first gradient stop
|
|
471
|
-
|
|
447
|
+
argumentList[firstStopIndex] = argumentList[firstStopIndex].replace(/^(.*\S)\s+0%$/, '$1');
|
|
472
448
|
// Remove default 100% stop position from the last gradient stop
|
|
473
|
-
|
|
449
|
+
argumentList[lastStopIndex] = argumentList[lastStopIndex].replace(/^(.*\S)\s+100%$/, '$1');
|
|
474
450
|
}
|
|
475
451
|
|
|
476
|
-
return
|
|
452
|
+
return argumentList.join(',');
|
|
477
453
|
}
|
|
478
454
|
|
|
479
455
|
/**
|
|
@@ -490,24 +466,21 @@ function minifyGradients (value) {
|
|
|
490
466
|
// Match gradient function names: linear-gradient, radial-gradient, conic-gradient, and their repeating- variants
|
|
491
467
|
const gradientMatch = rest.match(/^((?:repeating-)?(?:linear|radial|conic)-gradient)\(/i);
|
|
492
468
|
if (gradientMatch) {
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
depth--;
|
|
501
|
-
}
|
|
502
|
-
end++;
|
|
469
|
+
const functionName = gradientMatch[1];
|
|
470
|
+
const openIndex = position + functionName.length;
|
|
471
|
+
const closeIndex = findMatchingParenthesis(value, openIndex);
|
|
472
|
+
if (closeIndex === -1) {
|
|
473
|
+
// A gradient whose arguments never close is left exactly as it was written
|
|
474
|
+
result += value.slice(position);
|
|
475
|
+
break;
|
|
503
476
|
}
|
|
504
|
-
const
|
|
505
|
-
result +=
|
|
506
|
-
position =
|
|
507
|
-
|
|
508
|
-
result += value[position];
|
|
509
|
-
position++;
|
|
477
|
+
const argumentText = value.slice(openIndex + 1, closeIndex);
|
|
478
|
+
result += functionName + '(' + processGradientArguments(functionName, argumentText) + ')';
|
|
479
|
+
position = closeIndex + 1;
|
|
480
|
+
continue;
|
|
510
481
|
}
|
|
482
|
+
result += value[position];
|
|
483
|
+
position++;
|
|
511
484
|
}
|
|
512
485
|
return result;
|
|
513
486
|
}
|
package/src/value/light-dark.js
CHANGED
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
* @file Simplifies CSS light-dark functions whose light and dark values are equivalent.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
findMatchingParenthesis,
|
|
7
|
+
skipQuotedString,
|
|
8
|
+
splitTopLevelCommaList,
|
|
9
|
+
startsUrlToken
|
|
10
|
+
} from '../parser/source-search.js';
|
|
6
11
|
|
|
7
12
|
/**
|
|
8
13
|
* Determines whether a character can appear inside a CSS identifier.
|
|
@@ -35,75 +40,6 @@ function startsLightDarkFunction (value, index) {
|
|
|
35
40
|
return !isIdentifierCharacter(value[index - 1]);
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
/**
|
|
39
|
-
* Splits a CSS function argument list at top-level commas while respecting
|
|
40
|
-
* nested parentheses and quoted strings.
|
|
41
|
-
*
|
|
42
|
-
* @param {string} argumentString The raw content between function parentheses.
|
|
43
|
-
* @return {Array} The top-level argument strings.
|
|
44
|
-
*/
|
|
45
|
-
function splitTopLevelFunctionArguments (argumentString) {
|
|
46
|
-
const argumentsList = [];
|
|
47
|
-
let currentArgument = '';
|
|
48
|
-
let depth = 0;
|
|
49
|
-
let index = 0;
|
|
50
|
-
let activeQuote = '';
|
|
51
|
-
|
|
52
|
-
while (index < argumentString.length) {
|
|
53
|
-
const character = argumentString[index];
|
|
54
|
-
if (activeQuote) {
|
|
55
|
-
currentArgument += character;
|
|
56
|
-
if (character === '\\') {
|
|
57
|
-
if (index + 1 < argumentString.length) {
|
|
58
|
-
currentArgument += argumentString[index + 1];
|
|
59
|
-
index += 2;
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
} else if (character === activeQuote) {
|
|
63
|
-
activeQuote = '';
|
|
64
|
-
}
|
|
65
|
-
index++;
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (character === '"' || character === '\'') {
|
|
70
|
-
activeQuote = character;
|
|
71
|
-
currentArgument += character;
|
|
72
|
-
index++;
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (character === '(') {
|
|
77
|
-
depth++;
|
|
78
|
-
currentArgument += character;
|
|
79
|
-
index++;
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (character === ')') {
|
|
84
|
-
if (depth > 0) {
|
|
85
|
-
depth--;
|
|
86
|
-
}
|
|
87
|
-
currentArgument += character;
|
|
88
|
-
index++;
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (character === ',' && depth === 0) {
|
|
93
|
-
argumentsList.push(currentArgument.trim());
|
|
94
|
-
currentArgument = '';
|
|
95
|
-
index++;
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
currentArgument += character;
|
|
100
|
-
index++;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
argumentsList.push(currentArgument.trim());
|
|
104
|
-
return argumentsList;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
43
|
/**
|
|
108
44
|
* Simplifies `light-dark(a,b)` to `a` when both top-level arguments are identical
|
|
109
45
|
* after prior minification has normalized them.
|
|
@@ -115,36 +51,15 @@ function simplifyEquivalentLightDarkFunctions (value) {
|
|
|
115
51
|
let result = '';
|
|
116
52
|
let index = 0;
|
|
117
53
|
|
|
118
|
-
const consumeQuoted = (start) => {
|
|
119
|
-
const quote = value[start];
|
|
120
|
-
let end = start + 1;
|
|
121
|
-
while (end < value.length) {
|
|
122
|
-
if (value[end] === '\\') {
|
|
123
|
-
end += 2;
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
|
-
if (value[end] === quote) {
|
|
127
|
-
end++;
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
130
|
-
end++;
|
|
131
|
-
}
|
|
132
|
-
return end;
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
const startsUrl = (start) => {
|
|
136
|
-
return value.slice(start, start + 4).toLowerCase() === 'url(';
|
|
137
|
-
};
|
|
138
|
-
|
|
139
54
|
while (index < value.length) {
|
|
140
55
|
if (value[index] === '"' || value[index] === '\'') {
|
|
141
|
-
const end =
|
|
56
|
+
const end = skipQuotedString(value, index);
|
|
142
57
|
result += value.slice(index, end);
|
|
143
58
|
index = end;
|
|
144
59
|
continue;
|
|
145
60
|
}
|
|
146
61
|
|
|
147
|
-
if (
|
|
62
|
+
if (startsUrlToken(value, index)) {
|
|
148
63
|
const end = findMatchingParenthesis(value, index + 3);
|
|
149
64
|
if (end === -1) {
|
|
150
65
|
result += value.slice(index);
|
|
@@ -164,7 +79,7 @@ function simplifyEquivalentLightDarkFunctions (value) {
|
|
|
164
79
|
}
|
|
165
80
|
|
|
166
81
|
const argumentString = value.slice(openParenIndex + 1, closingParenIndex);
|
|
167
|
-
const argumentsList =
|
|
82
|
+
const argumentsList = splitTopLevelCommaList(argumentString);
|
|
168
83
|
if (argumentsList.length === 2) {
|
|
169
84
|
const firstArgument = simplifyEquivalentLightDarkFunctions(argumentsList[0]);
|
|
170
85
|
const secondArgument = simplifyEquivalentLightDarkFunctions(argumentsList[1]);
|
package/src/value/math.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import { calc } from '@csstools/css-calc';
|
|
6
6
|
|
|
7
|
+
import { findMatchingParenthesis } from '../parser/source-search.js';
|
|
8
|
+
|
|
7
9
|
import {
|
|
8
10
|
convertAbsoluteLengthToPx,
|
|
9
11
|
formatDimension,
|
|
@@ -83,6 +85,166 @@ function keepSeparatorAfterDissolvedFunction (replacer) {
|
|
|
83
85
|
};
|
|
84
86
|
}
|
|
85
87
|
|
|
88
|
+
/**
|
|
89
|
+
* The functions whose arguments are a math expression, where `*` and `/` are
|
|
90
|
+
* arithmetic operators rather than characters of some other syntax.
|
|
91
|
+
*
|
|
92
|
+
* @type {Set<string>}
|
|
93
|
+
*/
|
|
94
|
+
const MATH_FUNCTION_NAMES = new Set([
|
|
95
|
+
'abs',
|
|
96
|
+
'calc',
|
|
97
|
+
'clamp',
|
|
98
|
+
'hypot',
|
|
99
|
+
'max',
|
|
100
|
+
'min',
|
|
101
|
+
'mod',
|
|
102
|
+
'rem',
|
|
103
|
+
'round',
|
|
104
|
+
'sign'
|
|
105
|
+
]);
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Matches a single character that may appear in a CSS function name.
|
|
109
|
+
*
|
|
110
|
+
* @type {RegExp}
|
|
111
|
+
*/
|
|
112
|
+
const FUNCTION_NAME_CHARACTER = /[a-zA-Z-]/;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Finds where the function name that ends at an opening parenthesis begins.
|
|
116
|
+
*
|
|
117
|
+
* @param {string} value The CSS value string being scanned.
|
|
118
|
+
* @param {number} openIndex The index of the opening parenthesis.
|
|
119
|
+
* @return {number} The index of the function name's first character.
|
|
120
|
+
*/
|
|
121
|
+
function findFunctionNameStart (value, openIndex) {
|
|
122
|
+
let index = openIndex;
|
|
123
|
+
while (index > 0 && FUNCTION_NAME_CHARACTER.test(value[index - 1])) {
|
|
124
|
+
index--;
|
|
125
|
+
}
|
|
126
|
+
return index;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Rewrites the contents of every math function in a value, leaving the rest of
|
|
131
|
+
* the value untouched. A `*` only means multiplication inside these functions,
|
|
132
|
+
* so an arithmetic rewrite may only reach the text between their parentheses.
|
|
133
|
+
* The whole of a math function is handed over at once, so a math function
|
|
134
|
+
* nested in another is rewritten as part of its parent.
|
|
135
|
+
*
|
|
136
|
+
* @param {function(string): string} rewriteExpression Receives a math function's contents and returns its replacement.
|
|
137
|
+
* @param {string} value The CSS value string to rewrite.
|
|
138
|
+
* @return {string} The value with every math function's contents rewritten.
|
|
139
|
+
*/
|
|
140
|
+
function rewriteMathFunctionContents (rewriteExpression, value) {
|
|
141
|
+
let result = '';
|
|
142
|
+
let index = 0;
|
|
143
|
+
while (index < value.length) {
|
|
144
|
+
const openIndex = value.indexOf('(', index);
|
|
145
|
+
if (openIndex === -1) {
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
const functionName = value.slice(findFunctionNameStart(value, openIndex), openIndex).toLowerCase();
|
|
149
|
+
const closeIndex = findMatchingParenthesis(value, openIndex);
|
|
150
|
+
if (!MATH_FUNCTION_NAMES.has(functionName) || closeIndex === -1) {
|
|
151
|
+
result += value.slice(index, openIndex + 1);
|
|
152
|
+
index = openIndex + 1;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
result += value.slice(index, openIndex + 1);
|
|
156
|
+
result += rewriteExpression(value.slice(openIndex + 1, closeIndex));
|
|
157
|
+
index = closeIndex;
|
|
158
|
+
}
|
|
159
|
+
return result + value.slice(index);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Matches a multiplication by a decimal below one, capturing the digits after
|
|
164
|
+
* its decimal point and the unit written on it. The trailing lookahead keeps
|
|
165
|
+
* the match away from numbers that only begin this way, such as the exponent
|
|
166
|
+
* of `.5e3` or a unit name that is still being read.
|
|
167
|
+
*
|
|
168
|
+
* @type {RegExp}
|
|
169
|
+
*/
|
|
170
|
+
const FRACTIONAL_MULTIPLIER = /\s*\*\s*0*\.(\d+)([a-z%]*)(?![\w.%(])/gi;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* The most decimal places a fraction may be built from while the power of ten
|
|
174
|
+
* it needs is still exactly representable as an integer.
|
|
175
|
+
*
|
|
176
|
+
* @type {number}
|
|
177
|
+
*/
|
|
178
|
+
const MAXIMUM_FRACTION_DIGITS = 15;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Computes the greatest common divisor of two non-negative integers, which is
|
|
182
|
+
* what a fraction has to be divided by to be stated in lowest terms.
|
|
183
|
+
*
|
|
184
|
+
* @param {number} first The first integer.
|
|
185
|
+
* @param {number} second The second integer.
|
|
186
|
+
* @return {number} The greatest common divisor of both integers.
|
|
187
|
+
*/
|
|
188
|
+
function greatestCommonDivisor (first, second) {
|
|
189
|
+
let dividend = first;
|
|
190
|
+
let divisor = second;
|
|
191
|
+
while (divisor) {
|
|
192
|
+
const remainder = dividend % divisor;
|
|
193
|
+
dividend = divisor;
|
|
194
|
+
divisor = remainder;
|
|
195
|
+
}
|
|
196
|
+
return dividend;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Converts the digits behind a decimal point into the whole number that
|
|
201
|
+
* dividing by scales a value exactly as multiplying by that decimal does.
|
|
202
|
+
* The digits `25` (of `.25`) state the fraction 25/100, which reduces to 1/4,
|
|
203
|
+
* so the divisor is `4`. A fraction that does not reduce to a numerator of one
|
|
204
|
+
* cannot be stated as a single division, and so has no divisor to give.
|
|
205
|
+
*
|
|
206
|
+
* @param {string} fractionDigits The digits written after the decimal point.
|
|
207
|
+
* @return {number|null} The equivalent whole number divisor, or null when there is none.
|
|
208
|
+
*/
|
|
209
|
+
function reciprocalOfDecimalFraction (fractionDigits) {
|
|
210
|
+
if (fractionDigits.length > MAXIMUM_FRACTION_DIGITS) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
const numerator = Number(fractionDigits);
|
|
214
|
+
const denominator = 10 ** fractionDigits.length;
|
|
215
|
+
const commonFactor = greatestCommonDivisor(numerator, denominator);
|
|
216
|
+
if (numerator / commonFactor !== 1) {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
return denominator / commonFactor;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Rewrites a multiplication by a decimal below one as a division by the whole
|
|
224
|
+
* number that decimal is the reciprocal of, which is never longer to write and
|
|
225
|
+
* usually shorter (`calc(var(--x)*.25)` → `calc(var(--x)/4)`). A unit written
|
|
226
|
+
* on the multiplier moves onto the divisor, since only how the operand is
|
|
227
|
+
* stated changes.
|
|
228
|
+
*
|
|
229
|
+
* @param {string} expression The contents of a math function.
|
|
230
|
+
* @return {string} The expression with reciprocal multipliers stated as divisions.
|
|
231
|
+
*/
|
|
232
|
+
function convertReciprocalMultiplicationToDivision (expression) {
|
|
233
|
+
return expression.replace(FRACTIONAL_MULTIPLIER, (match, fractionDigits, unit) => {
|
|
234
|
+
const divisor = reciprocalOfDecimalFraction(fractionDigits);
|
|
235
|
+
if (divisor === null) {
|
|
236
|
+
return match;
|
|
237
|
+
}
|
|
238
|
+
const division = '/' + divisor + unit;
|
|
239
|
+
// Compared against the shortest the multiplication could have been written
|
|
240
|
+
const multiplication = '*.' + fractionDigits + unit;
|
|
241
|
+
if (division.length > multiplication.length) {
|
|
242
|
+
return match;
|
|
243
|
+
}
|
|
244
|
+
return division;
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
86
248
|
/**
|
|
87
249
|
* The units a folded calc() expression leads with, in the order they are
|
|
88
250
|
* written. Every other unit follows them alphabetically.
|
|
@@ -223,7 +385,7 @@ function tryFoldCalcExpression (expression) {
|
|
|
223
385
|
* @return {string} The value with math functions simplified where possible.
|
|
224
386
|
*/
|
|
225
387
|
function normalizeMathFunctions (value, property, originalValue = '') {
|
|
226
|
-
let result = value;
|
|
388
|
+
let result = rewriteMathFunctionContents(convertReciprocalMultiplicationToDivision, value);
|
|
227
389
|
|
|
228
390
|
// Unwrap calc(1 / (1 / x)) → x (double-reciprocal identity)
|
|
229
391
|
result = result.replace(/calc\(\s*1\s*\/\s*\(\s*1\s*\/\s*([^()]+)\s*\)\s*\)/gi, keepSeparatorAfterDissolvedFunction((match, inner) => {
|