@thejaredwilcurt/csslop 0.0.19 → 0.0.21
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 +8 -10
- package/src/declarations/background.js +322 -0
- package/src/declarations/border.js +120 -0
- package/src/declarations/config.js +36 -0
- package/src/declarations/merge.js +323 -0
- package/src/declarations/order.js +77 -0
- package/src/declarations/process.js +186 -869
- package/src/declarations/shorthand-values.js +374 -0
- package/src/rules/normalize.js +16 -5
- package/src/rules/selectors.js +71 -8
- package/src/rules/stringify.js +10 -11
- package/src/value/color-mix.js +6 -0
- package/src/value/colors.js +15 -0
- package/src/value/minify.js +149 -31
- package/src/value/shared.js +31 -0
- package/src/value/syntax.js +82 -1
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@thejaredwilcurt/csslop",
|
|
3
3
|
"main": "index.js",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.0.
|
|
5
|
+
"version": "0.0.21",
|
|
6
6
|
"description": "Experimental CSS minification",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"prestart": "node ./scripts/prestart.js",
|
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
"prebuild": "node ./scripts/prebuild.js",
|
|
12
12
|
"build": "vite build",
|
|
13
13
|
"postbuild": "node ./scripts/postbuild.js",
|
|
14
|
-
"copy": "node ./scripts/
|
|
15
|
-
"copy-repo": "node ./scripts/copyTestsFromRepo.js",
|
|
14
|
+
"copy": "node ./scripts/copyTests.js",
|
|
16
15
|
"test": "node ./tests/index.test.js",
|
|
17
16
|
"real": "node ./tests/realworld.test.js",
|
|
18
17
|
"lint": "eslint *.js scripts src tests --fix",
|
|
@@ -26,22 +25,21 @@
|
|
|
26
25
|
"devDependencies": {
|
|
27
26
|
"@codemirror/autocomplete": "^6.20.3",
|
|
28
27
|
"@codemirror/lang-css": "^6.3.1",
|
|
29
|
-
"@codemirror/view": "^6.43.
|
|
28
|
+
"@codemirror/view": "^6.43.8",
|
|
30
29
|
"@eslint/js": "^10.0.1",
|
|
31
30
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
32
31
|
"codemirror": "^6.0.2",
|
|
33
|
-
"
|
|
34
|
-
"eslint": "^10.8.0",
|
|
32
|
+
"eslint": "^10.8.1",
|
|
35
33
|
"eslint-config-tjw-base": "^5.0.0",
|
|
36
34
|
"eslint-config-tjw-import-x": "^1.0.1",
|
|
37
35
|
"eslint-config-tjw-jsdoc": "^2.0.1",
|
|
38
36
|
"eslint-plugin-import-x": "^4.17.0",
|
|
39
|
-
"eslint-plugin-jsdoc": "^
|
|
37
|
+
"eslint-plugin-jsdoc": "^64.1.0",
|
|
40
38
|
"fflate": "^0.8.3",
|
|
41
|
-
"globals": "^17.
|
|
39
|
+
"globals": "^17.11.0",
|
|
42
40
|
"pretty-ms": "^9.3.0",
|
|
43
|
-
"real-world-css-libraries": "^1.0.
|
|
44
|
-
"vite": "^8.2.
|
|
41
|
+
"real-world-css-libraries": "^1.0.7",
|
|
42
|
+
"vite": "^8.2.1"
|
|
45
43
|
},
|
|
46
44
|
"author": "The Jared Wilcurt",
|
|
47
45
|
"homepage": "https://github.com/TheJaredWilcurt/csslop#readme",
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Classifies background shorthand tokens and rebuilds the `background` shorthand from its component values.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { minifyValue } from '../value/minify.js';
|
|
6
|
+
|
|
7
|
+
import { shorthandMap } from './config.js';
|
|
8
|
+
|
|
9
|
+
const BACKGROUND_POSITION_KEYWORDS = new Set(['left', 'center', 'right', 'top', 'bottom']);
|
|
10
|
+
const BACKGROUND_REPEAT_KEYWORDS = new Set(['repeat', 'no-repeat', 'repeat-x', 'repeat-y', 'space', 'round']);
|
|
11
|
+
const BACKGROUND_ATTACHMENT_KEYWORDS = new Set(['scroll', 'fixed', 'local']);
|
|
12
|
+
const BACKGROUND_BOX_KEYWORDS = new Set(['border-box', 'padding-box', 'content-box']);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Resolves the background position from a value map. Prefers the combined
|
|
16
|
+
* `background-position` property if present, otherwise combines
|
|
17
|
+
* `background-position-x` and `background-position-y` into a single value.
|
|
18
|
+
*
|
|
19
|
+
* @param {Map} valueMap A map of property names to their minified values.
|
|
20
|
+
* @return {string|null} The resolved position string, or null if no position data is available.
|
|
21
|
+
*/
|
|
22
|
+
function resolveBackgroundPosition (valueMap) {
|
|
23
|
+
const position = valueMap.get('background-position');
|
|
24
|
+
if (position) {
|
|
25
|
+
return position;
|
|
26
|
+
}
|
|
27
|
+
const positionX = valueMap.get('background-position-x');
|
|
28
|
+
const positionY = valueMap.get('background-position-y');
|
|
29
|
+
if (positionX && positionY) {
|
|
30
|
+
return positionX + ' ' + positionY;
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Determines whether a token is a background image component such as `none`,
|
|
37
|
+
* `url(...)`, or an image-producing function like `linear-gradient(...)`.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} token The token to classify.
|
|
40
|
+
* @return {boolean} Whether the token is a background image token.
|
|
41
|
+
*/
|
|
42
|
+
function isBackgroundImageToken (token) {
|
|
43
|
+
if (token === 'none' || token.startsWith('url(')) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
if (!token.endsWith(')')) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
const functionNameMatch = token.match(/^([a-z-]+)\(/i);
|
|
50
|
+
if (!functionNameMatch) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
const functionName = functionNameMatch[1].toLowerCase();
|
|
54
|
+
return !['calc', 'min', 'max', 'clamp', 'var', 'env', 'rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'lab', 'lch', 'oklab', 'oklch', 'color'].includes(functionName);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Determines whether a token can participate in a background-position value.
|
|
59
|
+
*
|
|
60
|
+
* @param {string} token The token to classify.
|
|
61
|
+
* @return {boolean} Whether the token is a valid background-position token.
|
|
62
|
+
*/
|
|
63
|
+
function isBackgroundPositionToken (token) {
|
|
64
|
+
const lowercaseToken = token.toLowerCase();
|
|
65
|
+
if (BACKGROUND_POSITION_KEYWORDS.has(lowercaseToken)) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
if (/^[+-]?(?:\d+|\d*\.\d+)(?:%|[a-z]+)?$/i.test(token)) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return /^(?:calc|min|max|clamp|var|env)\(/i.test(token);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Determines whether a token is a background color component after excluding
|
|
76
|
+
* known image, position, repeat, attachment, and box tokens.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} token The token to classify.
|
|
79
|
+
* @return {boolean} Whether the token is a background color token.
|
|
80
|
+
*/
|
|
81
|
+
function isBackgroundColorToken (token) {
|
|
82
|
+
if (token === '/' || isBackgroundImageToken(token) || isBackgroundPositionToken(token)) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
const lowercaseToken = token.toLowerCase();
|
|
86
|
+
if (BACKGROUND_REPEAT_KEYWORDS.has(lowercaseToken) || BACKGROUND_ATTACHMENT_KEYWORDS.has(lowercaseToken) || BACKGROUND_BOX_KEYWORDS.has(lowercaseToken)) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return /^#/i.test(token) || /^[a-z-]+$/i.test(token) || /^(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\(/i.test(token);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Splits a token like `linear-gradient(...)100%` into separate image and
|
|
94
|
+
* position tokens when the function output is immediately followed by a
|
|
95
|
+
* background-position token.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} token The token to inspect.
|
|
98
|
+
* @return {Array} The original token, or separate image/position tokens.
|
|
99
|
+
*/
|
|
100
|
+
function splitAttachedBackgroundImageToken (token) {
|
|
101
|
+
const lastCloseParenthesis = token.lastIndexOf(')');
|
|
102
|
+
if (lastCloseParenthesis === -1 || lastCloseParenthesis === token.length - 1) {
|
|
103
|
+
return [token];
|
|
104
|
+
}
|
|
105
|
+
const imageToken = token.slice(0, lastCloseParenthesis + 1);
|
|
106
|
+
const followingToken = token.slice(lastCloseParenthesis + 1);
|
|
107
|
+
if (!isBackgroundImageToken(imageToken) || !isBackgroundPositionToken(followingToken)) {
|
|
108
|
+
return [token];
|
|
109
|
+
}
|
|
110
|
+
return [imageToken, followingToken];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Splits a single-layer background shorthand into top-level tokens while
|
|
115
|
+
* respecting nested parentheses and preserving `/` as its own token.
|
|
116
|
+
*
|
|
117
|
+
* @param {string} value The background shorthand value.
|
|
118
|
+
* @return {Array} The extracted top-level tokens.
|
|
119
|
+
*/
|
|
120
|
+
function splitBackgroundTokens (value) {
|
|
121
|
+
const tokens = [];
|
|
122
|
+
let currentToken = '';
|
|
123
|
+
let parenthesisDepth = 0;
|
|
124
|
+
|
|
125
|
+
for (const character of value) {
|
|
126
|
+
if (character === '(') {
|
|
127
|
+
parenthesisDepth++;
|
|
128
|
+
} else if (character === ')') {
|
|
129
|
+
parenthesisDepth--;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (parenthesisDepth === 0 && character === '/') {
|
|
133
|
+
if (currentToken) {
|
|
134
|
+
tokens.push(currentToken);
|
|
135
|
+
currentToken = '';
|
|
136
|
+
}
|
|
137
|
+
tokens.push('/');
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (parenthesisDepth === 0 && /\s/.test(character)) {
|
|
142
|
+
if (currentToken) {
|
|
143
|
+
tokens.push(currentToken);
|
|
144
|
+
currentToken = '';
|
|
145
|
+
}
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
currentToken += character;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (currentToken) {
|
|
153
|
+
tokens.push(currentToken);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const normalizedTokens = [];
|
|
157
|
+
for (const token of tokens) {
|
|
158
|
+
normalizedTokens.push(...splitAttachedBackgroundImageToken(token));
|
|
159
|
+
}
|
|
160
|
+
return normalizedTokens;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Extracts the simple image/color base from an existing background shorthand.
|
|
165
|
+
* Returns null for shorthands that already contain size, position, or any
|
|
166
|
+
* token that cannot be safely reconstructed by the background builder.
|
|
167
|
+
*
|
|
168
|
+
* @param {string} value The background shorthand value.
|
|
169
|
+
* @return {Map|null} A component map for safe reconstruction, or null.
|
|
170
|
+
*/
|
|
171
|
+
function extractSimpleBackgroundBase (value) {
|
|
172
|
+
const tokens = splitBackgroundTokens(value);
|
|
173
|
+
const componentMap = new Map();
|
|
174
|
+
|
|
175
|
+
for (const token of tokens) {
|
|
176
|
+
if (token === '/') {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
if (isBackgroundImageToken(token)) {
|
|
180
|
+
if (componentMap.has('background-image')) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
componentMap.set('background-image', token);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (isBackgroundColorToken(token)) {
|
|
187
|
+
if (componentMap.has('background-color')) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
componentMap.set('background-color', token);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return componentMap;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Serializes normalized background components into a minified background
|
|
201
|
+
* shorthand value while omitting default sub-values.
|
|
202
|
+
*
|
|
203
|
+
* @param {Map} valueMap The normalized background component map.
|
|
204
|
+
* @param {string} importantSuffix A trailing `!important` suffix, if needed.
|
|
205
|
+
* @return {string|null} The minified background shorthand, or null.
|
|
206
|
+
*/
|
|
207
|
+
function buildBackgroundShorthandValue (valueMap, importantSuffix) {
|
|
208
|
+
const color = valueMap.get('background-color');
|
|
209
|
+
const image = valueMap.get('background-image');
|
|
210
|
+
const repeat = valueMap.get('background-repeat');
|
|
211
|
+
const attachment = valueMap.get('background-attachment');
|
|
212
|
+
const size = valueMap.get('background-size');
|
|
213
|
+
const origin = valueMap.get('background-origin');
|
|
214
|
+
const clip = valueMap.get('background-clip');
|
|
215
|
+
const position = resolveBackgroundPosition(valueMap);
|
|
216
|
+
|
|
217
|
+
const result = [];
|
|
218
|
+
if (color && color !== 'transparent') {
|
|
219
|
+
result.push(color);
|
|
220
|
+
}
|
|
221
|
+
if (image && image !== 'none') {
|
|
222
|
+
result.push(image);
|
|
223
|
+
}
|
|
224
|
+
if (position && position !== '0 0' && position !== '0% 0%') {
|
|
225
|
+
result.push(position);
|
|
226
|
+
}
|
|
227
|
+
if (size && size !== 'auto') {
|
|
228
|
+
if (position && position !== '0 0' && position !== '0% 0%') {
|
|
229
|
+
result.push('/' + size);
|
|
230
|
+
} else {
|
|
231
|
+
result.push('0 0/' + size);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (repeat && repeat !== 'repeat') {
|
|
235
|
+
result.push(repeat);
|
|
236
|
+
}
|
|
237
|
+
if (attachment && attachment !== 'scroll') {
|
|
238
|
+
result.push(attachment);
|
|
239
|
+
}
|
|
240
|
+
const hasNonDefaultOrigin = origin && origin !== 'padding-box';
|
|
241
|
+
const hasNonDefaultClip = clip && clip !== 'border-box';
|
|
242
|
+
if (hasNonDefaultOrigin && hasNonDefaultClip) {
|
|
243
|
+
result.push(origin);
|
|
244
|
+
result.push(clip);
|
|
245
|
+
} else if (hasNonDefaultOrigin || hasNonDefaultClip) {
|
|
246
|
+
if (origin) {
|
|
247
|
+
result.push(origin);
|
|
248
|
+
}
|
|
249
|
+
if (clip) {
|
|
250
|
+
result.push(clip);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (!result.length) {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
return result.join(' ') + importantSuffix;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Merges later background longhands into an earlier simple background shorthand
|
|
261
|
+
* when their combined value can be reconstructed without changing semantics.
|
|
262
|
+
*
|
|
263
|
+
* @param {Array} declarations The declarations in source order.
|
|
264
|
+
* @return {Array} The updated declarations with absorbed longhands.
|
|
265
|
+
*/
|
|
266
|
+
function absorbBackgroundLonghandsIntoShorthand (declarations) {
|
|
267
|
+
const backgroundIndex = declarations.findIndex((declaration) => {
|
|
268
|
+
return declaration.property === 'background';
|
|
269
|
+
});
|
|
270
|
+
if (backgroundIndex === -1) {
|
|
271
|
+
return declarations;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const backgroundDeclaration = declarations[backgroundIndex];
|
|
275
|
+
const backgroundValue = minifyValue(backgroundDeclaration);
|
|
276
|
+
const backgroundIsImportant = backgroundValue.includes('!important');
|
|
277
|
+
const simpleBase = extractSimpleBackgroundBase(backgroundValue.replace('!important', '').trim());
|
|
278
|
+
if (!simpleBase) {
|
|
279
|
+
return declarations;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const absorbableProperties = new Set(shorthandMap.background.filter((property) => {
|
|
283
|
+
return property !== 'background';
|
|
284
|
+
}));
|
|
285
|
+
const relevantDeclarations = declarations.filter((declaration, index) => {
|
|
286
|
+
return index > backgroundIndex && absorbableProperties.has(declaration.property);
|
|
287
|
+
});
|
|
288
|
+
if (!relevantDeclarations.length) {
|
|
289
|
+
return declarations;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const sharesImportance = relevantDeclarations.every((declaration) => {
|
|
293
|
+
return minifyValue(declaration).includes('!important') === backgroundIsImportant;
|
|
294
|
+
});
|
|
295
|
+
if (!sharesImportance) {
|
|
296
|
+
return declarations;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
for (const declaration of relevantDeclarations) {
|
|
300
|
+
simpleBase.set(declaration.property, minifyValue(declaration).replace('!important', '').trim());
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const mergedValue = buildBackgroundShorthandValue(simpleBase, backgroundIsImportant ? '!important' : '');
|
|
304
|
+
if (!mergedValue) {
|
|
305
|
+
return declarations;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return declarations.flatMap((declaration, index) => {
|
|
309
|
+
if (index === backgroundIndex) {
|
|
310
|
+
return [{ ...declaration, value: mergedValue }];
|
|
311
|
+
}
|
|
312
|
+
if (index > backgroundIndex && absorbableProperties.has(declaration.property)) {
|
|
313
|
+
return [];
|
|
314
|
+
}
|
|
315
|
+
return [declaration];
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export {
|
|
320
|
+
absorbBackgroundLonghandsIntoShorthand,
|
|
321
|
+
buildBackgroundShorthandValue
|
|
322
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Rewrites border longhand declarations that cannot be expressed as a single `border` shorthand into the shortest valid shorthand plus override pair.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { minifyValue } from '../value/minify.js';
|
|
6
|
+
import { splitTopLevelComponents } from '../value/syntax.js';
|
|
7
|
+
|
|
8
|
+
import { CSS_WIDE_KEYWORDS } from './config.js';
|
|
9
|
+
|
|
10
|
+
const BORDER_TRIO_PROPERTIES = ['border-width', 'border-style', 'border-color'];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Finds the index of the last declaration for a property, which is the one that
|
|
14
|
+
* wins the cascade within a rule.
|
|
15
|
+
*
|
|
16
|
+
* @param {Array} declarations The declarations to search.
|
|
17
|
+
* @param {string} property The property name to look for.
|
|
18
|
+
* @return {number} The index of the matching declaration, or -1 when absent.
|
|
19
|
+
*/
|
|
20
|
+
function findLastDeclarationIndex (declarations, property) {
|
|
21
|
+
for (let index = declarations.length - 1; index >= 0; index--) {
|
|
22
|
+
if (declarations[index].property === property) {
|
|
23
|
+
return index;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return -1;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Rewrites a `border-width` + `border-style` + `border-color` trio whose color
|
|
31
|
+
* differs per edge into a `border` shorthand built from the first color, followed
|
|
32
|
+
* by a `border-color` override that restores the per-edge colors. The rewrite is
|
|
33
|
+
* only applied when the resulting pair is shorter than the three longhands.
|
|
34
|
+
*
|
|
35
|
+
* @param {Array} declarations The declarations of a single rule.
|
|
36
|
+
* @return {Array} The declarations, with the trio rewritten when it is shorter.
|
|
37
|
+
*/
|
|
38
|
+
function collapseBorderTrioWithPerEdgeColor (declarations) {
|
|
39
|
+
const trioIndexes = BORDER_TRIO_PROPERTIES.map((property) => {
|
|
40
|
+
return findLastDeclarationIndex(declarations, property);
|
|
41
|
+
});
|
|
42
|
+
const hasWholeTrio = trioIndexes.every((index) => {
|
|
43
|
+
return index !== -1;
|
|
44
|
+
});
|
|
45
|
+
if (!hasWholeTrio) {
|
|
46
|
+
return declarations;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const trioValues = trioIndexes.map((index) => {
|
|
50
|
+
return minifyValue(declarations[index]);
|
|
51
|
+
});
|
|
52
|
+
const allImportant = trioValues.every((value) => {
|
|
53
|
+
return value.includes('!important');
|
|
54
|
+
});
|
|
55
|
+
const noneImportant = trioValues.every((value) => {
|
|
56
|
+
return !value.includes('!important');
|
|
57
|
+
});
|
|
58
|
+
if (!allImportant && !noneImportant) {
|
|
59
|
+
return declarations;
|
|
60
|
+
}
|
|
61
|
+
const importantSuffix = allImportant ? '!important' : '';
|
|
62
|
+
|
|
63
|
+
const [width, style, color] = trioValues.map((value) => {
|
|
64
|
+
return value.replace('!important', '').trim();
|
|
65
|
+
});
|
|
66
|
+
const colorComponents = splitTopLevelComponents(color);
|
|
67
|
+
const isSingleComponent = (value) => {
|
|
68
|
+
return splitTopLevelComponents(value).length === 1;
|
|
69
|
+
};
|
|
70
|
+
const canBuildShorthand = (
|
|
71
|
+
isSingleComponent(width) &&
|
|
72
|
+
isSingleComponent(style) &&
|
|
73
|
+
colorComponents.length > 1
|
|
74
|
+
);
|
|
75
|
+
if (!canBuildShorthand) {
|
|
76
|
+
return declarations;
|
|
77
|
+
}
|
|
78
|
+
const usesCssWideKeyword = [width, style, ...colorComponents].some((value) => {
|
|
79
|
+
return CSS_WIDE_KEYWORDS.has(value.toLowerCase());
|
|
80
|
+
});
|
|
81
|
+
if (usesCssWideKeyword) {
|
|
82
|
+
return declarations;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const borderValue = [width, style, colorComponents[0]].join(' ') + importantSuffix;
|
|
86
|
+
const colorValue = color + importantSuffix;
|
|
87
|
+
const rewrittenLength = ('border:' + borderValue + ';border-color:' + colorValue).length;
|
|
88
|
+
const longhandLength = (
|
|
89
|
+
'border-width:' + trioValues[0] +
|
|
90
|
+
';border-style:' + trioValues[1] +
|
|
91
|
+
';border-color:' + trioValues[2]
|
|
92
|
+
).length;
|
|
93
|
+
if (rewrittenLength >= longhandLength) {
|
|
94
|
+
return declarations;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const replacedIndexes = new Set(trioIndexes);
|
|
98
|
+
const insertionIndex = Math.min(...trioIndexes);
|
|
99
|
+
return declarations.flatMap((declaration, index) => {
|
|
100
|
+
if (index === insertionIndex) {
|
|
101
|
+
return [
|
|
102
|
+
{
|
|
103
|
+
property: 'border',
|
|
104
|
+
value: borderValue,
|
|
105
|
+
isAssembledShorthand: true
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
property: 'border-color',
|
|
109
|
+
value: colorValue
|
|
110
|
+
}
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
if (replacedIndexes.has(index)) {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
return [declaration];
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export { collapseBorderTrioWithPerEdgeColor };
|
|
@@ -51,7 +51,43 @@ const shorthandOverrideMap = {
|
|
|
51
51
|
mask: ['mask-border', 'mask-border-source', 'mask-border-slice', 'mask-border-width', 'mask-border-outset', 'mask-border-repeat', 'mask-border-mode']
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* CSS-wide keywords, which are only valid as the entire value of a declaration
|
|
56
|
+
* and never as an individual component of a shorthand value.
|
|
57
|
+
*
|
|
58
|
+
* @type {Set<string>}
|
|
59
|
+
*/
|
|
60
|
+
const CSS_WIDE_KEYWORDS = new Set(['inherit', 'initial', 'unset', 'revert', 'revert-layer']);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Shorthand properties that themselves hold a full width/style/color value for a
|
|
64
|
+
* single edge of a box, so they can only collapse into their parent shorthand
|
|
65
|
+
* when every edge carries the exact same value.
|
|
66
|
+
*
|
|
67
|
+
* @type {Set<string>}
|
|
68
|
+
*/
|
|
69
|
+
const EDGE_SHORTHANDS = new Set([
|
|
70
|
+
'border-top',
|
|
71
|
+
'border-right',
|
|
72
|
+
'border-bottom',
|
|
73
|
+
'border-left',
|
|
74
|
+
'border-inline-start',
|
|
75
|
+
'border-inline-end',
|
|
76
|
+
'border-block-start',
|
|
77
|
+
'border-block-end'
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The four physical edge shorthands that together cover the `border` shorthand.
|
|
82
|
+
*
|
|
83
|
+
* @type {Array}
|
|
84
|
+
*/
|
|
85
|
+
const BORDER_EDGE_PROPERTIES = ['border-top', 'border-right', 'border-bottom', 'border-left'];
|
|
86
|
+
|
|
54
87
|
export {
|
|
88
|
+
BORDER_EDGE_PROPERTIES,
|
|
89
|
+
CSS_WIDE_KEYWORDS,
|
|
90
|
+
EDGE_SHORTHANDS,
|
|
55
91
|
shorthandMap,
|
|
56
92
|
shorthandOverrideMap
|
|
57
93
|
};
|