@node-projects/web-component-designer 0.1.331 → 0.1.334

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.
@@ -1,168 +1,452 @@
1
- var ParseState;
2
- (function (ParseState) {
3
- ParseState[ParseState["none"] = 0] = "none";
4
- ParseState[ParseState["parseName"] = 1] = "parseName";
5
- ParseState[ParseState["parseAttribute"] = 2] = "parseAttribute";
6
- ParseState[ParseState["parseInFunc"] = 3] = "parseInFunc";
7
- ParseState[ParseState["parseNameOrPseudo"] = 4] = "parseNameOrPseudo";
8
- })(ParseState || (ParseState = {}));
1
+ // Char codes used throughout
2
+ const CH_HASH = 35; // #
3
+ const CH_DOT = 46; // .
4
+ const CH_COLON = 58; // :
5
+ const CH_LBRACKET = 91; // [
6
+ const CH_RBRACKET = 93; // ]
7
+ const CH_LPAREN = 40; // (
8
+ const CH_RPAREN = 41; // )
9
+ const CH_COMMA = 44; // ,
10
+ const CH_BACKSLASH = 92; // \
11
+ const CH_STAR = 42; // *
12
+ const CH_PIPE = 124; // |
13
+ const CH_SPACE = 32; // ' '
14
+ const CH_GT = 62; // >
15
+ const CH_PLUS = 43; // +
16
+ const CH_TILDE = 126; // ~
17
+ const CH_SQUOTE = 39; // '
18
+ const CH_DQUOTE = 34; // "
19
+ const CH_UNDERSCORE = 95; // _
20
+ const CH_DASH = 45; // -
9
21
  export function calculateSpecificity(selector) {
10
- return calculateSpecificityInternal(selector, 0)[0];
22
+ const spec = { A: 0, B: 0, C: 0 };
23
+ // Fast path: simple selectors without pseudo-classes, attributes, functions, commas, or escapes
24
+ if (!needsFullParse(selector)) {
25
+ calcSimple(selector, spec);
26
+ return spec;
27
+ }
28
+ parseSelectorList(selector, 0, spec);
29
+ return spec;
30
+ }
31
+ function needsFullParse(selector) {
32
+ for (let i = 0; i < selector.length; i++) {
33
+ const c = selector.charCodeAt(i);
34
+ if (c === CH_COLON || c === CH_LBRACKET || c === CH_LPAREN || c === CH_COMMA || c === CH_BACKSLASH)
35
+ return true;
36
+ }
37
+ return false;
11
38
  }
