UNPKG

19.1 kBJavaScriptView Raw
1import '../../globals';
2import { isCssVariable } from '../core/properties';
3import { isNullOrUndefined } from '../../utils/types';
4import { parseSelector } from '../../css/parser';
5var Match;
6(function (Match) {
7 /**
8 * Depends on attributes or pseudoclasses state;
9 */
10 Match.Dynamic = true;
11 /**
12 * Depends only on the tree structure.
13 */
14 Match.Static = false;
15})(Match || (Match = {}));
16function getNodeDirectSibling(node) {
17 if (!node.parent || !node.parent.getChildIndex || !node.parent.getChildAt) {
18 return null;
19 }
20 const nodeIndex = node.parent.getChildIndex(node);
21 if (nodeIndex === 0) {
22 return null;
23 }
24 return node.parent.getChildAt(nodeIndex - 1);
25}
26function SelectorProperties(specificity, rarity, dynamic = false) {
27 return (cls) => {
28 cls.prototype.specificity = specificity;
29 cls.prototype.rarity = rarity;
30 cls.prototype.combinator = undefined;
31 cls.prototype.dynamic = dynamic;
32 return cls;
33 };
34}
35let SelectorCore = class SelectorCore {
36 lookupSort(sorter, base) {
37 sorter.sortAsUniversal(base || this);
38 }
39};
40SelectorCore = __decorate([
41 SelectorProperties(0 /* Specificity.Universal */, 0 /* Rarity.Universal */, Match.Static)
42], SelectorCore);
43export { SelectorCore };
44export class SimpleSelector extends SelectorCore {
45 accumulateChanges(node, map) {
46 if (!this.dynamic) {
47 return this.match(node);
48 }
49 else if (this.mayMatch(node)) {
50 this.trackChanges(node, map);
51 return true;
52 }
53 return false;
54 }
55 mayMatch(node) {
56 return this.match(node);
57 }
58 trackChanges(node, map) {
59 // No-op, silence the tslint 'block is empty'.
60 // Some derived classes (dynamic) will actually fill the map with stuff here, some (static) won't do anything.
61 }
62}
63function wrap(text) {
64 return text ? ` ${text} ` : '';
65}
66let InvalidSelector = class InvalidSelector extends SimpleSelector {
67 constructor(e) {
68 super();
69 this.e = e;
70 }
71 toString() {
72 return `<error: ${this.e}>`;
73 }
74 match(node) {
75 return false;
76 }
77 lookupSort(sorter, base) {
78 // No-op, silence the tslint 'block is empty'.
79 // It feels like tslint has problems with simple polymorphism...
80 // This selector is invalid and will never match so we won't bother sorting it to further appear in queries.
81 }
82};
83InvalidSelector = __decorate([
84 SelectorProperties(0 /* Specificity.Invalid */, 4 /* Rarity.Invalid */, Match.Static),
85 __metadata("design:paramtypes", [Error])
86], InvalidSelector);
87export { InvalidSelector };
88let UniversalSelector = class UniversalSelector extends SimpleSelector {
89 toString() {
90 return `*${wrap(this.combinator)}`;
91 }
92 match(node) {
93 return true;
94 }
95};
96UniversalSelector = __decorate([
97 SelectorProperties(0 /* Specificity.Universal */, 0 /* Rarity.Universal */, Match.Static)
98], UniversalSelector);
99export { UniversalSelector };
100let IdSelector = class IdSelector extends SimpleSelector {
101 constructor(id) {
102 super();
103 this.id = id;
104 }
105 toString() {
106 return `#${this.id}${wrap(this.combinator)}`;
107 }
108 match(node) {
109 return node.id === this.id;
110 }
111 lookupSort(sorter, base) {
112 sorter.sortById(this.id, base || this);
113 }
114};
115IdSelector = __decorate([
116 SelectorProperties(100 /* Specificity.Id */, 3 /* Rarity.Id */, Match.Static),
117 __metadata("design:paramtypes", [String])
118], IdSelector);
119export { IdSelector };
120let TypeSelector = class TypeSelector extends SimpleSelector {
121 constructor(cssType) {
122 super();
123 this.cssType = cssType;
124 }
125 toString() {
126 return `${this.cssType}${wrap(this.combinator)}`;
127 }
128 match(node) {
129 return node.cssType === this.cssType;
130 }
131 lookupSort(sorter, base) {
132 sorter.sortByType(this.cssType, base || this);
133 }
134};
135TypeSelector = __decorate([
136 SelectorProperties(1 /* Specificity.Type */, 1 /* Rarity.Type */, Match.Static),
137 __metadata("design:paramtypes", [String])
138], TypeSelector);
139export { TypeSelector };
140let ClassSelector = class ClassSelector extends SimpleSelector {
141 constructor(cssClass) {
142 super();
143 this.cssClass = cssClass;
144 }
145 toString() {
146 return `.${this.cssClass}${wrap(this.combinator)}`;
147 }
148 match(node) {
149 return node.cssClasses && node.cssClasses.has(this.cssClass);
150 }
151 lookupSort(sorter, base) {
152 sorter.sortByClass(this.cssClass, base || this);
153 }
154};
155ClassSelector = __decorate([
156 SelectorProperties(10 /* Specificity.Class */, 2 /* Rarity.Class */, Match.Static),
157 __metadata("design:paramtypes", [String])
158], ClassSelector);
159export { ClassSelector };
160let AttributeSelector = class AttributeSelector extends SimpleSelector {
161 constructor(attribute, test, value) {
162 super();
163 this.attribute = attribute;
164 this.test = test;
165 this.value = value;
166 if (!test) {
167 // HasAttribute
168 this.match = (node) => !isNullOrUndefined(node[attribute]);
169 return;
170 }
171 if (!value) {
172 this.match = (node) => false;
173 }
174 this.match = (node) => {
175 const attr = node[attribute] + '';
176 if (test === '=') {
177 // Equals
178 return attr === value;
179 }
180 if (test === '^=') {
181 // PrefixMatch
182 return attr.startsWith(value);
183 }
184 if (test === '$=') {
185 // SuffixMatch
186 return attr.endsWith(value);
187 }
188 if (test === '*=') {
189 // SubstringMatch
190 return attr.indexOf(value) !== -1;
191 }
192 if (test === '~=') {
193 // Includes
194 const words = attr.split(' ');
195 return words && words.indexOf(value) !== -1;
196 }
197 if (test === '|=') {
198 // DashMatch
199 return attr === value || attr.startsWith(value + '-');
200 }
201 };
202 }
203 toString() {
204 return `[${this.attribute}${wrap(this.test)}${(this.test && this.value) || ''}]${wrap(this.combinator)}`;
205 }
206 match(node) {
207 return false;
208 }
209 mayMatch(node) {
210 return true;
211 }
212 trackChanges(node, map) {
213 map.addAttribute(node, this.attribute);
214 }
215};
216AttributeSelector = __decorate([
217 SelectorProperties(10 /* Specificity.Attribute */, 0 /* Rarity.Attribute */, Match.Dynamic),
218 __metadata("design:paramtypes", [String, String, String])
219], AttributeSelector);
220export { AttributeSelector };
221let PseudoClassSelector = class PseudoClassSelector extends SimpleSelector {
222 constructor(cssPseudoClass) {
223 super();
224 this.cssPseudoClass = cssPseudoClass;
225 }
226 toString() {
227 return `:${this.cssPseudoClass}${wrap(this.combinator)}`;
228 }
229 match(node) {
230 return node.cssPseudoClasses && node.cssPseudoClasses.has(this.cssPseudoClass);
231 }
232 mayMatch(node) {
233 return true;
234 }
235 trackChanges(node, map) {
236 map.addPseudoClass(node, this.cssPseudoClass);
237 }
238};
239PseudoClassSelector = __decorate([
240 SelectorProperties(10 /* Specificity.PseudoClass */, 0 /* Rarity.PseudoClass */, Match.Dynamic),
241 __metadata("design:paramtypes", [String])
242], PseudoClassSelector);
243export { PseudoClassSelector };
244export class SimpleSelectorSequence extends SimpleSelector {
245 constructor(selectors) {
246 super();
247 this.selectors = selectors;
248 this.specificity = selectors.reduce((sum, sel) => sel.specificity + sum, 0);
249 this.head = this.selectors.reduce((prev, curr) => (!prev || curr.rarity > prev.rarity ? curr : prev), null);
250 this.dynamic = selectors.some((sel) => sel.dynamic);
251 }
252 toString() {
253 return `${this.selectors.join('')}${wrap(this.combinator)}`;
254 }
255 match(node) {
256 return this.selectors.every((sel) => sel.match(node));
257 }
258 mayMatch(node) {
259 return this.selectors.every((sel) => sel.mayMatch(node));
260 }
261 trackChanges(node, map) {
262 this.selectors.forEach((sel) => sel.trackChanges(node, map));
263 }
264 lookupSort(sorter, base) {
265 this.head.lookupSort(sorter, base || this);
266 }
267}
268export class Selector extends SelectorCore {
269 constructor(selectors) {
270 super();
271 this.selectors = selectors;
272 const supportedCombinator = [undefined, ' ', '>', '+'];
273 let siblingGroup;
274 let lastGroup;
275 const groups = [];
276 this.specificity = 0;
277 this.dynamic = false;
278 for (let i = selectors.length - 1; i > -1; i--) {
279 const sel = selectors[i];
280 if (supportedCombinator.indexOf(sel.combinator) === -1) {
281 throw new Error(`Unsupported combinator "${sel.combinator}".`);
282 }
283 if (sel.combinator === undefined || sel.combinator === ' ') {
284 groups.push((lastGroup = [(siblingGroup = [])]));
285 }
286 if (sel.combinator === '>') {
287 lastGroup.push((siblingGroup = []));
288 }
289 this.specificity += sel.specificity;
290 if (sel.dynamic) {
291 this.dynamic = true;
292 }
293 siblingGroup.push(sel);
294 }
295 this.groups = groups.map((g) => new Selector.ChildGroup(g.map((sg) => new Selector.SiblingGroup(sg))));
296 this.last = selectors[selectors.length - 1];
297 }
298 toString() {
299 return this.selectors.join('');
300 }
301 match(node) {
302 return this.groups.every((group, i) => {
303 if (i === 0) {
304 node = group.match(node);
305 return !!node;
306 }
307 else {
308 let ancestor = node;
309 while ((ancestor = ancestor.parent ?? ancestor._modalParent)) {
310 if ((node = group.match(ancestor))) {
311 return true;
312 }
313 }
314 return false;
315 }
316 });
317 }
318 lookupSort(sorter, base) {
319 this.last.lookupSort(sorter, this);
320 }
321 accumulateChanges(node, map) {
322 if (!this.dynamic) {
323 return this.match(node);
324 }
325 const bounds = [];
326 const mayMatch = this.groups.every((group, i) => {
327 if (i === 0) {
328 const nextNode = group.mayMatch(node);
329 bounds.push({ left: node, right: node });
330 node = nextNode;
331 return !!node;
332 }
333 else {
334 let ancestor = node;
335 while ((ancestor = ancestor.parent)) {
336 const nextNode = group.mayMatch(ancestor);
337 if (nextNode) {
338 bounds.push({ left: ancestor, right: null });
339 node = nextNode;
340 return true;
341 }
342 }
343 return false;
344 }
345 });
346 // Calculating the right bounds for each selectors won't save much
347 if (!mayMatch) {
348 return false;
349 }
350 if (!map) {
351 return mayMatch;
352 }
353 for (let i = 0; i < this.groups.length; i++) {
354 const group = this.groups[i];
355 if (!group.dynamic) {
356 continue;
357 }
358 const bound = bounds[i];
359 let node = bound.left;
360 do {
361 if (group.mayMatch(node)) {
362 group.trackChanges(node, map);
363 }
364 } while (node !== bound.right && (node = node.parent));
365 }
366 return mayMatch;
367 }
368}
369(function (Selector) {
370 // Non-spec. Selector sequences are grouped by ancestor then by child combinators for easier backtracking.
371 class ChildGroup {
372 constructor(selectors) {
373 this.selectors = selectors;
374 this.dynamic = selectors.some((sel) => sel.dynamic);
375 }
376 match(node) {
377 return this.selectors.every((sel, i) => (node = i === 0 ? node : node.parent) && sel.match(node)) ? node : null;
378 }
379 mayMatch(node) {
380 return this.selectors.every((sel, i) => (node = i === 0 ? node : node.parent) && sel.mayMatch(node)) ? node : null;
381 }
382 trackChanges(node, map) {
383 this.selectors.forEach((sel, i) => (node = i === 0 ? node : node.parent) && sel.trackChanges(node, map));
384 }
385 }
386 Selector.ChildGroup = ChildGroup;
387 class SiblingGroup {
388 constructor(selectors) {
389 this.selectors = selectors;
390 this.dynamic = selectors.some((sel) => sel.dynamic);
391 }
392 match(node) {
393 return this.selectors.every((sel, i) => (node = i === 0 ? node : getNodeDirectSibling(node)) && sel.match(node)) ? node : null;
394 }
395 mayMatch(node) {
396 return this.selectors.every((sel, i) => (node = i === 0 ? node : getNodeDirectSibling(node)) && sel.mayMatch(node)) ? node : null;
397 }
398 trackChanges(node, map) {
399 this.selectors.forEach((sel, i) => (node = i === 0 ? node : getNodeDirectSibling(node)) && sel.trackChanges(node, map));
400 }
401 }
402 Selector.SiblingGroup = SiblingGroup;
403})(Selector || (Selector = {}));
404export class RuleSet {
405 constructor(selectors, declarations) {
406 this.selectors = selectors;
407 this.declarations = declarations;
408 this.selectors.forEach((sel) => (sel.ruleset = this));
409 }
410 toString() {
411 return `${this.selectors.join(', ')} {${this.declarations.map((d, i) => `${i === 0 ? ' ' : ''}${d.property}: ${d.value}`).join('; ')} }`;
412 }
413 lookupSort(sorter) {
414 this.selectors.forEach((sel) => sel.lookupSort(sorter));
415 }
416}
417export function fromAstNodes(astRules) {
418 return astRules.filter(isRule).map((rule) => {
419 const declarations = rule.declarations.filter(isDeclaration).map(createDeclaration);
420 const selectors = rule.selectors.map(createSelector);
421 return new RuleSet(selectors, declarations);
422 });
423}
424function createDeclaration(decl) {
425 return { property: isCssVariable(decl.property) ? decl.property : decl.property.toLowerCase(), value: decl.value };
426}
427function createSimpleSelectorFromAst(ast) {
428 if (ast.type === '.') {
429 return new ClassSelector(ast.identifier);
430 }
431 if (ast.type === '') {
432 return new TypeSelector(ast.identifier.replace('-', '').toLowerCase());
433 }
434 if (ast.type === '#') {
435 return new IdSelector(ast.identifier);
436 }
437 if (ast.type === '[]') {
438 return new AttributeSelector(ast.property, ast.test, ast.test && ast.value);
439 }
440 if (ast.type === ':') {
441 return new PseudoClassSelector(ast.identifier);
442 }
443 if (ast.type === '*') {
444 return new UniversalSelector();
445 }
446}
447function createSimpleSelectorSequenceFromAst(ast) {
448 if (ast.length === 0) {
449 return new InvalidSelector(new Error('Empty simple selector sequence.'));
450 }
451 else if (ast.length === 1) {
452 return createSimpleSelectorFromAst(ast[0]);
453 }
454 else {
455 return new SimpleSelectorSequence(ast.map(createSimpleSelectorFromAst));
456 }
457}
458function createSelectorFromAst(ast) {
459 if (ast.length === 0) {
460 return new InvalidSelector(new Error('Empty selector.'));
461 }
462 else if (ast.length === 1) {
463 return createSimpleSelectorSequenceFromAst(ast[0][0]);
464 }
465 else {
466 const simpleSelectorSequences = [];
467 let simpleSelectorSequence;
468 let combinator;
469 for (let i = 0; i < ast.length; i++) {
470 simpleSelectorSequence = createSimpleSelectorSequenceFromAst(ast[i][0]);
471 combinator = ast[i][1];
472 if (combinator) {
473 simpleSelectorSequence.combinator = combinator;
474 }
475 simpleSelectorSequences.push(simpleSelectorSequence);
476 }
477 return new Selector(simpleSelectorSequences);
478 }
479}
480export function createSelector(sel) {
481 try {
482 const parsedSelector = parseSelector(sel);
483 if (!parsedSelector) {
484 return new InvalidSelector(new Error('Empty selector'));
485 }
486 return createSelectorFromAst(parsedSelector.value);
487 }
488 catch (e) {
489 return new InvalidSelector(e);
490 }
491}
492function isRule(node) {
493 return node.type === 'rule';
494}
495function isDeclaration(node) {
496 return node.type === 'declaration';
497}
498export class SelectorsMap {
499 constructor(rulesets) {
500 this.id = {};
501 this.class = {};
502 this.type = {};
503 this.universal = [];
504 this.position = 0;
505 rulesets.forEach((rule) => rule.lookupSort(this));
506 }
507 query(node) {
508 const selectorsMatch = new SelectorsMatch();
509 const { cssClasses, id, cssType } = node;
510 const selectorClasses = [this.universal, this.id[id], this.type[cssType]];
511 if (cssClasses && cssClasses.size) {
512 cssClasses.forEach((c) => selectorClasses.push(this.class[c]));
513 }
514 const selectors = selectorClasses.reduce((cur, next) => cur.concat(next || []), []);
515 selectorsMatch.selectors = selectors.filter((sel) => sel.accumulateChanges(node, selectorsMatch)).sort((a, b) => a.specificity - b.specificity || a.pos - b.pos);
516 return selectorsMatch;
517 }
518 sortById(id, sel) {
519 this.addToMap(this.id, id, sel);
520 }
521 sortByClass(cssClass, sel) {
522 this.addToMap(this.class, cssClass, sel);
523 }
524 sortByType(cssType, sel) {
525 this.addToMap(this.type, cssType, sel);
526 }
527 sortAsUniversal(sel) {
528 this.universal.push(this.makeDocSelector(sel));
529 }
530 addToMap(map, head, sel) {
531 if (!map[head]) {
532 map[head] = [];
533 }
534 map[head].push(this.makeDocSelector(sel));
535 }
536 makeDocSelector(sel) {
537 sel.pos = this.position++;
538 return sel;
539 }
540}
541export class SelectorsMatch {
542 constructor() {
543 this.changeMap = new Map();
544 }
545 addAttribute(node, attribute) {
546 const deps = this.properties(node);
547 if (!deps.attributes) {
548 deps.attributes = new Set();
549 }
550 deps.attributes.add(attribute);
551 }
552 addPseudoClass(node, pseudoClass) {
553 const deps = this.properties(node);
554 if (!deps.pseudoClasses) {
555 deps.pseudoClasses = new Set();
556 }
557 deps.pseudoClasses.add(pseudoClass);
558 }
559 properties(node) {
560 let set = this.changeMap.get(node);
561 if (!set) {
562 this.changeMap.set(node, (set = {}));
563 }
564 return set;
565 }
566}
567export const CSSHelper = {
568 createSelector,
569 SelectorCore,
570 SimpleSelector,
571 InvalidSelector,
572 UniversalSelector,
573 TypeSelector,
574 ClassSelector,
575 AttributeSelector,
576 PseudoClassSelector,
577 SimpleSelectorSequence,
578 Selector,
579 RuleSet,
580 SelectorsMap,
581 fromAstNodes,
582 SelectorsMatch,
583};
584//# sourceMappingURL=css-selector.js.map
\No newline at end of file