@thejaredwilcurt/csslop 0.0.24 → 0.0.26
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 +5 -1
- package/package.json +5 -3
- package/src/declarations/background.js +26 -1
- package/src/declarations/config.js +156 -2
- package/src/declarations/css-wide-keywords.js +29 -60
- package/src/declarations/fold.js +342 -0
- package/src/declarations/lookup.js +78 -0
- package/src/declarations/merge.js +59 -54
- package/src/declarations/order.js +45 -6
- package/src/declarations/process.js +148 -55
- package/src/declarations/shorthand-values.js +23 -0
- package/src/index.js +29 -5
- package/src/position-try.js +3 -1
- package/src/rules/optimize.js +198 -78
- package/src/rules/property.js +177 -0
- package/src/rules/stringify.js +13 -30
- package/src/value/gradients.js +87 -46
- package/src/value/math.js +17 -2
- package/src/value/minify.js +110 -14
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Folds longhand declarations that follow their own shorthand back into that shorthand, whenever restating the whole shorthand is shorter than keeping the shorthand and its overrides apart.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { splitTopLevelComponents } from '../value/syntax.js';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
CSS_WIDE_KEYWORDS,
|
|
9
|
+
expandToLeafProperties,
|
|
10
|
+
shorthandMap
|
|
11
|
+
} from './config.js';
|
|
12
|
+
import { describeDeclaration } from './lookup.js';
|
|
13
|
+
import { canMergeVarValue } from './merge.js';
|
|
14
|
+
import { buildShorthandValue } from './shorthand-values.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The shorthands whose value is a positional list of the values of their
|
|
18
|
+
* longhands: either a start/end pair or the four sides of a box. Only these can
|
|
19
|
+
* be expanded back into one value per longhand and rebuilt around an override.
|
|
20
|
+
*
|
|
21
|
+
* @type {Array}
|
|
22
|
+
*/
|
|
23
|
+
const POSITIONAL_SHORTHAND_NAMES = [
|
|
24
|
+
'margin',
|
|
25
|
+
'padding',
|
|
26
|
+
'inset',
|
|
27
|
+
'gap',
|
|
28
|
+
'overflow',
|
|
29
|
+
'place-items',
|
|
30
|
+
'place-content',
|
|
31
|
+
'place-self',
|
|
32
|
+
'border-width',
|
|
33
|
+
'border-style',
|
|
34
|
+
'border-color',
|
|
35
|
+
'border-radius',
|
|
36
|
+
'margin-inline',
|
|
37
|
+
'margin-block',
|
|
38
|
+
'padding-inline',
|
|
39
|
+
'padding-block',
|
|
40
|
+
'inset-inline',
|
|
41
|
+
'inset-block',
|
|
42
|
+
'border-inline-width',
|
|
43
|
+
'border-block-width'
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The words of the property names that describe the same box as a shorthand,
|
|
48
|
+
* computed on first use. The shorthand tables never change, so a shorthand
|
|
49
|
+
* always has the same family.
|
|
50
|
+
*
|
|
51
|
+
* @type {Map<string, Set<string>>}
|
|
52
|
+
*/
|
|
53
|
+
const familyWordsByShorthand = new Map();
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Collects the hyphen separated words of a shorthand and of every longhand it
|
|
57
|
+
* sets. Logical properties such as `padding-inline-start` describe the same box
|
|
58
|
+
* as physical ones such as `padding-right`, but which physical side they map to
|
|
59
|
+
* depends on the writing mode, so the words of the names are what relates them.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} shorthandName The CSS shorthand property name.
|
|
62
|
+
* @return {Set} The words that mark a property as part of the same family.
|
|
63
|
+
*/
|
|
64
|
+
function collectFamilyWords (shorthandName) {
|
|
65
|
+
const cachedWords = familyWordsByShorthand.get(shorthandName);
|
|
66
|
+
if (cachedWords) {
|
|
67
|
+
return cachedWords;
|
|
68
|
+
}
|
|
69
|
+
const words = new Set(shorthandName.split('-'));
|
|
70
|
+
for (const leafProperty of expandToLeafProperties(shorthandName)) {
|
|
71
|
+
for (const word of leafProperty.split('-')) {
|
|
72
|
+
words.add(word);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
familyWordsByShorthand.set(shorthandName, words);
|
|
76
|
+
return words;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Checks whether a property might set part of the same box as a shorthand,
|
|
81
|
+
* which it does when the two names have a word in common.
|
|
82
|
+
*
|
|
83
|
+
* @param {string} property The property name to classify.
|
|
84
|
+
* @param {Set} familyWords The words of the shorthand's family.
|
|
85
|
+
* @return {boolean} Whether the property belongs to the same family.
|
|
86
|
+
*/
|
|
87
|
+
function belongsToFamily (property, familyWords) {
|
|
88
|
+
return property.split('-').some((word) => {
|
|
89
|
+
return familyWords.has(word);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @typedef {object} FoldCandidate
|
|
95
|
+
* @property {object} shorthandEntry The description of the shorthand declaration.
|
|
96
|
+
* @property {Array} overrideEntries The descriptions of the longhands that follow it.
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Finds the shorthand of a family and the longhands declared after it, which
|
|
101
|
+
* are the declarations a fold would replace with a single shorthand. A family
|
|
102
|
+
* that states its shorthand or one of its longhands twice keeps an intentional
|
|
103
|
+
* fallback, so it is left alone.
|
|
104
|
+
*
|
|
105
|
+
* @param {Array} declarations The declarations of a single rule, in source order.
|
|
106
|
+
* @param {string} shorthandName The CSS shorthand property name.
|
|
107
|
+
* @return {FoldCandidate|null} The declarations to fold, or null when there are none.
|
|
108
|
+
*/
|
|
109
|
+
function findFoldCandidate (declarations, shorthandName) {
|
|
110
|
+
const longhandProperties = new Set(shorthandMap[shorthandName]);
|
|
111
|
+
const shorthandEntries = [];
|
|
112
|
+
const overrideEntries = [];
|
|
113
|
+
const overriddenProperties = new Set();
|
|
114
|
+
let hasRepeatedOverride = false;
|
|
115
|
+
|
|
116
|
+
declarations.forEach((declaration, index) => {
|
|
117
|
+
if (declaration.property === shorthandName) {
|
|
118
|
+
shorthandEntries.push(describeDeclaration(declaration, index));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (!shorthandEntries.length || !longhandProperties.has(declaration.property)) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (overriddenProperties.has(declaration.property)) {
|
|
125
|
+
hasRepeatedOverride = true;
|
|
126
|
+
}
|
|
127
|
+
overriddenProperties.add(declaration.property);
|
|
128
|
+
overrideEntries.push(describeDeclaration(declaration, index));
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
if (shorthandEntries.length !== 1 || !overrideEntries.length || hasRepeatedOverride) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
shorthandEntry: shorthandEntries[0],
|
|
136
|
+
overrideEntries
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Checks whether a declaration lying between the shorthand and the last of its
|
|
142
|
+
* overrides would change meaning once the overrides move up to the shorthand.
|
|
143
|
+
* Only a declaration from another family is harmless, and a nested rule is
|
|
144
|
+
* never safe to step over because its own declarations may set the same box.
|
|
145
|
+
*
|
|
146
|
+
* @param {Array} declarations The declarations of a single rule, in source order.
|
|
147
|
+
* @param {string} shorthandName The CSS shorthand property name.
|
|
148
|
+
* @param {object} candidate The shorthand and the longhands that follow it.
|
|
149
|
+
* @return {boolean} Whether nothing stands in the way of the fold.
|
|
150
|
+
*/
|
|
151
|
+
function isFoldPathClear (declarations, shorthandName, candidate) {
|
|
152
|
+
const { shorthandEntry, overrideEntries } = candidate;
|
|
153
|
+
const foldedIndexes = new Set(overrideEntries.map((entry) => {
|
|
154
|
+
return entry.index;
|
|
155
|
+
}));
|
|
156
|
+
const lastOverrideIndex = overrideEntries[overrideEntries.length - 1].index;
|
|
157
|
+
const familyWords = collectFamilyWords(shorthandName);
|
|
158
|
+
|
|
159
|
+
for (let index = shorthandEntry.index + 1; index < lastOverrideIndex; index++) {
|
|
160
|
+
if (foldedIndexes.has(index)) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const { property } = declarations[index];
|
|
164
|
+
if (!property || belongsToFamily(property, familyWords)) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Determines whether a value can stand as one component of a positional
|
|
173
|
+
* shorthand. A CSS-wide keyword is only valid as a declaration's entire value,
|
|
174
|
+
* a `/` separates the two radii of a corner rather than two components, and a
|
|
175
|
+
* `var()` may expand to any number of components at computed value time.
|
|
176
|
+
*
|
|
177
|
+
* @param {string} component The value component to check.
|
|
178
|
+
* @param {object} context The minification context with registered custom property data.
|
|
179
|
+
* @return {boolean} Whether the component can be positioned in a shorthand.
|
|
180
|
+
*/
|
|
181
|
+
function isPositionalComponent (component, context) {
|
|
182
|
+
return (
|
|
183
|
+
!component.includes('/') &&
|
|
184
|
+
!CSS_WIDE_KEYWORDS.has(component.toLowerCase()) &&
|
|
185
|
+
canMergeVarValue(component, context)
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Expands the components of a positional shorthand into one value per longhand,
|
|
191
|
+
* repeating the value of the opposite side for every component the author left
|
|
192
|
+
* out, as the CSS box model rules require.
|
|
193
|
+
*
|
|
194
|
+
* @param {Array} components The value components the shorthand was written with.
|
|
195
|
+
* @param {number} longhandCount The number of longhands the shorthand sets.
|
|
196
|
+
* @return {Array} One value per longhand, in longhand order.
|
|
197
|
+
*/
|
|
198
|
+
function expandPositionalComponents (components, longhandCount) {
|
|
199
|
+
if (longhandCount === 2) {
|
|
200
|
+
const [start, end = start] = components;
|
|
201
|
+
return [start, end];
|
|
202
|
+
}
|
|
203
|
+
const [top, right = top, bottom = top, left = right] = components;
|
|
204
|
+
return [top, right, bottom, left];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Rebuilds a shorthand from its own value plus the longhands declared after it.
|
|
209
|
+
*
|
|
210
|
+
* @param {string} shorthandName The CSS shorthand property name.
|
|
211
|
+
* @param {object} candidate The shorthand and the longhands that follow it.
|
|
212
|
+
* @param {object} context The minification context with registered custom property data.
|
|
213
|
+
* @return {string|null} The rebuilt shorthand value, or null when it cannot be built.
|
|
214
|
+
*/
|
|
215
|
+
function buildFoldedValue (shorthandName, candidate, context) {
|
|
216
|
+
const { shorthandEntry, overrideEntries } = candidate;
|
|
217
|
+
const longhands = shorthandMap[shorthandName];
|
|
218
|
+
const components = splitTopLevelComponents(shorthandEntry.value);
|
|
219
|
+
if (!components.length || components.length > longhands.length) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const expandedValues = expandPositionalComponents(components, longhands.length);
|
|
224
|
+
const valueByProperty = new Map(longhands.map((property, index) => {
|
|
225
|
+
return [property, expandedValues[index]];
|
|
226
|
+
}));
|
|
227
|
+
for (const entry of overrideEntries) {
|
|
228
|
+
const overrideComponents = splitTopLevelComponents(entry.value);
|
|
229
|
+
if (overrideComponents.length !== 1) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
valueByProperty.set(entry.property, entry.value);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const cleanValues = longhands.map((property) => {
|
|
236
|
+
return valueByProperty.get(property);
|
|
237
|
+
});
|
|
238
|
+
const areAllPositional = cleanValues.every((value) => {
|
|
239
|
+
return isPositionalComponent(value, context);
|
|
240
|
+
});
|
|
241
|
+
if (!areAllPositional) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return buildShorthandValue(shorthandName, {
|
|
246
|
+
properties: longhands,
|
|
247
|
+
valueMap: valueByProperty,
|
|
248
|
+
cleanValues,
|
|
249
|
+
importantSuffix: shorthandEntry.isImportant ? '!important' : ''
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Replaces a shorthand and the longhands declared after it with the single
|
|
255
|
+
* shorthand that states the same box.
|
|
256
|
+
*
|
|
257
|
+
* @param {Array} declarations The declarations of a single rule, in source order.
|
|
258
|
+
* @param {string} shorthandName The CSS shorthand property name.
|
|
259
|
+
* @param {object} candidate The shorthand and the longhands that follow it.
|
|
260
|
+
* @param {string} foldedValue The rebuilt shorthand value.
|
|
261
|
+
* @return {Array} The declarations, with the family stated once.
|
|
262
|
+
*/
|
|
263
|
+
function applyFold (declarations, shorthandName, candidate, foldedValue) {
|
|
264
|
+
const { shorthandEntry, overrideEntries } = candidate;
|
|
265
|
+
const foldedIndexes = new Set(overrideEntries.map((entry) => {
|
|
266
|
+
return entry.index;
|
|
267
|
+
}));
|
|
268
|
+
return declarations.flatMap((declaration, index) => {
|
|
269
|
+
if (index === shorthandEntry.index) {
|
|
270
|
+
return [{
|
|
271
|
+
property: shorthandName,
|
|
272
|
+
value: foldedValue,
|
|
273
|
+
isAssembledShorthand: true
|
|
274
|
+
}];
|
|
275
|
+
}
|
|
276
|
+
if (foldedIndexes.has(index)) {
|
|
277
|
+
return [];
|
|
278
|
+
}
|
|
279
|
+
return [declaration];
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Folds the longhands that follow one shorthand back into it, when the single
|
|
285
|
+
* rebuilt shorthand is shorter than the declarations it replaces.
|
|
286
|
+
*
|
|
287
|
+
* @param {Array} declarations The declarations of a single rule, in source order.
|
|
288
|
+
* @param {string} shorthandName The CSS shorthand property name.
|
|
289
|
+
* @param {object} context The minification context with registered custom property data.
|
|
290
|
+
* @return {Array} The declarations, folded when that is shorter.
|
|
291
|
+
*/
|
|
292
|
+
function foldOverridesIntoShorthand (declarations, shorthandName, context) {
|
|
293
|
+
const candidate = findFoldCandidate(declarations, shorthandName);
|
|
294
|
+
if (!candidate) {
|
|
295
|
+
return declarations;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// A shorthand and a longhand of differing importance do not describe one
|
|
299
|
+
// cascade step, so the pair cannot be restated as a single declaration.
|
|
300
|
+
const { shorthandEntry, overrideEntries } = candidate;
|
|
301
|
+
const shareImportance = overrideEntries.every((entry) => {
|
|
302
|
+
return entry.isImportant === shorthandEntry.isImportant;
|
|
303
|
+
});
|
|
304
|
+
if (!shareImportance || !isFoldPathClear(declarations, shorthandName, candidate)) {
|
|
305
|
+
return declarations;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const foldedValue = buildFoldedValue(shorthandName, candidate, context);
|
|
309
|
+
if (!foldedValue) {
|
|
310
|
+
return declarations;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const foldedLength = (shorthandName + ':' + foldedValue).length;
|
|
314
|
+
const originalLength = [shorthandEntry.text, ...overrideEntries.map((entry) => {
|
|
315
|
+
return entry.text;
|
|
316
|
+
})].join(';').length;
|
|
317
|
+
if (foldedLength >= originalLength) {
|
|
318
|
+
return declarations;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return applyFold(declarations, shorthandName, candidate, foldedValue);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Folds every longhand that follows its own shorthand back into that shorthand.
|
|
326
|
+
* A longhand after a shorthand only overrides the one side the shorthand had
|
|
327
|
+
* already set, so `padding:10px;padding-right:20px` states the same box as
|
|
328
|
+
* `padding:10px 20px 10px 10px`, and the shorter of the two is kept.
|
|
329
|
+
*
|
|
330
|
+
* @param {Array} declarations The declarations of a single rule, in source order.
|
|
331
|
+
* @param {object} context The minification context with registered custom property data.
|
|
332
|
+
* @return {Array} The declarations, with eligible families folded.
|
|
333
|
+
*/
|
|
334
|
+
function foldLonghandOverridesIntoShorthands (declarations, context) {
|
|
335
|
+
let result = declarations;
|
|
336
|
+
for (const shorthandName of POSITIONAL_SHORTHAND_NAMES) {
|
|
337
|
+
result = foldOverridesIntoShorthand(result, shorthandName, context);
|
|
338
|
+
}
|
|
339
|
+
return result;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export { foldLonghandOverridesIntoShorthands };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Builds keyed lookups and normalized views over a rule's declarations,
|
|
3
|
+
* so the shorthand passes can ask which properties a rule sets, and what each
|
|
4
|
+
* of them minifies to, without rescanning its declarations once per property
|
|
5
|
+
* they are interested in.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { minifyValue } from '../value/minify.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {object} DeclarationDescription
|
|
12
|
+
* @property {object} declaration The original declaration object.
|
|
13
|
+
* @property {number} index The declaration's index within the rule.
|
|
14
|
+
* @property {string} property The declared property name.
|
|
15
|
+
* @property {string} text The minified `property:value` text.
|
|
16
|
+
* @property {string} value The minified value, without any `!important`.
|
|
17
|
+
* @property {boolean} isImportant Whether the declaration carries `!important`.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Describes a declaration through its minified value, which is the form the
|
|
22
|
+
* shorthand passes compare, rewrite, and measure the output length of.
|
|
23
|
+
*
|
|
24
|
+
* @param {object} declaration The CSS declaration object.
|
|
25
|
+
* @param {number} index The declaration's index within the rule.
|
|
26
|
+
* @return {DeclarationDescription} The normalized view of the declaration.
|
|
27
|
+
*/
|
|
28
|
+
function describeDeclaration (declaration, index) {
|
|
29
|
+
const minifiedValue = minifyValue(declaration);
|
|
30
|
+
return {
|
|
31
|
+
declaration,
|
|
32
|
+
index,
|
|
33
|
+
property: declaration.property,
|
|
34
|
+
text: declaration.property + ':' + minifiedValue,
|
|
35
|
+
value: minifiedValue.replace('!important', '').trim(),
|
|
36
|
+
isImportant: minifiedValue.includes('!important')
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Indexes the first declaration of each property. The first occurrence is the
|
|
42
|
+
* one a linear search would return, so this stands in for repeated scans that
|
|
43
|
+
* look a property's declaration up by name.
|
|
44
|
+
*
|
|
45
|
+
* @param {Array} declarations The declarations of a single rule, in source order.
|
|
46
|
+
* @return {Map} Map of property name to its first declaration.
|
|
47
|
+
*/
|
|
48
|
+
function indexFirstDeclarationByProperty (declarations) {
|
|
49
|
+
const declarationByProperty = new Map();
|
|
50
|
+
for (const declaration of declarations) {
|
|
51
|
+
if (declaration.property && !declarationByProperty.has(declaration.property)) {
|
|
52
|
+
declarationByProperty.set(declaration.property, declaration);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return declarationByProperty;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Collects the names of every property a rule declares.
|
|
60
|
+
*
|
|
61
|
+
* @param {Array} declarations The declarations of a single rule.
|
|
62
|
+
* @return {Set} The declared property names.
|
|
63
|
+
*/
|
|
64
|
+
function collectDeclaredProperties (declarations) {
|
|
65
|
+
const declaredProperties = new Set();
|
|
66
|
+
for (const declaration of declarations) {
|
|
67
|
+
if (declaration.property) {
|
|
68
|
+
declaredProperties.add(declaration.property);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return declaredProperties;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export {
|
|
75
|
+
collectDeclaredProperties,
|
|
76
|
+
describeDeclaration,
|
|
77
|
+
indexFirstDeclarationByProperty
|
|
78
|
+
};
|
|
@@ -8,9 +8,10 @@ import {
|
|
|
8
8
|
BORDER_EDGE_PROPERTIES,
|
|
9
9
|
CSS_WIDE_KEYWORDS,
|
|
10
10
|
EDGE_SHORTHANDS,
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
getLonghandsOf,
|
|
12
|
+
getOverridesOf
|
|
13
13
|
} from './config.js';
|
|
14
|
+
import { indexFirstDeclarationByProperty } from './lookup.js';
|
|
14
15
|
import { buildShorthandValue } from './shorthand-values.js';
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -21,67 +22,72 @@ import { buildShorthandValue } from './shorthand-values.js';
|
|
|
21
22
|
*/
|
|
22
23
|
const MIXED_IMPORTANT_SHORTHANDS = new Set(['margin', 'padding', 'inset', 'position-try']);
|
|
23
24
|
|
|
25
|
+
/**
|
|
26
|
+
* The shorthands that may be built from only some of their longhands, and the
|
|
27
|
+
* longhands each of them cannot do without. Every listed group has to be
|
|
28
|
+
* satisfied by at least one of the properties it holds, so `font` needs both a
|
|
29
|
+
* size and a family, while `background` needs a color or an image.
|
|
30
|
+
*
|
|
31
|
+
* @type {{[key: string]: Array}}
|
|
32
|
+
*/
|
|
33
|
+
const PARTIAL_MERGE_REQUIREMENTS = {
|
|
34
|
+
animation: [['animation-name'], ['animation-duration']],
|
|
35
|
+
background: [['background-color', 'background-image']],
|
|
36
|
+
'background-position': [['background-position-x'], ['background-position-y']],
|
|
37
|
+
'border-image': [['border-image-source']],
|
|
38
|
+
font: [['font-size'], ['font-family']],
|
|
39
|
+
mask: [['mask-image']]
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Checks whether a rule declares enough of a shorthand's longhands for the
|
|
44
|
+
* shorthand to be built from the subset it does declare.
|
|
45
|
+
*
|
|
46
|
+
* @param {Array} requirementGroups The groups of interchangeable longhands the shorthand requires.
|
|
47
|
+
* @param {Set} declaredProperties The property names the rule currently declares.
|
|
48
|
+
* @return {boolean} Whether every requirement group is satisfied.
|
|
49
|
+
*/
|
|
50
|
+
function meetsPartialMergeRequirements (requirementGroups, declaredProperties) {
|
|
51
|
+
return requirementGroups.every((requiredProperties) => {
|
|
52
|
+
return requiredProperties.some((property) => {
|
|
53
|
+
return declaredProperties.has(property);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
24
58
|
/**
|
|
25
59
|
* Determines which longhand properties are present and eligible for merging into a given shorthand. Returns null when the required longhands for the shorthand are not all available.
|
|
26
60
|
*
|
|
27
|
-
* @param {string} shorthand
|
|
28
|
-
* @param {Array} longhands
|
|
29
|
-
* @param {
|
|
30
|
-
* @return {Array|null}
|
|
61
|
+
* @param {string} shorthand The CSS shorthand property name.
|
|
62
|
+
* @param {Array} longhands The expected longhand property names for this shorthand.
|
|
63
|
+
* @param {Set} declaredProperties The property names the rule currently declares.
|
|
64
|
+
* @return {Array|null} The list of longhand names to merge, or null if merging is not possible.
|
|
31
65
|
*/
|
|
32
|
-
function getMergeProps (shorthand, longhands,
|
|
66
|
+
function getMergeProps (shorthand, longhands, declaredProperties) {
|
|
33
67
|
const presentLonghands = longhands.filter((longhand) => {
|
|
34
|
-
return
|
|
35
|
-
return declaration.property === longhand;
|
|
36
|
-
});
|
|
68
|
+
return declaredProperties.has(longhand);
|
|
37
69
|
});
|
|
38
70
|
if (presentLonghands.length === 0) {
|
|
39
71
|
return null;
|
|
40
72
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (
|
|
44
|
-
return presentLonghands;
|
|
45
|
-
}
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
if (shorthand === 'background-position') {
|
|
49
|
-
const hasBothAxes = presentLonghands.includes('background-position-x') && presentLonghands.includes('background-position-y');
|
|
50
|
-
if (hasBothAxes) {
|
|
51
|
-
return presentLonghands;
|
|
52
|
-
}
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
if (shorthand === 'background') {
|
|
56
|
-
const hasBackgroundProp = presentLonghands.includes('background-color') || presentLonghands.includes('background-image');
|
|
57
|
-
if (hasBackgroundProp) {
|
|
58
|
-
return presentLonghands;
|
|
59
|
-
}
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
if (shorthand === 'mask') {
|
|
63
|
-
if (presentLonghands.includes('mask-image')) {
|
|
64
|
-
return presentLonghands;
|
|
65
|
-
}
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
if (shorthand === 'border-image') {
|
|
69
|
-
if (presentLonghands.includes('border-image-source')) {
|
|
73
|
+
const requirementGroups = PARTIAL_MERGE_REQUIREMENTS[shorthand];
|
|
74
|
+
if (requirementGroups) {
|
|
75
|
+
if (meetsPartialMergeRequirements(requirementGroups, declaredProperties)) {
|
|
70
76
|
return presentLonghands;
|
|
71
77
|
}
|
|
72
78
|
return null;
|
|
73
79
|
}
|
|
74
80
|
if (shorthand === 'border') {
|
|
75
81
|
const hasAllBorderParts = (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
82
|
+
declaredProperties.has('border-width') &&
|
|
83
|
+
declaredProperties.has('border-style') &&
|
|
84
|
+
declaredProperties.has('border-color')
|
|
79
85
|
);
|
|
80
86
|
if (hasAllBorderParts) {
|
|
81
87
|
return ['border-width', 'border-style', 'border-color'];
|
|
82
88
|
}
|
|
83
89
|
const hasAllBorderEdges = BORDER_EDGE_PROPERTIES.every((edgeProperty) => {
|
|
84
|
-
return
|
|
90
|
+
return declaredProperties.has(edgeProperty);
|
|
85
91
|
});
|
|
86
92
|
if (hasAllBorderEdges) {
|
|
87
93
|
return [...BORDER_EDGE_PROPERTIES];
|
|
@@ -90,9 +96,9 @@ function getMergeProps (shorthand, longhands, declarations) {
|
|
|
90
96
|
}
|
|
91
97
|
if (shorthand === 'flex') {
|
|
92
98
|
const hasAllFlexParts = (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
99
|
+
declaredProperties.has('flex-grow') &&
|
|
100
|
+
declaredProperties.has('flex-shrink') &&
|
|
101
|
+
declaredProperties.has('flex-basis')
|
|
96
102
|
);
|
|
97
103
|
if (hasAllFlexParts) {
|
|
98
104
|
return ['flex-grow', 'flex-shrink', 'flex-basis'];
|
|
@@ -150,13 +156,12 @@ function canMergeVarValue (value, context) {
|
|
|
150
156
|
* @return {boolean} True when the shorthand only affects the merged longhands.
|
|
151
157
|
*/
|
|
152
158
|
function shorthandAffectsOnlyMergedLonghands (shorthandName, properties) {
|
|
153
|
-
|
|
154
|
-
if (overrides.length) {
|
|
159
|
+
if (getOverridesOf(shorthandName).size) {
|
|
155
160
|
return false;
|
|
156
161
|
}
|
|
157
|
-
const
|
|
158
|
-
return
|
|
159
|
-
return
|
|
162
|
+
const mergedProperties = new Set(properties);
|
|
163
|
+
return [...getLonghandsOf(shorthandName)].every((longhand) => {
|
|
164
|
+
return mergedProperties.has(longhand);
|
|
160
165
|
});
|
|
161
166
|
}
|
|
162
167
|
|
|
@@ -196,10 +201,9 @@ function resolveCssWideKeywordMerge (values, shorthandName, properties) {
|
|
|
196
201
|
* @return {Array|null} The minified longhand values, or null when one is missing.
|
|
197
202
|
*/
|
|
198
203
|
function collectLonghandValues (properties, declarations) {
|
|
204
|
+
const declarationByProperty = indexFirstDeclarationByProperty(declarations);
|
|
199
205
|
const values = properties.map((property) => {
|
|
200
|
-
const declaration =
|
|
201
|
-
return candidate.property === property;
|
|
202
|
-
});
|
|
206
|
+
const declaration = declarationByProperty.get(property);
|
|
203
207
|
if (declaration) {
|
|
204
208
|
return minifyValue(declaration);
|
|
205
209
|
}
|
|
@@ -318,6 +322,7 @@ function tryMergeToShorthand (properties, declarations, shorthandName = '', cont
|
|
|
318
322
|
}
|
|
319
323
|
|
|
320
324
|
export {
|
|
325
|
+
canMergeVarValue,
|
|
321
326
|
getMergeProps,
|
|
322
327
|
tryMergeToShorthand
|
|
323
328
|
};
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
shorthandMap,
|
|
7
7
|
shorthandOverrideMap
|
|
8
8
|
} from './config.js';
|
|
9
|
+
import { collectDeclaredProperties } from './lookup.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Pairs of properties where the first must be emitted before the second, because
|
|
@@ -15,6 +16,10 @@ import {
|
|
|
15
16
|
* @type {Array}
|
|
16
17
|
*/
|
|
17
18
|
const REQUIRED_PROPERTY_ORDER = [
|
|
19
|
+
['animation', 'animation-timeline'],
|
|
20
|
+
['animation', 'animation-range'],
|
|
21
|
+
['animation', 'animation-range-start'],
|
|
22
|
+
['animation', 'animation-range-end'],
|
|
18
23
|
['border', 'border-image'],
|
|
19
24
|
['font', 'font-feature-settings'],
|
|
20
25
|
['font', 'font-variant-ligatures'],
|
|
@@ -40,8 +45,16 @@ function orderDeclarations (declarations) {
|
|
|
40
45
|
return declaration?.property === property;
|
|
41
46
|
});
|
|
42
47
|
};
|
|
48
|
+
// Most rules declare neither half of any of these pairs, so the properties a
|
|
49
|
+
// rule does declare are gathered once rather than scanned for per pair.
|
|
50
|
+
// Reordering the declarations never changes which properties are declared,
|
|
51
|
+
// so the set stays accurate as the pairs are applied.
|
|
52
|
+
const declaredProperties = collectDeclaredProperties(ordered);
|
|
43
53
|
|
|
44
54
|
for (const [property, followingProperty] of REQUIRED_PROPERTY_ORDER) {
|
|
55
|
+
if (!declaredProperties.has(property) || !declaredProperties.has(followingProperty)) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
45
58
|
const fromIndex = findPropertyIndex(property);
|
|
46
59
|
const toIndex = findPropertyIndex(followingProperty);
|
|
47
60
|
if (fromIndex === -1 || toIndex === -1 || fromIndex < toIndex) {
|
|
@@ -55,20 +68,46 @@ function orderDeclarations (declarations) {
|
|
|
55
68
|
}
|
|
56
69
|
|
|
57
70
|
/**
|
|
58
|
-
*
|
|
71
|
+
* The overridden longhands of each shorthand, computed on first use. The
|
|
72
|
+
* shorthand tables never change, so the answer for a property name is the same
|
|
73
|
+
* every time it is asked for.
|
|
74
|
+
*
|
|
75
|
+
* @type {Map<string, Set<string>>}
|
|
76
|
+
*/
|
|
77
|
+
const overriddenLonghandsByShorthand = new Map();
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Collects all longhands that a shorthand would override.
|
|
59
81
|
*
|
|
60
82
|
* @param {string} shorthandProperty The CSS shorthand property name.
|
|
61
|
-
* @return {
|
|
83
|
+
* @return {Set} The longhand property names the shorthand overrides, including nested longhands.
|
|
62
84
|
*/
|
|
63
|
-
function
|
|
85
|
+
function collectOverriddenLonghands (shorthandProperty) {
|
|
64
86
|
const direct = shorthandMap[shorthandProperty] || [];
|
|
65
87
|
const overrides = shorthandOverrideMap[shorthandProperty] || [];
|
|
66
|
-
const all = [...direct, ...overrides];
|
|
88
|
+
const all = new Set([...direct, ...overrides]);
|
|
67
89
|
for (const property of direct) {
|
|
68
90
|
const nested = shorthandMap[property] || [];
|
|
69
|
-
|
|
91
|
+
for (const nestedProperty of nested) {
|
|
92
|
+
all.add(nestedProperty);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return all;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get all longhands that a shorthand would override.
|
|
100
|
+
*
|
|
101
|
+
* @param {string} shorthandProperty The CSS shorthand property name.
|
|
102
|
+
* @return {Set} The longhand property names that the shorthand overrides, including nested longhands.
|
|
103
|
+
*/
|
|
104
|
+
function getOverriddenLonghands (shorthandProperty) {
|
|
105
|
+
let overridden = overriddenLonghandsByShorthand.get(shorthandProperty);
|
|
106
|
+
if (!overridden) {
|
|
107
|
+
overridden = collectOverriddenLonghands(shorthandProperty);
|
|
108
|
+
overriddenLonghandsByShorthand.set(shorthandProperty, overridden);
|
|
70
109
|
}
|
|
71
|
-
return
|
|
110
|
+
return overridden;
|
|
72
111
|
}
|
|
73
112
|
|
|
74
113
|
export {
|