postcss-merge-idents 9.0.2 → 9.0.4
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 +5 -3
- package/src/data/mergeIdents.json +58 -0
- package/src/index.js +233 -100
- package/src/lib/grammar.js +271 -0
- package/src/lib/layerRegistry.js +224 -0
- package/src/lib/valueRewriter.js +258 -0
- package/types/index.d.ts +20 -0
- package/types/index.d.ts.map +1 -1
- package/types/lib/grammar.d.ts +43 -0
- package/types/lib/grammar.d.ts.map +1 -0
- package/types/lib/layerRegistry.d.ts +74 -0
- package/types/lib/layerRegistry.d.ts.map +1 -0
- package/types/lib/valueRewriter.d.ts +17 -0
- package/types/lib/valueRewriter.d.ts.map +1 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { TokenType } from '@csstools/css-tokenizer';
|
|
2
|
+
import cssnanoUtils from 'cssnano-utils';
|
|
3
|
+
import specData from '../data/mergeIdents.json' with { type: 'json' };
|
|
4
|
+
|
|
5
|
+
const { asciiLowerCase, decoded, tokens } = cssnanoUtils;
|
|
6
|
+
|
|
7
|
+
const VENDOR_PREFIX = /^-(webkit|moz|o)-/v;
|
|
8
|
+
|
|
9
|
+
const CSS_WIDE_KEYWORDS = new Set(specData.cssWideKeywords);
|
|
10
|
+
|
|
11
|
+
const KEYFRAMES_NAME_RESERVED = new Set([
|
|
12
|
+
...CSS_WIDE_KEYWORDS,
|
|
13
|
+
'default',
|
|
14
|
+
'none',
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
// css-animations-2 spells <single-animation-composition> only in the
|
|
18
|
+
// animation-composition property grammar; the shorthand grammar webref gives
|
|
19
|
+
// does not reach it yet, so these keywords stay hand-written.
|
|
20
|
+
const ANIMATION_COMPOSITION_KEYWORDS = new Set([
|
|
21
|
+
'accumulate',
|
|
22
|
+
'add',
|
|
23
|
+
'replace',
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
const KEYFRAMES_SHORTHAND_KEYWORDS = new Set([
|
|
27
|
+
...CSS_WIDE_KEYWORDS,
|
|
28
|
+
...specData.keyframes.shorthandKeywords,
|
|
29
|
+
...ANIMATION_COMPOSITION_KEYWORDS,
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
const COUNTER_STYLE_NAME_RESERVED = new Set([
|
|
33
|
+
...CSS_WIDE_KEYWORDS,
|
|
34
|
+
'default',
|
|
35
|
+
'decimal',
|
|
36
|
+
'none',
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
// The predefined counter style names are specified in prose (CSS Counter
|
|
40
|
+
// Styles Level 3 §6) rather than in any grammar, so webref cannot list them.
|
|
41
|
+
const PREDEFINED_COUNTER_STYLES = [
|
|
42
|
+
'arabic-indic',
|
|
43
|
+
'armenian',
|
|
44
|
+
'bengali',
|
|
45
|
+
'cambodian',
|
|
46
|
+
'circle',
|
|
47
|
+
'cjk-decimal',
|
|
48
|
+
'cjk-earthly-branch',
|
|
49
|
+
'cjk-heavenly-stem',
|
|
50
|
+
'cjk-ideographic',
|
|
51
|
+
'decimal',
|
|
52
|
+
'decimal-leading-zero',
|
|
53
|
+
'devanagari',
|
|
54
|
+
'disc',
|
|
55
|
+
'disclosure-closed',
|
|
56
|
+
'disclosure-open',
|
|
57
|
+
'ethiopic-numeric',
|
|
58
|
+
'georgian',
|
|
59
|
+
'gujarati',
|
|
60
|
+
'gurmukhi',
|
|
61
|
+
'hebrew',
|
|
62
|
+
'hiragana',
|
|
63
|
+
'hiragana-iroha',
|
|
64
|
+
'japanese-formal',
|
|
65
|
+
'japanese-informal',
|
|
66
|
+
'kannada',
|
|
67
|
+
'katakana',
|
|
68
|
+
'katakana-iroha',
|
|
69
|
+
'khmer',
|
|
70
|
+
'korean-hangul-formal',
|
|
71
|
+
'korean-hanja-formal',
|
|
72
|
+
'korean-hanja-informal',
|
|
73
|
+
'lao',
|
|
74
|
+
'lower-alpha',
|
|
75
|
+
'lower-armenian',
|
|
76
|
+
'lower-greek',
|
|
77
|
+
'lower-latin',
|
|
78
|
+
'lower-roman',
|
|
79
|
+
'malayalam',
|
|
80
|
+
'mongolian',
|
|
81
|
+
'myanmar',
|
|
82
|
+
'oriya',
|
|
83
|
+
'persian',
|
|
84
|
+
'simp-chinese-formal',
|
|
85
|
+
'simp-chinese-informal',
|
|
86
|
+
'square',
|
|
87
|
+
'tamil',
|
|
88
|
+
'telugu',
|
|
89
|
+
'thai',
|
|
90
|
+
'tibetan',
|
|
91
|
+
'trad-chinese-formal',
|
|
92
|
+
'trad-chinese-informal',
|
|
93
|
+
'upper-alpha',
|
|
94
|
+
'upper-armenian',
|
|
95
|
+
'upper-latin',
|
|
96
|
+
'upper-roman',
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
const COUNTER_STYLE_RESERVED = new Set([
|
|
100
|
+
...CSS_WIDE_KEYWORDS,
|
|
101
|
+
...specData.counterStyle.keywords,
|
|
102
|
+
...PREDEFINED_COUNTER_STYLES,
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
const COUNTER_STYLE_FUNCTIONS = new Map(
|
|
106
|
+
Object.entries(specData.counterStyle.functions).map(([name, args]) => [
|
|
107
|
+
// Stylesheets spell a function without the trailing `()` webref names it
|
|
108
|
+
// by, preserving the historical lookup behavior.
|
|
109
|
+
name.slice(0, -2),
|
|
110
|
+
args,
|
|
111
|
+
])
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const CONDITIONAL_GROUP_RULES = new Set([
|
|
115
|
+
'media',
|
|
116
|
+
'supports',
|
|
117
|
+
'container',
|
|
118
|
+
'document',
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* @param {string} name
|
|
123
|
+
* @return {boolean}
|
|
124
|
+
*/
|
|
125
|
+
function isConditionalGroupRule(name) {
|
|
126
|
+
const lower = asciiLowerCase(name);
|
|
127
|
+
return CONDITIONAL_GROUP_RULES.has(lower) || lower.endsWith('document');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @param {string} params
|
|
132
|
+
* @param {string} atRuleName
|
|
133
|
+
* @return {{ isString: boolean, isReservedName: boolean, key: string, tokenText: string } | null}
|
|
134
|
+
*/
|
|
135
|
+
function parseAtRuleName(params, atRuleName) {
|
|
136
|
+
const trimmed = params.trim();
|
|
137
|
+
if (!trimmed) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
const tokenList = tokens(trimmed).filter(
|
|
141
|
+
(t) => t[0] !== TokenType.Whitespace && t[0] !== TokenType.Comment
|
|
142
|
+
);
|
|
143
|
+
if (tokenList.length !== 1) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
const isCounterStyle = atRuleName.endsWith('counter-style');
|
|
147
|
+
const first = tokenList[0];
|
|
148
|
+
if (first[0] === TokenType.Ident) {
|
|
149
|
+
const val = decoded(first);
|
|
150
|
+
const lower = asciiLowerCase(val);
|
|
151
|
+
const isReserved = isCounterStyle
|
|
152
|
+
? COUNTER_STYLE_NAME_RESERVED.has(lower)
|
|
153
|
+
: KEYFRAMES_NAME_RESERVED.has(lower);
|
|
154
|
+
if (isReserved) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
const isReservedName = isCounterStyle
|
|
158
|
+
? COUNTER_STYLE_RESERVED.has(lower)
|
|
159
|
+
: KEYFRAMES_SHORTHAND_KEYWORDS.has(lower) || val.startsWith('--');
|
|
160
|
+
return {
|
|
161
|
+
isString: false,
|
|
162
|
+
isReservedName,
|
|
163
|
+
key: `ident:${val}`,
|
|
164
|
+
tokenText: first[1],
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
if (!isCounterStyle && first[0] === TokenType.String) {
|
|
168
|
+
const val = /** @type {{value: string}} */ (first[4]).value;
|
|
169
|
+
return {
|
|
170
|
+
isString: true,
|
|
171
|
+
isReservedName: false,
|
|
172
|
+
key: `str:${val}`,
|
|
173
|
+
tokenText: first[1],
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @param {import('postcss').Declaration} decl
|
|
181
|
+
* @return {{ targetAtRuleName: string, kind: 'animation-shorthand' | 'animation-name' | 'counter-style' | 'counter-style-func' } | null}
|
|
182
|
+
*/
|
|
183
|
+
function classifyDeclaration(decl) {
|
|
184
|
+
const prop = asciiLowerCase(decl.prop);
|
|
185
|
+
if (prop.startsWith('--')) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const prefixMatch = prop.match(VENDOR_PREFIX);
|
|
190
|
+
const prefix = prefixMatch ? prefixMatch[0] : '';
|
|
191
|
+
const unprefixed = prefix ? prop.slice(prefix.length) : prop;
|
|
192
|
+
|
|
193
|
+
if (unprefixed === 'animation') {
|
|
194
|
+
return {
|
|
195
|
+
targetAtRuleName: `${prefix}keyframes`,
|
|
196
|
+
kind: 'animation-shorthand',
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
if (unprefixed === 'animation-name') {
|
|
200
|
+
return { targetAtRuleName: `${prefix}keyframes`, kind: 'animation-name' };
|
|
201
|
+
}
|
|
202
|
+
if (unprefixed === 'list-style' || unprefixed === 'list-style-type') {
|
|
203
|
+
return { targetAtRuleName: 'counter-style', kind: 'counter-style' };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (
|
|
207
|
+
decl.parent?.type === 'atrule' &&
|
|
208
|
+
asciiLowerCase(decl.parent.name).endsWith('counter-style')
|
|
209
|
+
) {
|
|
210
|
+
if (prop === 'system' || prop === 'fallback' || prop === 'speak-as') {
|
|
211
|
+
return {
|
|
212
|
+
targetAtRuleName: asciiLowerCase(decl.parent.name),
|
|
213
|
+
kind: 'counter-style',
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (
|
|
219
|
+
unprefixed === 'content' ||
|
|
220
|
+
unprefixed === 'bookmark-label' ||
|
|
221
|
+
unprefixed === 'copy-into' ||
|
|
222
|
+
unprefixed === 'string-set'
|
|
223
|
+
) {
|
|
224
|
+
return { targetAtRuleName: 'counter-style', kind: 'counter-style-func' };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* @param {import('postcss').Node} node
|
|
232
|
+
* @return {import('postcss').Container}
|
|
233
|
+
*/
|
|
234
|
+
function getContainer(node) {
|
|
235
|
+
let curr = node.parent;
|
|
236
|
+
while (curr && curr.type !== 'root') {
|
|
237
|
+
if (
|
|
238
|
+
curr.type === 'atrule' &&
|
|
239
|
+
isConditionalGroupRule(
|
|
240
|
+
/** @type {import('postcss').AtRule} */ (curr).name
|
|
241
|
+
)
|
|
242
|
+
) {
|
|
243
|
+
return curr;
|
|
244
|
+
}
|
|
245
|
+
curr = curr.parent;
|
|
246
|
+
}
|
|
247
|
+
return curr ?? node.root();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* @param {{ rule: import('postcss').AtRule, body?: string }} entry
|
|
252
|
+
* @return {string}
|
|
253
|
+
*/
|
|
254
|
+
function getBody(entry) {
|
|
255
|
+
if (entry.body === undefined) {
|
|
256
|
+
entry.body = entry.rule.nodes ? entry.rule.nodes.toString() : '';
|
|
257
|
+
}
|
|
258
|
+
return entry.body;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export {
|
|
262
|
+
CSS_WIDE_KEYWORDS,
|
|
263
|
+
KEYFRAMES_SHORTHAND_KEYWORDS,
|
|
264
|
+
COUNTER_STYLE_RESERVED,
|
|
265
|
+
COUNTER_STYLE_FUNCTIONS,
|
|
266
|
+
isConditionalGroupRule,
|
|
267
|
+
parseAtRuleName,
|
|
268
|
+
classifyDeclaration,
|
|
269
|
+
getContainer,
|
|
270
|
+
getBody,
|
|
271
|
+
};
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { TokenType } from '@csstools/css-tokenizer';
|
|
2
|
+
import cssnanoUtils from 'cssnano-utils';
|
|
3
|
+
|
|
4
|
+
const { asciiLowerCase, decoded, tokens } = cssnanoUtils;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Parses a @layer parameter list into decoded layer-name paths. A period is a
|
|
8
|
+
* separator only as a Delim token between idents; an escaped dot is part of
|
|
9
|
+
* the preceding identifier's value, so `a\.b` is one segment named "a.b".
|
|
10
|
+
* Whitespace and comments around separators are allowed, preserving the
|
|
11
|
+
* previous handling of `a . b`.
|
|
12
|
+
*
|
|
13
|
+
* @param {string} params
|
|
14
|
+
* @return {string[][] | null} one decoded segment path per declared name
|
|
15
|
+
*/
|
|
16
|
+
function parseLayerNameList(params) {
|
|
17
|
+
const tokenList = tokens(params).filter(
|
|
18
|
+
(t) => t[0] !== TokenType.Whitespace && t[0] !== TokenType.Comment
|
|
19
|
+
);
|
|
20
|
+
/** @type {string[][]} */
|
|
21
|
+
const names = [];
|
|
22
|
+
/** @type {string[] | null} */
|
|
23
|
+
let current = null;
|
|
24
|
+
let expectIdent = true;
|
|
25
|
+
for (const token of tokenList) {
|
|
26
|
+
const type = token[0];
|
|
27
|
+
if (type === TokenType.Comma) {
|
|
28
|
+
if (expectIdent || !current) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
names.push(current);
|
|
32
|
+
current = null;
|
|
33
|
+
expectIdent = true;
|
|
34
|
+
} else if (type === TokenType.Delim && token[1] === '.') {
|
|
35
|
+
if (expectIdent || !current) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
expectIdent = true;
|
|
39
|
+
} else if (type === TokenType.Ident && expectIdent) {
|
|
40
|
+
current ??= [];
|
|
41
|
+
current.push(decoded(token));
|
|
42
|
+
expectIdent = false;
|
|
43
|
+
} else {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (expectIdent) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
if (current) {
|
|
51
|
+
names.push(current);
|
|
52
|
+
}
|
|
53
|
+
return names;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {import('postcss').Node} node
|
|
58
|
+
* @return {import('postcss').AtRule[]} enclosing @layer at-rules, outermost first
|
|
59
|
+
*/
|
|
60
|
+
function enclosingLayerRules(node) {
|
|
61
|
+
/** @type {import('postcss').AtRule[]} */
|
|
62
|
+
const chain = [];
|
|
63
|
+
let curr = node.parent;
|
|
64
|
+
while (curr && curr.type !== 'root') {
|
|
65
|
+
if (
|
|
66
|
+
curr.type === 'atrule' &&
|
|
67
|
+
asciiLowerCase(/** @type {import('postcss').AtRule} */ (curr).name) ===
|
|
68
|
+
'layer'
|
|
69
|
+
) {
|
|
70
|
+
chain.unshift(/** @type {import('postcss').AtRule} */ (curr));
|
|
71
|
+
}
|
|
72
|
+
curr = curr.parent;
|
|
73
|
+
}
|
|
74
|
+
return chain;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
class LayerRegistry {
|
|
78
|
+
constructor() {
|
|
79
|
+
this.nextIndex = 0;
|
|
80
|
+
/** @type {Map<string, { index: number, children: LayerRegistry }>} */
|
|
81
|
+
this.layers = new Map();
|
|
82
|
+
/** @type {Map<import('postcss').AtRule, { index: number, children: LayerRegistry }>} */
|
|
83
|
+
this.anonymous = new Map();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {string} name
|
|
88
|
+
* @return {{ index: number, children: LayerRegistry }}
|
|
89
|
+
*/
|
|
90
|
+
getOrCreate(name) {
|
|
91
|
+
let entry = this.layers.get(name);
|
|
92
|
+
if (!entry) {
|
|
93
|
+
entry = { index: this.nextIndex++, children: new LayerRegistry() };
|
|
94
|
+
this.layers.set(name, entry);
|
|
95
|
+
}
|
|
96
|
+
return entry;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Entry for a layer with no name or an unparseable name: every occurrence
|
|
101
|
+
* is a unique cascade layer, but names nested inside still order beneath
|
|
102
|
+
* it through the child registry.
|
|
103
|
+
*
|
|
104
|
+
* @param {import('postcss').AtRule} layerRule
|
|
105
|
+
* @return {{ index: number, children: LayerRegistry }}
|
|
106
|
+
*/
|
|
107
|
+
getOrCreateAnonymous(layerRule) {
|
|
108
|
+
let entry = this.anonymous.get(layerRule);
|
|
109
|
+
if (!entry) {
|
|
110
|
+
entry = { index: this.nextIndex++, children: new LayerRegistry() };
|
|
111
|
+
this.anonymous.set(layerRule, entry);
|
|
112
|
+
}
|
|
113
|
+
return entry;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The registry entry a single layer at-rule occupies, creating missing
|
|
118
|
+
* segments as needed. Statement lists and unparseable names fall back to a
|
|
119
|
+
* unique anonymous entry so they order but never match another spelling.
|
|
120
|
+
*
|
|
121
|
+
* @param {import('postcss').AtRule} layerRule
|
|
122
|
+
* @return {{ index: number, children: LayerRegistry }}
|
|
123
|
+
*/
|
|
124
|
+
entryFor(layerRule) {
|
|
125
|
+
const names = layerRule.params.trim()
|
|
126
|
+
? parseLayerNameList(layerRule.params)
|
|
127
|
+
: null;
|
|
128
|
+
if (names === null || names.length !== 1) {
|
|
129
|
+
return this.getOrCreateAnonymous(layerRule);
|
|
130
|
+
}
|
|
131
|
+
let entry = this.getOrCreate(names[0][0]);
|
|
132
|
+
for (let i = 1; i < names[0].length; i++) {
|
|
133
|
+
entry = entry.children.getOrCreate(names[0][i]);
|
|
134
|
+
}
|
|
135
|
+
return entry;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Registers the layers a @layer at-rule declares, in document order and
|
|
140
|
+
* nested beneath any enclosing layers, so later lookups observe the real
|
|
141
|
+
* layer tree.
|
|
142
|
+
*
|
|
143
|
+
* @param {import('postcss').AtRule} layerRule
|
|
144
|
+
* @return {void}
|
|
145
|
+
*/
|
|
146
|
+
declareLayerAtRule(layerRule) {
|
|
147
|
+
/** @type {LayerRegistry} */
|
|
148
|
+
let current = this;
|
|
149
|
+
for (const parent of enclosingLayerRules(layerRule)) {
|
|
150
|
+
current = current.entryFor(parent).children;
|
|
151
|
+
}
|
|
152
|
+
if (!layerRule.params.trim()) {
|
|
153
|
+
// An unnamed block layer is itself a unique anonymous layer; register
|
|
154
|
+
// it so nested names order beneath it.
|
|
155
|
+
if (layerRule.nodes) {
|
|
156
|
+
current.getOrCreateAnonymous(layerRule);
|
|
157
|
+
}
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const names = parseLayerNameList(layerRule.params);
|
|
161
|
+
if (names === null) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
for (const segments of names) {
|
|
165
|
+
let entry = current.getOrCreate(segments[0]);
|
|
166
|
+
for (let i = 1; i < segments.length; i++) {
|
|
167
|
+
entry = entry.children.getOrCreate(segments[i]);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* @param {import('postcss').Node} node
|
|
174
|
+
* @return {number[]}
|
|
175
|
+
*/
|
|
176
|
+
getPriority(node) {
|
|
177
|
+
const chain = enclosingLayerRules(node);
|
|
178
|
+
if (chain.length === 0) {
|
|
179
|
+
return [Infinity];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** @type {number[]} */
|
|
183
|
+
const priority = [];
|
|
184
|
+
/** @type {LayerRegistry} */
|
|
185
|
+
let current = this;
|
|
186
|
+
for (const layerRule of chain) {
|
|
187
|
+
const entry = current.entryFor(layerRule);
|
|
188
|
+
priority.push(entry.index);
|
|
189
|
+
current = entry.children;
|
|
190
|
+
}
|
|
191
|
+
priority.push(Infinity);
|
|
192
|
+
return priority;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @param {number[]} p1
|
|
198
|
+
* @param {number[]} p2
|
|
199
|
+
* @return {number}
|
|
200
|
+
*/
|
|
201
|
+
function compareLayerPriority(p1, p2) {
|
|
202
|
+
const len = Math.min(p1.length, p2.length);
|
|
203
|
+
for (let i = 0; i < len; i++) {
|
|
204
|
+
if (p1[i] !== p2[i]) {
|
|
205
|
+
return p1[i] - p2[i];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return p1.length - p2.length;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* @param {{ layerPriority: number[], documentIndex: number }} a
|
|
213
|
+
* @param {{ layerPriority: number[], documentIndex: number }} b
|
|
214
|
+
* @return {number}
|
|
215
|
+
*/
|
|
216
|
+
function compareAtRulePriority(a, b) {
|
|
217
|
+
const layerCmp = compareLayerPriority(a.layerPriority, b.layerPriority);
|
|
218
|
+
if (layerCmp !== 0) {
|
|
219
|
+
return layerCmp;
|
|
220
|
+
}
|
|
221
|
+
return a.documentIndex - b.documentIndex;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export { LayerRegistry, compareAtRulePriority };
|