12
- function calculateSpecificityInternal(selector, startIndex) {
13
- let s = { A: 0, B: 0, C: 0 };
14
- let parseState = ParseState.none;
15
- for (let n = startIndex; n < selector.length; n++) {
16
- let c = selector[n];
17
- if (parseState === ParseState.parseInFunc) {
18
- if (c == ')') {
19
- parseState = ParseState.none;
39
+ function calcSimple(selector, spec) {
40
+ const len = selector.length;
41
+ let i = 0;
42
+ while (i < len) {
43
+ const c = selector.charCodeAt(i);
44
+ // Column combinator ||
45
+ if (c === CH_PIPE && i + 1 < len && selector.charCodeAt(i + 1) === CH_PIPE) {
46
+ i += 2;
47
+ continue;
48
+ }
49
+ if (c === CH_SPACE || c === CH_GT || c === CH_PLUS || c === CH_TILDE) {
50
+ i++;
51
+ continue;
52
+ }
53
+ if (c === CH_HASH) {
54
+ spec.A++;
55
+ i++;
56
+ while (i < len && isIdentCC(selector.charCodeAt(i)))
57
+ i++;
58
+ continue;
59
+ }
60
+ if (c === CH_DOT) {
61
+ spec.B++;
62
+ i++;
63
+ while (i < len && isIdentCC(selector.charCodeAt(i)))
64
+ i++;
65
+ continue;
66
+ }
67
+ if (c === CH_STAR) {
68
+ i++;
69
+ continue;
70
+ }
71
+ if (isIdentStartCC(c) || c === CH_PIPE) {
72
+ while (i < len) {
73
+ const cc = selector.charCodeAt(i);
74
+ if (isIdentCC(cc) || cc === CH_PIPE)
75
+ i++;
76
+ else
77
+ break;
20
78
  }
79
+ spec.C++;
80
+ continue;
21
81
  }
22
- else if (parseState === ParseState.parseAttribute) {
23
- if (c == ']') {
24
- parseState = ParseState.none;
82
+ i++;
83
+ }
84
+ }
85
+ /* ---- Full parser (used when selector contains :, [, (, \, or ,) ---- */
86
+ function parseSelectorList(input, start, spec) {
87
+ let i = start;
88
+ let tA = 0, tB = 0, tC = 0;
89
+ while (i < input.length) {
90
+ tA = tB = tC = 0;
91
+ i = parseSelector(input, i, spec, true);
92
+ // parseSelector wrote into spec; grab those values as temp then reset
93
+ tA = spec.A;
94
+ tB = spec.B;
95
+ tC = spec.C;
96
+ // On the very first iteration we just keep the values.
97
+ // On subsequent iterations we pick the most specific (lexicographic).
98
+ // To avoid extra bookkeeping we always overwrite spec and compare below.
99
+ if (input.charCodeAt(i) === CH_COMMA) {
100
+ i++;
101
+ // Need to parse more selectors — save best so far
102
+ let bestA = tA, bestB = tB, bestC = tC;
103
+ while (i < input.length) {
104
+ spec.A = spec.B = spec.C = 0;
105
+ i = parseSelector(input, i, spec, true);
106
+ if (spec.A > bestA || (spec.A === bestA && (spec.B > bestB || (spec.B === bestB && spec.C > bestC)))) {
107
+ bestA = spec.A;
108
+ bestB = spec.B;
109
+ bestC = spec.C;
110
+ }
111
+ if (input.charCodeAt(i) === CH_COMMA) {
112
+ i++;
113
+ continue;
114
+ }
115
+ break;
25
116
  }
117
+ spec.A = bestA;
118
+ spec.B = bestB;
119
+ spec.C = bestC;
26
120
  }
27
- else {
28
- switch (c) {
29
- case '#':
30
- s.A++;
31
- parseState = ParseState.parseName;
32
- break;
33
- case '.':
34
- s.B++;
35
- parseState = ParseState.parseName;
36
- break;
37
- case '[':
38
- s.B++;
39
- parseState = ParseState.parseAttribute;
40
- break;
41
- case '(':
42
- break;
43
- case ')':
44
- return [s, n];
45
- case ',':
46
- return [s, n];
47
- case ':':
48
- if (selector[n + 1] === ':') {
49
- s.C++;
50
- parseState = ParseState.parseName;
51
- }
52
- else {
53
- if (selector.substring(n + 1, n + 4) === 'is(') {
54
- parseState = ParseState.none;
55
- n += 4;
56
- const res = getMaxSpecificityFromSelectorList(selector, n);
57
- n = res[1];
58
- s.A += res[0].A;
59
- s.B += res[0].B;
60
- s.C += res[0].C;
61
- }
62
- else if (selector.substring(n + 1, n + 5) === 'has(') {
63
- n += 5;
64
- const res = getMaxSpecificityFromSelectorList(selector, n);
65
- n = res[1];
66
- s.A += res[0].A;
67
- s.B += res[0].B;
68
- s.C += res[0].C;
69
- }
70
- else if (selector.substring(n + 1, n + 5) === 'not(') {
71
- n += 5;
72
- const res = getMaxSpecificityFromSelectorList(selector, n);
73
- n = res[1];
74
- s.A += res[0].A;
75
- s.B += res[0].B;
76
- s.C += res[0].C;
77
- }
78
- else if (selector.substring(n + 1, n + 11) === 'nth-child(') {
79
- s.B++;
80
- n += 11;
81
- const res = getMaxSpecificityFromSelectorList(selector, n);
82
- n = res[1];
83
- s.A += res[0].A;
84
- s.B += res[0].B;
85
- s.C += res[0].C;
86
- }
87
- else if (selector.substring(n + 1, n + 16) === 'nth-last-child(') {
88
- s.B++;
89
- n += 16;
90
- const res = getMaxSpecificityFromSelectorList(selector, n);
91
- n = res[1];
92
- s.A += res[0].A;
93
- s.B += res[0].B;
94
- s.C += res[0].C;
95
- }
96
- else if (selector.substring(n + 1, n + 6) === 'host(') {
97
- s.B++;
98
- n += 6;
99
- const res = calculateSpecificityInternal(selector, n);
100
- n = res[1];
101
- s.A += res[0].A;
102
- s.B += res[0].B;
103
- s.C += res[0].C;
104
- }
105
- else if (selector.substring(n + 1, n + 14) === 'host-context(') {
106
- s.B++;
107
- n += 14;
108
- const res = calculateSpecificityInternal(selector, n);
109
- n = res[1];
110
- s.A += res[0].A;
111
- s.B += res[0].B;
112
- s.C += res[0].C;
113
- }
114
- else if (selector.substring(n + 1, n + 9) === 'slotted(') {
115
- s.B++;
116
- n += 9;
117
- const res = calculateSpecificityInternal(selector, n);
118
- n = res[1];
119
- s.A += res[0].A;
120
- s.B += res[0].B;
121
- s.C += res[0].C;
122
- }
123
- else if (selector.substring(n + 1, n + 7) === 'where(') { //where does not add specificity
124
- parseState = ParseState.parseInFunc;
125
- n += 7;
126
- }
127
- else {
128
- s.B++;
129
- parseState = ParseState.parseName;
130
- n++;
121
+ break;
122
+ }
123
+ return i;
124
+ }
125
+ // When called from parseSelectorList with direct=true, writes directly into spec (avoids temp object).
126
+ // When called recursively (direct=false), the caller manages its own temp.
127
+ function parseSelector(input, start, spec, direct) {
128
+ let i = start;
129
+ if (direct) {
130
+ spec.A = spec.B = spec.C = 0;
131
+ }
132
+ while (i < input.length) {
133
+ const c = input.charCodeAt(i);
134
+ if (c === CH_COMMA || c === CH_RPAREN)
135
+ break;
136
+ // Column combinator ||
137
+ if (c === CH_PIPE && input.charCodeAt(i + 1) === CH_PIPE) {
138
+ i += 2;
139
+ continue;
140
+ }
141
+ if (c === CH_SPACE || c === CH_GT || c === CH_PLUS || c === CH_TILDE) {
142
+ i++;
143
+ continue;
144
+ }
145
+ if (c === CH_HASH) {
146
+ i = readIdent(input, i + 1);
147
+ spec.A++;
148
+ continue;
149
+ }
150
+ if (c === CH_DOT) {
151
+ i = readIdent(input, i + 1);
152
+ spec.B++;
153
+ continue;
154
+ }
155
+ if (c === CH_LBRACKET) {
156
+ i = readBalanced(input, i, CH_LBRACKET, CH_RBRACKET);
157
+ spec.B++;
158
+ continue;
159
+ }
160
+ if (c === CH_COLON) {
161
+ if (input.charCodeAt(i + 1) === CH_COLON) {
162
+ i += 2;
163
+ i = readIdent(input, i);
164
+ if (input.charCodeAt(i) === CH_LPAREN)
165
+ i = readBalanced(input, i, CH_LPAREN, CH_RPAREN);
166
+ spec.C++;
167
+ continue;
168
+ }
169
+ const nameStart = i + 1;
170
+ const nameEnd = readIdent(input, nameStart);
171
+ i = nameEnd;
172
+ // Legacy single-colon pseudo-elements count as C (pseudo-element), not B
173
+ if (isLegacyPseudoElement(input, nameStart, nameEnd - nameStart)) {
174
+ spec.C++;
175
+ continue;
176
+ }
177
+ if (input.charCodeAt(i) === CH_LPAREN) {
178
+ const innerStart = i + 1;
179
+ const innerEnd = readBalanced(input, i, CH_LPAREN, CH_RPAREN);
180
+ // Identify the pseudo-class by comparing chars directly (avoids substring allocation)
181
+ const nameLen = nameEnd - nameStart;
182
+ const pcKind = classifyPseudo(input, nameStart, nameLen);
183
+ switch (pcKind) {
184
+ case PC_WHERE:
185
+ i = innerEnd;
186
+ continue;
187
+ case PC_IS:
188
+ case PC_NOT:
189
+ case PC_HAS:
190
+ case PC_MATCHES:
191
+ case PC_WEBKIT_ANY:
192
+ case PC_MOZ_ANY: {
193
+ let bestA = 0, bestB = 0, bestC = 0;
194
+ let j = innerStart;
195
+ const limit = innerEnd - 1;
196
+ while (j < limit) {
197
+ const saved = { A: 0, B: 0, C: 0 };
198
+ j = parseSelectorInner(input, j, saved);
199
+ if (saved.A > bestA || (saved.A === bestA && (saved.B > bestB || (saved.B === bestB && saved.C > bestC)))) {
200
+ bestA = saved.A;
201
+ bestB = saved.B;
202
+ bestC = saved.C;
203
+ }
204
+ if (input.charCodeAt(j) === CH_COMMA)
205
+ j++;
206
+ else
207
+ break;
131
208
  }
209
+ spec.A += bestA;
210
+ spec.B += bestB;
211
+ spec.C += bestC;
212
+ i = innerEnd;
213
+ continue;
132
214
  }
133
- break;
134
- case '>':
135
- case ' ':
136
- case '~':
137
- case '+':
138
- parseState = ParseState.none;
139
- break;
140
- case '*':
141
- break;
142
- default:
143
- if (parseState === ParseState.none) {
144
- s.C++;
145
- parseState = ParseState.parseName;
215
+ case PC_SLOTTED: {
216
+ const inner = { A: 0, B: 0, C: 0 };
217
+ parseSelector(input, innerStart, inner, false);
218
+ spec.A += inner.A;
219
+ spec.B += inner.B;
220
+ spec.C += inner.C;
221
+ i = innerEnd;
222
+ continue;
223
+ }
224
+ case PC_HOST:
225
+ case PC_HOST_CTX: {
226
+ spec.B++;
227
+ const inner = { A: 0, B: 0, C: 0 };
228
+ parseSelector(input, innerStart, inner, false);
229
+ spec.A += inner.A;
230
+ spec.B += inner.B;
231
+ spec.C += inner.C;
232
+ i = innerEnd;
233
+ continue;
146
234
  }
235
+ case PC_NTH_CHILD:
236
+ case PC_NTH_LAST: {
237
+ spec.B++;
238
+ const limit = innerEnd - 1;
239
+ const ofIndex = findOfKeyword(input, innerStart, limit);
240
+ if (ofIndex !== -1) {
241
+ let afterOf = ofIndex + 2;
242
+ while (afterOf < limit && isWhitespaceCC(input.charCodeAt(afterOf)))
243
+ afterOf++;
244
+ let bestA = 0, bestB = 0, bestC = 0;
245
+ let j = afterOf;
246
+ while (j < limit) {
247
+ const ts = { A: 0, B: 0, C: 0 };
248
+ j = parseSelector(input, j, ts, false);
249
+ if (ts.A > bestA || (ts.A === bestA && (ts.B > bestB || (ts.B === bestB && ts.C > bestC)))) {
250
+ bestA = ts.A;
251
+ bestB = ts.B;
252
+ bestC = ts.C;
253
+ }
254
+ if (input.charCodeAt(j) === CH_COMMA)
255
+ j++;
256
+ else
257
+ break;
258
+ }
259
+ spec.A += bestA;
260
+ spec.B += bestB;
261
+ spec.C += bestC;
262
+ }
263
+ i = innerEnd;
264
+ continue;
265
+ }
266
+ default:
267
+ spec.B++;
268
+ i = innerEnd;
269
+ continue;
270
+ }
271
+ }
272
+ else {
273
+ spec.B++;
274
+ continue;
275
+ }
276
+ }
277
+ if (c === CH_STAR) {
278
+ i++;
279
+ continue;
280
+ }
281
+ if (isIdentStartCC(c) || c === CH_PIPE) {
282
+ while (i < input.length && (isIdentCC(input.charCodeAt(i)) || input.charCodeAt(i) === CH_PIPE))
283
+ i++;
284
+ spec.C++;
285
+ continue;
286
+ }
287
+ i++;
288
+ }
289
+ return i;
290
+ }
291
+ // Parse a single selector within a functional pseudo-class argument (handles commas at the caller level)
292
+ function parseSelectorInner(input, start, spec) {
293
+ return parseSelector(input, start, spec, false);
294
+ }
295
+ /* ---- Pseudo-class classification (avoids substring + switch on string) ---- */
296
+ const PC_OTHER = 0;
297
+ const PC_WHERE = 1;
298
+ const PC_IS = 2;
299
+ const PC_NOT = 3;
300
+ const PC_HAS = 4;
301
+ const PC_SLOTTED = 5;
302
+ const PC_HOST = 6;
303
+ const PC_HOST_CTX = 7;
304
+ const PC_NTH_CHILD = 8;
305
+ const PC_NTH_LAST = 9;
306
+ const PC_MATCHES = 10;
307
+ const PC_WEBKIT_ANY = 11;
308
+ const PC_MOZ_ANY = 12;
309
+ function classifyPseudo(input, start, len) {
310
+ // Most common first
311
+ if (len === 5 && input.charCodeAt(start) === 119) { // 'w'here
312
+ if (input.charCodeAt(start + 1) === 104 && input.charCodeAt(start + 2) === 101 &&
313
+ input.charCodeAt(start + 3) === 114 && input.charCodeAt(start + 4) === 101)
314
+ return PC_WHERE;
315
+ }
316
+ if (len === 3 && input.charCodeAt(start) === 110) { // 'n'ot
317
+ if (input.charCodeAt(start + 1) === 111 && input.charCodeAt(start + 2) === 116)
318
+ return PC_NOT;
319
+ }
320
+ if (len === 2 && input.charCodeAt(start) === 105) { // 'i's
321
+ if (input.charCodeAt(start + 1) === 115)
322
+ return PC_IS;
323
+ }
324
+ if (len === 3 && input.charCodeAt(start) === 104) { // 'h'as / 'h'ost
325
+ if (input.charCodeAt(start + 1) === 97 && input.charCodeAt(start + 2) === 115)
326
+ return PC_HAS;
327
+ }
328
+ if (len === 4 && input.charCodeAt(start) === 104) { // 'h'ost
329
+ if (input.charCodeAt(start + 1) === 111 && input.charCodeAt(start + 2) === 115 &&
330
+ input.charCodeAt(start + 3) === 116)
331
+ return PC_HOST;
332
+ }
333
+ if (len === 12 && input.charCodeAt(start) === 104) { // host-context
334
+ if (input.charCodeAt(start + 4) === CH_DASH && input.charCodeAt(start + 5) === 99) {
335
+ // Quick check first+last chars, then full
336
+ if (input.substring(start, start + len) === 'host-context')
337
+ return PC_HOST_CTX;
338
+ }
339
+ }
340
+ if (len === 7 && input.charCodeAt(start) === 115) { // slotted
341
+ if (input.substring(start, start + 7) === 'slotted')
342
+ return PC_SLOTTED;
343
+ }
344
+ if (len === 9 && input.charCodeAt(start) === 110) { // nth-child
345
+ if (input.substring(start, start + 9) === 'nth-child')
346
+ return PC_NTH_CHILD;
347
+ }
348
+ if (len === 14 && input.charCodeAt(start) === 110) { // nth-last-child
349
+ if (input.substring(start, start + 14) === 'nth-last-child')
350
+ return PC_NTH_LAST;
351
+ }
352
+ if (len === 7 && input.charCodeAt(start) === 109) { // matches
353
+ if (input.substring(start, start + 7) === 'matches')
354
+ return PC_MATCHES;
355
+ }
356
+ if (len === 11 && input.charCodeAt(start) === CH_DASH) { // -webkit-any
357
+ if (input.substring(start, start + 11) === '-webkit-any')
358
+ return PC_WEBKIT_ANY;
359
+ }
360
+ if (len === 8 && input.charCodeAt(start) === CH_DASH) { // -moz-any
361
+ if (input.substring(start, start + 8) === '-moz-any')
362
+ return PC_MOZ_ANY;
363
+ }
364
+ return PC_OTHER;
365
+ }
366
+ // Legacy single-colon pseudo-elements that should count as C, not B
367
+ function isLegacyPseudoElement(input, start, len) {
368
+ if (len === 6) { // before / after
369
+ const c0 = input.charCodeAt(start);
370
+ if (c0 === 98)
371
+ return input.substring(start, start + 6) === 'before'; // 'b'efore
372
+ if (c0 === 97)
373
+ return input.substring(start, start + 6) === 'after'; // 'a'fter (5 chars, won't match — handled below)
374
+ }
375
+ if (len === 5 && input.charCodeAt(start) === 97) { // after
376
+ return input.substring(start, start + 5) === 'after';
377
+ }
378
+ if (len === 10 && input.charCodeAt(start) === 102) { // first-line
379
+ return input.substring(start, start + 10) === 'first-line';
380
+ }
381
+ if (len === 12 && input.charCodeAt(start) === 102) { // first-letter
382
+ return input.substring(start, start + 12) === 'first-letter';
383
+ }
384
+ return false;
385
+ }
386
+ /* ---- Character classification (charCode-based, no string allocation) ---- */
387
+ function isIdentStartCC(code) {
388
+ return (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || code === CH_UNDERSCORE || code === CH_DASH || code === CH_BACKSLASH;
389
+ }
390
+ function isIdentCC(code) {
391
+ return (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || (code >= 48 && code <= 57) || code === CH_UNDERSCORE || code === CH_DASH || code === CH_BACKSLASH;
392
+ }
393
+ function isWhitespaceCC(code) {
394
+ return code === 32 || code === 9 || code === 10 || code === 13 || code === 12;
395
+ }
396
+ function readIdent(input, start) {
397
+ let i = start;
398
+ while (i < input.length) {
399
+ const cc = input.charCodeAt(i);
400
+ if (cc === CH_BACKSLASH) {
401
+ i += 2;
402
+ continue;
403
+ }
404
+ if (!isIdentCC(cc))
405
+ break;
406
+ i++;
407
+ }
408
+ return i;
409
+ }
410
+ function readBalanced(input, start, open, close) {
411
+ let depth = 0, inQuote = 0, i = start;
412
+ while (i < input.length) {
413
+ const c = input.charCodeAt(i);
414
+ if (inQuote) {
415
+ if (c === CH_BACKSLASH) {
416
+ i += 2;
417
+ continue;
147
418
  }
419
+ if (c === inQuote)
420
+ inQuote = 0;
421
+ i++;
422
+ continue;
423
+ }
424
+ if (c === CH_DQUOTE || c === CH_SQUOTE) {
425
+ inQuote = c;
426
+ i++;
427
+ continue;
148
428
  }
429
+ if (c === open)
430
+ depth++;
431
+ else if (c === close) {
432
+ depth--;
433
+ if (depth === 0)
434
+ return i + 1;
435
+ }
436
+ i++;
149
437
  }
150
- return [s, selector.length];
438
+ return i;
151
439
  }
152
- function getMaxSpecificityFromSelectorList(selector, startIndex) {
153
- let idx = startIndex;
154
- let s = null;
155
- while (s === null || selector[idx] === ',') {
156
- if (selector[idx] === ',')
157
- idx++;
158
- const res = calculateSpecificityInternal(selector, idx);
159
- if (res[1] > idx)
160
- idx = res[1];
161
- else
162
- return [s, 0];
163
- if (s == null || res[0].A > s.A || (res[0].A === s.A && res[0].B > s.B) || (res[0].A === s.A && res[0].B === s.B && res[0].C > s.C))
164
- s = res[0];
165
- }
166
- return [s, idx];
440
+ function findOfKeyword(input, start, end) {
441
+ let i = start;
442
+ while (i <= end - 1) {
443
+ while (i <= end - 1 && isWhitespaceCC(input.charCodeAt(i)))
444
+ i++;
445
+ if (input.charCodeAt(i) === 111 && input.charCodeAt(i + 1) === 102 && // 'o','f'
446
+ (i + 2 > end - 1 || isWhitespaceCC(input.charCodeAt(i + 2)) || input.charCodeAt(i + 2) === CH_LPAREN))
447
+ return i;
448
+ i++;
449
+ }
450
+ return -1;
167
451
  }
168
452
  //# sourceMappingURL=SpecificityCalculator.js.map