@asamuzakjp/dom-selector 2.0.3-a.1 → 2.0.3-a.2

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.
@@ -0,0 +1,2722 @@
1
+ /**
2
+ * finder.js
3
+ */
4
+
5
+ /* import */
6
+ import isCustomElementName from 'is-potential-custom-element-name';
7
+ import {
8
+ getDirectionality, isContentEditable, isInclusive, isInShadowTree,
9
+ prepareDOMObjects, sortNodes
10
+ } from './dom-util.js';
11
+ import { matchSelector, matchPseudoElementSelector } from './matcher.js';
12
+ import {
13
+ generateCSS, parseSelector, sortAST, unescapeSelector, walkAST
14
+ } from './parser.js';
15
+
16
+ /* constants */
17
+ import {
18
+ ALPHA_NUM, COMBINATOR, DOCUMENT_FRAGMENT_NODE, DOCUMENT_NODE, ELEMENT_NODE,
19
+ NOT_SUPPORTED_ERR, REG_LOGICAL_PSEUDO, REG_SHADOW_HOST, SELECTOR_CLASS,
20
+ SELECTOR_ID, SELECTOR_PSEUDO_CLASS, SELECTOR_PSEUDO_ELEMENT, SELECTOR_TYPE,
21
+ SHOW_ALL, SHOW_DOCUMENT, SHOW_DOCUMENT_FRAGMENT, SHOW_ELEMENT, SYNTAX_ERR,
22
+ TEXT_NODE
23
+ } from './constant.js';
24
+
25
+ const DIR_NEXT = 'next';
26
+ const DIR_PREV = 'prev';
27
+ const TARGET_ALL = 'all';
28
+ const TARGET_FIRST = 'first';
29
+ const TARGET_LINEAL = 'lineal';
30
+ const TARGET_SELF = 'self';
31
+ const WALKER_FILTER = SHOW_DOCUMENT | SHOW_DOCUMENT_FRAGMENT | SHOW_ELEMENT;
32
+
33
+ /**
34
+ * Finder
35
+ * NOTE: #ast[i] corresponds to #nodes[i]
36
+ * #ast: [
37
+ * {
38
+ * branch: branch[],
39
+ * dir: string|null,
40
+ * filtered: boolean,
41
+ * find: boolean
42
+ * },
43
+ * {
44
+ * branch: branch[],
45
+ * dir: string|null,
46
+ * filtered: boolean,
47
+ * find: boolean
48
+ * }
49
+ * ]
50
+ * #nodes: [
51
+ * [node{}, node{}],
52
+ * [node{}, node{}, node{}]
53
+ * ]
54
+ * branch[]: [twig{}, twig{}]
55
+ * twig{}: {
56
+ * combo: leaf{}|null,
57
+ * leaves: leaves[]
58
+ * }
59
+ * leaves[]: [leaf{}, leaf{}, leaf{}]
60
+ * leaf{}: CSSTree AST object
61
+ * node{}: Element node
62
+ */
63
+ export class Finder {
64
+ /* private fields */
65
+ #ast;
66
+ #cache;
67
+ #clones;
68
+ #document;
69
+ #finder;
70
+ #node;
71
+ #nodes;
72
+ #root;
73
+ #selector;
74
+ #shadow;
75
+ #sort;
76
+ #tree;
77
+ #warn;
78
+ #window;
79
+
80
+ /**
81
+ * construct
82
+ */
83
+ constructor() {
84
+ this.#cache = new WeakMap();
85
+ }
86
+
87
+ /**
88
+ * handle error
89
+ * @private
90
+ * @param {Error} e - Error
91
+ * @throws Error
92
+ * @returns {void}
93
+ */
94
+ _onError(e) {
95
+ if (e instanceof DOMException ||
96
+ (this.#window && e instanceof this.#window.DOMException)) {
97
+ if (e.name === NOT_SUPPORTED_ERR) {
98
+ if (this.#warn) {
99
+ console.warn(e.message);
100
+ }
101
+ } else if (this.#window) {
102
+ throw new this.#window.DOMException(e.message, e.name);
103
+ } else {
104
+ throw e;
105
+ }
106
+ } else {
107
+ throw e;
108
+ }
109
+ }
110
+
111
+ /**
112
+ * setup finder
113
+ * @private
114
+ * @param {object} node - Document, DocumentFragment, Element node
115
+ * @param {string} selector - CSS selector
116
+ * @param {object} opt - options
117
+ * @returns {void}
118
+ */
119
+ _setup(node, selector, opt = {}) {
120
+ const { warn } = opt;
121
+ this.#warn = !!warn;
122
+ [this.#window, this.#document, this.#root] = prepareDOMObjects(node);
123
+ this.#node = node;
124
+ this.#shadow = isInShadowTree(node);
125
+ this.#selector = selector;
126
+ [this.#ast, this.#nodes] = this._correspond(selector);
127
+ this.#clones = new WeakMap();
128
+ }
129
+
130
+ /**
131
+ * correspond #ast and #nodes
132
+ * @private
133
+ * @param {string} selector - CSS selector
134
+ * @returns {Array.<Array.<object|undefined>>} - array of #ast and #nodes
135
+ */
136
+ _correspond(selector) {
137
+ const nodes = [];
138
+ let ast;
139
+ let cachedItem = this.#document && this.#cache.get(this.#document);
140
+ if (cachedItem && cachedItem.has(`${selector}`)) {
141
+ ast = cachedItem.get(selector);
142
+ if (typeof ast === 'string') {
143
+ throw new DOMException(ast, SYNTAX_ERR);
144
+ }
145
+ }
146
+ if (ast) {
147
+ const l = ast.length;
148
+ for (let i = 0; i < l; i++) {
149
+ ast[i].dir = null;
150
+ ast[i].filtered = false;
151
+ ast[i].find = false;
152
+ nodes[i] = [];
153
+ }
154
+ } else {
155
+ let cssAst;
156
+ try {
157
+ cssAst = parseSelector(selector);
158
+ } catch (e) {
159
+ if (this.#document) {
160
+ if (!cachedItem) {
161
+ cachedItem = new Map();
162
+ }
163
+ cachedItem.set(`${selector}`, e.message);
164
+ this.#cache.set(this.#document, cachedItem);
165
+ }
166
+ this._onError(e);
167
+ }
168
+ const branches = walkAST(cssAst);
169
+ ast = [];
170
+ let i = 0;
171
+ for (const [...items] of branches) {
172
+ const branch = [];
173
+ let item = items.shift();
174
+ if (item && item.type !== COMBINATOR) {
175
+ const leaves = new Set();
176
+ while (item) {
177
+ if (item.type === COMBINATOR) {
178
+ const [nextItem] = items;
179
+ if (nextItem.type === COMBINATOR) {
180
+ const msg = `Invalid selector ${selector}`;
181
+ if (this.#document) {
182
+ if (!cachedItem) {
183
+ cachedItem = new Map();
184
+ }
185
+ cachedItem.set(`${selector}`, msg);
186
+ this.#cache.set(this.#document, cachedItem);
187
+ }
188
+ throw new DOMException(msg, SYNTAX_ERR);
189
+ }
190
+ branch.push({
191
+ combo: item,
192
+ leaves: sortAST(leaves)
193
+ });
194
+ leaves.clear();
195
+ } else if (item) {
196
+ leaves.add(item);
197
+ }
198
+ if (items.length) {
199
+ item = items.shift();
200
+ } else {
201
+ branch.push({
202
+ combo: null,
203
+ leaves: sortAST(leaves)
204
+ });
205
+ leaves.clear();
206
+ break;
207
+ }
208
+ }
209
+ }
210
+ ast.push({
211
+ branch,
212
+ dir: null,
213
+ filtered: false,
214
+ find: false
215
+ });
216
+ nodes[i] = [];
217
+ i++;
218
+ }
219
+ if (this.#document) {
220
+ if (!cachedItem) {
221
+ cachedItem = new Map();
222
+ }
223
+ cachedItem.set(`${selector}`, ast);
224
+ this.#cache.set(this.#document, cachedItem);
225
+ }
226
+ }
227
+ return [
228
+ ast,
229
+ nodes
230
+ ];
231
+ }
232
+
233
+ /**
234
+ * traverse tree walker
235
+ * @private
236
+ * @param {object} [node] - Element node
237
+ * @param {object} [walker] - tree walker
238
+ * @returns {?object} - current node
239
+ */
240
+ _traverse(node = {}, walker = this.#tree) {
241
+ let current;
242
+ let refNode = walker.currentNode;
243
+ if (node.nodeType === ELEMENT_NODE && refNode === node) {
244
+ current = refNode;
245
+ } else {
246
+ if (refNode !== walker.root) {
247
+ while (refNode) {
248
+ if (refNode === walker.root ||
249
+ (node.nodeType === ELEMENT_NODE && refNode === node)) {
250
+ break;
251
+ }
252
+ refNode = walker.parentNode();
253
+ }
254
+ }
255
+ if (node.nodeType === ELEMENT_NODE) {
256
+ while (refNode) {
257
+ if (refNode === node) {
258
+ current = refNode;
259
+ break;
260
+ }
261
+ refNode = walker.nextNode();
262
+ }
263
+ } else {
264
+ current = refNode;
265
+ }
266
+ }
267
+ return current ?? null;
268
+ }
269
+
270
+ /**
271
+ * collect nth child
272
+ * @private
273
+ * @param {object} anb - An+B options
274
+ * @param {number} anb.a - a
275
+ * @param {number} anb.b - b
276
+ * @param {boolean} [anb.reverse] - reverse order
277
+ * @param {object} [anb.selector] - AST
278
+ * @param {object} node - Element node
279
+ * @param {object} opt - options
280
+ * @returns {Set.<object>} - collection of matched nodes
281
+ */
282
+ _collectNthChild(anb, node, opt) {
283
+ const { a, b, reverse, selector } = anb;
284
+ const { parentNode } = node;
285
+ let matched = new Set();
286
+ let selectorBranches;
287
+ if (selector) {
288
+ if (this.#cache.has(selector)) {
289
+ selectorBranches = this.#cache.get(selector);
290
+ } else {
291
+ selectorBranches = walkAST(selector);
292
+ this.#cache.set(selector, selectorBranches);
293
+ }
294
+ }
295
+ if (parentNode) {
296
+ const walker = this.#document.createTreeWalker(parentNode, WALKER_FILTER);
297
+ let l = 0;
298
+ let refNode = walker.firstChild();
299
+ while (refNode) {
300
+ l++;
301
+ refNode = walker.nextSibling();
302
+ }
303
+ refNode = this._traverse(parentNode, walker);
304
+ const selectorNodes = new Set();
305
+ if (selectorBranches) {
306
+ refNode = this._traverse(parentNode, walker);
307
+ refNode = walker.firstChild();
308
+ while (refNode) {
309
+ let bool;
310
+ for (const leaves of selectorBranches) {
311
+ bool = this._matchLeaves(leaves, refNode, opt);
312
+ if (!bool) {
313
+ break;
314
+ }
315
+ }
316
+ if (bool) {
317
+ selectorNodes.add(refNode);
318
+ }
319
+ refNode = walker.nextSibling();
320
+ }
321
+ }
322
+ // :first-child, :last-child, :nth-child(b of S), :nth-last-child(b of S)
323
+ if (a === 0) {
324
+ if (b > 0 && b <= l) {
325
+ if (selectorNodes.size) {
326
+ let i = 0;
327
+ refNode = this._traverse(parentNode, walker);
328
+ if (reverse) {
329
+ refNode = walker.lastChild();
330
+ } else {
331
+ refNode = walker.firstChild();
332
+ }
333
+ while (refNode) {
334
+ if (selectorNodes.has(refNode)) {
335
+ if (i === b - 1) {
336
+ matched.add(refNode);
337
+ break;
338
+ }
339
+ i++;
340
+ }
341
+ if (reverse) {
342
+ refNode = walker.previousSibling();
343
+ } else {
344
+ refNode = walker.nextSibling();
345
+ }
346
+ }
347
+ } else if (!selector) {
348
+ let i = 0;
349
+ refNode = this._traverse(parentNode, walker);
350
+ if (reverse) {
351
+ refNode = walker.lastChild();
352
+ } else {
353
+ refNode = walker.firstChild();
354
+ }
355
+ while (refNode) {
356
+ if (i === b - 1) {
357
+ matched.add(refNode);
358
+ break;
359
+ }
360
+ if (reverse) {
361
+ refNode = walker.previousSibling();
362
+ } else {
363
+ refNode = walker.nextSibling();
364
+ }
365
+ i++;
366
+ }
367
+ }
368
+ }
369
+ // :nth-child()
370
+ } else {
371
+ let nth = b - 1;
372
+ if (a > 0) {
373
+ while (nth < 0) {
374
+ nth += a;
375
+ }
376
+ }
377
+ if (nth >= 0 && nth < l) {
378
+ let i = 0;
379
+ let j = a > 0 ? 0 : b - 1;
380
+ refNode = this._traverse(parentNode, walker);
381
+ if (reverse) {
382
+ refNode = walker.lastChild();
383
+ } else {
384
+ refNode = walker.firstChild();
385
+ }
386
+ while (refNode) {
387
+ if (refNode && nth >= 0 && nth < l) {
388
+ if (selectorNodes.size) {
389
+ if (selectorNodes.has(refNode)) {
390
+ if (j === nth) {
391
+ matched.add(refNode);
392
+ nth += a;
393
+ }
394
+ if (a > 0) {
395
+ j++;
396
+ } else {
397
+ j--;
398
+ }
399
+ }
400
+ } else if (i === nth) {
401
+ if (!selector) {
402
+ matched.add(refNode);
403
+ }
404
+ nth += a;
405
+ }
406
+ if (reverse) {
407
+ refNode = walker.previousSibling();
408
+ } else {
409
+ refNode = walker.nextSibling();
410
+ }
411
+ i++;
412
+ } else {
413
+ break;
414
+ }
415
+ }
416
+ }
417
+ }
418
+ if (reverse && matched.size > 1) {
419
+ const m = [...matched];
420
+ matched = new Set(m.reverse());
421
+ }
422
+ } else if (node === this.#root && this.#root.nodeType === ELEMENT_NODE &&
423
+ (a + b) === 1) {
424
+ if (selectorBranches) {
425
+ let bool;
426
+ for (const leaves of selectorBranches) {
427
+ bool = this._matchLeaves(leaves, node, opt);
428
+ if (bool) {
429
+ break;
430
+ }
431
+ }
432
+ if (bool) {
433
+ matched.add(node);
434
+ }
435
+ } else {
436
+ matched.add(node);
437
+ }
438
+ }
439
+ return matched;
440
+ }
441
+
442
+ /**
443
+ * collect nth of type
444
+ * @private
445
+ * @param {object} anb - An+B options
446
+ * @param {number} anb.a - a
447
+ * @param {number} anb.b - b
448
+ * @param {boolean} [anb.reverse] - reverse order
449
+ * @param {object} node - Element node
450
+ * @returns {Set.<object>} - collection of matched nodes
451
+ */
452
+ _collectNthOfType(anb, node) {
453
+ const { a, b, reverse } = anb;
454
+ const { localName, parentNode, prefix } = node;
455
+ let matched = new Set();
456
+ if (parentNode) {
457
+ const walker = this.#document.createTreeWalker(parentNode, WALKER_FILTER);
458
+ let l = 0;
459
+ let refNode = walker.firstChild();
460
+ while (refNode) {
461
+ l++;
462
+ refNode = walker.nextSibling();
463
+ }
464
+ // :first-of-type, :last-of-type
465
+ if (a === 0) {
466
+ if (b > 0 && b <= l) {
467
+ let j = 0;
468
+ refNode = this._traverse(parentNode, walker);
469
+ if (reverse) {
470
+ refNode = walker.lastChild();
471
+ } else {
472
+ refNode = walker.firstChild();
473
+ }
474
+ while (refNode) {
475
+ const { localName: itemLocalName, prefix: itemPrefix } = refNode;
476
+ if (itemLocalName === localName && itemPrefix === prefix) {
477
+ if (j === b - 1) {
478
+ matched.add(refNode);
479
+ break;
480
+ }
481
+ j++;
482
+ }
483
+ if (reverse) {
484
+ refNode = walker.previousSibling();
485
+ } else {
486
+ refNode = walker.nextSibling();
487
+ }
488
+ }
489
+ }
490
+ // :nth-of-type()
491
+ } else {
492
+ let nth = b - 1;
493
+ if (a > 0) {
494
+ while (nth < 0) {
495
+ nth += a;
496
+ }
497
+ }
498
+ if (nth >= 0 && nth < l) {
499
+ let j = a > 0 ? 0 : b - 1;
500
+ refNode = this._traverse(parentNode, walker);
501
+ if (reverse) {
502
+ refNode = walker.lastChild();
503
+ } else {
504
+ refNode = walker.firstChild();
505
+ }
506
+ while (refNode) {
507
+ const { localName: itemLocalName, prefix: itemPrefix } = refNode;
508
+ if (itemLocalName === localName && itemPrefix === prefix) {
509
+ if (j === nth) {
510
+ matched.add(refNode);
511
+ nth += a;
512
+ }
513
+ if (nth < 0 || nth >= l) {
514
+ break;
515
+ } else if (a > 0) {
516
+ j++;
517
+ } else {
518
+ j--;
519
+ }
520
+ }
521
+ if (reverse) {
522
+ refNode = walker.previousSibling();
523
+ } else {
524
+ refNode = walker.nextSibling();
525
+ }
526
+ }
527
+ }
528
+ }
529
+ if (reverse && matched.size > 1) {
530
+ const m = [...matched];
531
+ matched = new Set(m.reverse());
532
+ }
533
+ } else if (node === this.#root && this.#root.nodeType === ELEMENT_NODE &&
534
+ (a + b) === 1) {
535
+ matched.add(node);
536
+ }
537
+ return matched;
538
+ }
539
+
540
+ /**
541
+ * match An+B
542
+ * @private
543
+ * @param {object} ast - AST
544
+ * @param {object} node - Element node
545
+ * @param {string} nthName - nth pseudo-class name
546
+ * @param {object} opt - options
547
+ * @returns {Set.<object>} - collection of matched nodes
548
+ */
549
+ _matchAnPlusB(ast, node, nthName, opt) {
550
+ const {
551
+ nth: {
552
+ a,
553
+ b,
554
+ name: nthIdentName
555
+ },
556
+ selector
557
+ } = ast;
558
+ const identName = unescapeSelector(nthIdentName);
559
+ const anbMap = new Map();
560
+ if (identName) {
561
+ if (identName === 'even') {
562
+ anbMap.set('a', 2);
563
+ anbMap.set('b', 0);
564
+ } else if (identName === 'odd') {
565
+ anbMap.set('a', 2);
566
+ anbMap.set('b', 1);
567
+ }
568
+ if (nthName.indexOf('last') > -1) {
569
+ anbMap.set('reverse', true);
570
+ }
571
+ } else {
572
+ if (typeof a === 'string' && /-?\d+/.test(a)) {
573
+ anbMap.set('a', a * 1);
574
+ } else {
575
+ anbMap.set('a', 0);
576
+ }
577
+ if (typeof b === 'string' && /-?\d+/.test(b)) {
578
+ anbMap.set('b', b * 1);
579
+ } else {
580
+ anbMap.set('b', 0);
581
+ }
582
+ if (nthName.indexOf('last') > -1) {
583
+ anbMap.set('reverse', true);
584
+ }
585
+ }
586
+ let matched = new Set();
587
+ if (anbMap.has('a') && anbMap.has('b')) {
588
+ if (/^nth-(?:last-)?child$/.test(nthName)) {
589
+ if (selector) {
590
+ anbMap.set('selector', selector);
591
+ }
592
+ const anb = Object.fromEntries(anbMap);
593
+ const nodes = this._collectNthChild(anb, node, opt);
594
+ if (nodes.size) {
595
+ matched = nodes;
596
+ }
597
+ } else if (/^nth-(?:last-)?of-type$/.test(nthName)) {
598
+ const anb = Object.fromEntries(anbMap);
599
+ const nodes = this._collectNthOfType(anb, node);
600
+ if (nodes.size) {
601
+ matched = nodes;
602
+ }
603
+ }
604
+ }
605
+ return matched;
606
+ }
607
+
608
+ /**
609
+ * match directionality pseudo-class - :dir()
610
+ * @private
611
+ * @param {object} ast - AST
612
+ * @param {object} node - Element node
613
+ * @returns {?object} - matched node
614
+ */
615
+ _matchDirectionPseudoClass(ast, node) {
616
+ const astName = unescapeSelector(ast.name);
617
+ const dir = getDirectionality(node);
618
+ let res;
619
+ if (astName === dir) {
620
+ res = node;
621
+ }
622
+ return res ?? null;
623
+ }
624
+
625
+ /**
626
+ * match language pseudo-class - :lang()
627
+ * @private
628
+ * @see https://datatracker.ietf.org/doc/html/rfc4647#section-3.3.1
629
+ * @param {object} ast - AST
630
+ * @param {object} node - Element node
631
+ * @returns {?object} - matched node
632
+ */
633
+ _matchLanguagePseudoClass(ast, node) {
634
+ const astName = unescapeSelector(ast.name);
635
+ let res;
636
+ if (astName === '*') {
637
+ if (node.hasAttribute('lang')) {
638
+ if (node.getAttribute('lang')) {
639
+ res = node;
640
+ }
641
+ } else {
642
+ let parent = node.parentNode;
643
+ while (parent) {
644
+ if (parent.nodeType === ELEMENT_NODE) {
645
+ if (parent.hasAttribute('lang')) {
646
+ if (parent.getAttribute('lang')) {
647
+ res = node;
648
+ }
649
+ break;
650
+ }
651
+ parent = parent.parentNode;
652
+ } else {
653
+ break;
654
+ }
655
+ }
656
+ }
657
+ } else if (astName) {
658
+ const langPart = `(?:-${ALPHA_NUM})*`;
659
+ const regLang = new RegExp(`^(?:\\*-)?${ALPHA_NUM}${langPart}$`, 'i');
660
+ if (regLang.test(astName)) {
661
+ let regExtendedLang;
662
+ if (astName.indexOf('-') > -1) {
663
+ const [langMain, langSub, ...langRest] = astName.split('-');
664
+ let extendedMain;
665
+ if (langMain === '*') {
666
+ extendedMain = `${ALPHA_NUM}${langPart}`;
667
+ } else {
668
+ extendedMain = `${langMain}${langPart}`;
669
+ }
670
+ const extendedSub = `-${langSub}${langPart}`;
671
+ const len = langRest.length;
672
+ let extendedRest = '';
673
+ if (len) {
674
+ for (let i = 0; i < len; i++) {
675
+ extendedRest += `-${langRest[i]}${langPart}`;
676
+ }
677
+ }
678
+ regExtendedLang =
679
+ new RegExp(`^${extendedMain}${extendedSub}${extendedRest}$`, 'i');
680
+ } else {
681
+ regExtendedLang = new RegExp(`^${astName}${langPart}$`, 'i');
682
+ }
683
+ if (node.hasAttribute('lang')) {
684
+ if (regExtendedLang.test(node.getAttribute('lang'))) {
685
+ res = node;
686
+ }
687
+ } else {
688
+ let parent = node.parentNode;
689
+ while (parent) {
690
+ if (parent.nodeType === ELEMENT_NODE) {
691
+ if (parent.hasAttribute('lang')) {
692
+ const value = parent.getAttribute('lang');
693
+ if (regExtendedLang.test(value)) {
694
+ res = node;
695
+ }
696
+ break;
697
+ }
698
+ parent = parent.parentNode;
699
+ } else {
700
+ break;
701
+ }
702
+ }
703
+ }
704
+ }
705
+ }
706
+ return res ?? null;
707
+ }
708
+
709
+ /**
710
+ * match :has() pseudo-class function
711
+ * @private
712
+ * @param {Array.<object>} leaves - AST leaves
713
+ * @param {object} node - Element node
714
+ * @param {object} opt - options
715
+ * @returns {boolean} - result
716
+ */
717
+ _matchHasPseudoFunc(leaves, node, opt = {}) {
718
+ let bool;
719
+ if (Array.isArray(leaves) && leaves.length) {
720
+ const [leaf] = leaves;
721
+ const { type: leafType } = leaf;
722
+ let combo;
723
+ if (leafType === COMBINATOR) {
724
+ combo = leaves.shift();
725
+ } else {
726
+ combo = {
727
+ name: ' ',
728
+ type: COMBINATOR
729
+ };
730
+ }
731
+ const twigLeaves = [];
732
+ while (leaves.length) {
733
+ const [item] = leaves;
734
+ const { type: itemType } = item;
735
+ if (itemType === COMBINATOR) {
736
+ break;
737
+ } else {
738
+ twigLeaves.push(leaves.shift());
739
+ }
740
+ }
741
+ const twig = {
742
+ combo,
743
+ leaves: twigLeaves
744
+ };
745
+ opt.dir = DIR_NEXT;
746
+ const nodes = this._matchCombinator(twig, node, opt);
747
+ if (nodes.size) {
748
+ if (leaves.length) {
749
+ for (const nextNode of nodes) {
750
+ bool = this._matchHasPseudoFunc(Object.assign([], leaves),
751
+ nextNode, opt);
752
+ if (bool) {
753
+ break;
754
+ }
755
+ }
756
+ } else {
757
+ bool = true;
758
+ }
759
+ }
760
+ }
761
+ return !!bool;
762
+ }
763
+
764
+ /**
765
+ * match logical pseudo-class functions - :has(), :is(), :not(), :where()
766
+ * @private
767
+ * @param {object} astData - AST data
768
+ * @param {object} node - Element node
769
+ * @param {object} opt - options
770
+ * @returns {?object} - matched node
771
+ */
772
+ _matchLogicalPseudoFunc(astData, node, opt = {}) {
773
+ const {
774
+ astName = '', branches = [], selector = '', twigBranches = []
775
+ } = astData;
776
+ let res;
777
+ if (astName === 'has') {
778
+ if (selector.includes(':has(')) {
779
+ res = null;
780
+ } else {
781
+ let bool;
782
+ for (const leaves of branches) {
783
+ bool = this._matchHasPseudoFunc(Object.assign([], leaves), node, opt);
784
+ if (bool) {
785
+ break;
786
+ }
787
+ }
788
+ if (bool) {
789
+ res = node;
790
+ }
791
+ }
792
+ } else {
793
+ const forgive = /^(?:is|where)$/.test(astName);
794
+ opt.forgive = forgive;
795
+ const l = twigBranches.length;
796
+ let bool;
797
+ for (let i = 0; i < l; i++) {
798
+ const branch = twigBranches[i];
799
+ const lastIndex = branch.length - 1;
800
+ const { leaves } = branch[lastIndex];
801
+ bool = this._matchLeaves(leaves, node, opt);
802
+ if (bool && lastIndex > 0) {
803
+ let nextNodes = new Set([node]);
804
+ for (let j = lastIndex - 1; j >= 0; j--) {
805
+ const twig = branch[j];
806
+ const arr = [];
807
+ opt.dir = DIR_PREV;
808
+ for (const nextNode of nextNodes) {
809
+ const m = this._matchCombinator(twig, nextNode, opt);
810
+ if (m.size) {
811
+ arr.push(...m);
812
+ }
813
+ }
814
+ if (arr.length) {
815
+ if (j === 0) {
816
+ bool = true;
817
+ } else {
818
+ nextNodes = new Set(arr);
819
+ }
820
+ } else {
821
+ bool = false;
822
+ break;
823
+ }
824
+ }
825
+ }
826
+ if (bool) {
827
+ break;
828
+ }
829
+ }
830
+ if (astName === 'not') {
831
+ if (!bool) {
832
+ res = node;
833
+ }
834
+ } else if (bool) {
835
+ res = node;
836
+ }
837
+ }
838
+ return res ?? null;
839
+ }
840
+
841
+ /**
842
+ * match pseudo-class selector
843
+ * @private
844
+ * @see https://html.spec.whatwg.org/#pseudo-classes
845
+ * @param {object} ast - AST
846
+ * @param {object} node - Element node
847
+ * @param {object} opt - options
848
+ * @param {boolean} [opt.forgive] - forgive unknown pseudo-class
849
+ * @param {boolean} [opt.warn] - warn unsupported pseudo-class
850
+ * @returns {Set.<object>} - collection of matched nodes
851
+ */
852
+ _matchPseudoClassSelector(ast, node, opt = {}) {
853
+ const { children: astChildren } = ast;
854
+ const { localName, parentNode } = node;
855
+ const { forgive, warn } = opt;
856
+ const astName = unescapeSelector(ast.name);
857
+ let matched = new Set();
858
+ // :has(), :is(), :not(), :where()
859
+ if (REG_LOGICAL_PSEUDO.test(astName)) {
860
+ let astData;
861
+ if (this.#cache.has(ast)) {
862
+ astData = this.#cache.get(ast);
863
+ } else {
864
+ const branches = walkAST(ast);
865
+ const selectors = [];
866
+ const twigBranches = [];
867
+ for (const [...leaves] of branches) {
868
+ for (const leaf of leaves) {
869
+ const css = generateCSS(leaf);
870
+ selectors.push(css);
871
+ }
872
+ const branch = [];
873
+ const leavesSet = new Set();
874
+ let item = leaves.shift();
875
+ while (item) {
876
+ if (item.type === COMBINATOR) {
877
+ branch.push({
878
+ combo: item,
879
+ leaves: [...leavesSet]
880
+ });
881
+ leavesSet.clear();
882
+ } else if (item) {
883
+ leavesSet.add(item);
884
+ }
885
+ if (leaves.length) {
886
+ item = leaves.shift();
887
+ } else {
888
+ branch.push({
889
+ combo: null,
890
+ leaves: [...leavesSet]
891
+ });
892
+ leavesSet.clear();
893
+ break;
894
+ }
895
+ }
896
+ twigBranches.push(branch);
897
+ }
898
+ astData = {
899
+ astName,
900
+ branches,
901
+ twigBranches,
902
+ selector: selectors.join(',')
903
+ };
904
+ this.#cache.set(ast, astData);
905
+ }
906
+ const res = this._matchLogicalPseudoFunc(astData, node, opt);
907
+ if (res) {
908
+ matched.add(res);
909
+ }
910
+ } else if (Array.isArray(astChildren)) {
911
+ const [branch] = astChildren;
912
+ // :nth-child(), :nth-last-child(), nth-of-type(), :nth-last-of-type()
913
+ if (/^nth-(?:last-)?(?:child|of-type)$/.test(astName)) {
914
+ const nodes = this._matchAnPlusB(branch, node, astName, opt);
915
+ if (nodes.size) {
916
+ matched = nodes;
917
+ }
918
+ // :dir()
919
+ } else if (astName === 'dir') {
920
+ const res = this._matchDirectionPseudoClass(branch, node);
921
+ if (res) {
922
+ matched.add(res);
923
+ }
924
+ // :lang()
925
+ } else if (astName === 'lang') {
926
+ const res = this._matchLanguagePseudoClass(branch, node);
927
+ if (res) {
928
+ matched.add(res);
929
+ }
930
+ } else {
931
+ switch (astName) {
932
+ case 'current':
933
+ case 'nth-col':
934
+ case 'nth-last-col': {
935
+ if (warn) {
936
+ const msg = `Unsupported pseudo-class :${astName}()`;
937
+ throw new DOMException(msg, NOT_SUPPORTED_ERR);
938
+ }
939
+ break;
940
+ }
941
+ case 'host':
942
+ case 'host-context': {
943
+ // ignore
944
+ break;
945
+ }
946
+ default: {
947
+ if (!forgive) {
948
+ const msg = `Unknown pseudo-class :${astName}()`;
949
+ throw new DOMException(msg, SYNTAX_ERR);
950
+ }
951
+ }
952
+ }
953
+ }
954
+ } else {
955
+ const regAnchor = /^a(?:rea)?$/;
956
+ const regFormCtrl =
957
+ /^(?:(?:fieldse|inpu|selec)t|button|opt(?:group|ion)|textarea)$/;
958
+ const regFormValidity = /^(?:(?:inpu|selec)t|button|form|textarea)$/;
959
+ const regInteract = /^d(?:etails|ialog)$/;
960
+ const regTypeCheck = /^(?:checkbox|radio)$/;
961
+ const regTypeDate = /^(?:date(?:time-local)?|month|time|week)$/;
962
+ const regTypeRange =
963
+ /(?:(?:rang|tim)e|date(?:time-local)?|month|number|week)$/;
964
+ const regTypeText = /^(?:(?:emai|te|ur)l|number|password|search|text)$/;
965
+ switch (astName) {
966
+ case 'any-link':
967
+ case 'link': {
968
+ if (regAnchor.test(localName) && node.hasAttribute('href')) {
969
+ matched.add(node);
970
+ }
971
+ break;
972
+ }
973
+ case 'local-link': {
974
+ if (regAnchor.test(localName) && node.hasAttribute('href')) {
975
+ const { href, origin, pathname } = new URL(this.#document.URL);
976
+ const attrURL = new URL(node.getAttribute('href'), href);
977
+ if (attrURL.origin === origin && attrURL.pathname === pathname) {
978
+ matched.add(node);
979
+ }
980
+ }
981
+ break;
982
+ }
983
+ case 'visited': {
984
+ // prevent fingerprinting
985
+ break;
986
+ }
987
+ case 'target': {
988
+ const { hash } = new URL(this.#document.URL);
989
+ if (node.id && hash === `#${node.id}` &&
990
+ this.#document.contains(node)) {
991
+ matched.add(node);
992
+ }
993
+ break;
994
+ }
995
+ case 'target-within': {
996
+ const { hash } = new URL(this.#document.URL);
997
+ if (hash) {
998
+ const id = hash.replace(/^#/, '');
999
+ let current = this.#document.getElementById(id);
1000
+ while (current) {
1001
+ if (current === node) {
1002
+ matched.add(node);
1003
+ break;
1004
+ }
1005
+ current = current.parentNode;
1006
+ }
1007
+ }
1008
+ break;
1009
+ }
1010
+ case 'scope': {
1011
+ if (this.#node.nodeType === ELEMENT_NODE) {
1012
+ if (node === this.#node) {
1013
+ matched.add(node);
1014
+ }
1015
+ } else if (node === this.#document.documentElement) {
1016
+ matched.add(node);
1017
+ }
1018
+ break;
1019
+ }
1020
+ case 'focus': {
1021
+ if (node === this.#document.activeElement) {
1022
+ matched.add(node);
1023
+ }
1024
+ break;
1025
+ }
1026
+ case 'focus-within': {
1027
+ let current = this.#document.activeElement;
1028
+ while (current) {
1029
+ if (current === node) {
1030
+ matched.add(node);
1031
+ break;
1032
+ }
1033
+ current = current.parentNode;
1034
+ }
1035
+ break;
1036
+ }
1037
+ case 'open': {
1038
+ if (regInteract.test(localName) && node.hasAttribute('open')) {
1039
+ matched.add(node);
1040
+ }
1041
+ break;
1042
+ }
1043
+ case 'closed': {
1044
+ if (regInteract.test(localName) && !node.hasAttribute('open')) {
1045
+ matched.add(node);
1046
+ }
1047
+ break;
1048
+ }
1049
+ case 'disabled': {
1050
+ if (regFormCtrl.test(localName) || isCustomElementName(localName)) {
1051
+ if (node.disabled || node.hasAttribute('disabled')) {
1052
+ matched.add(node);
1053
+ } else {
1054
+ let parent = parentNode;
1055
+ while (parent) {
1056
+ if (parent.localName === 'fieldset') {
1057
+ break;
1058
+ }
1059
+ parent = parent.parentNode;
1060
+ }
1061
+ if (parent && parentNode.localName !== 'legend' &&
1062
+ parent.hasAttribute('disabled')) {
1063
+ matched.add(node);
1064
+ }
1065
+ }
1066
+ }
1067
+ break;
1068
+ }
1069
+ case 'enabled': {
1070
+ if ((regFormCtrl.test(localName) || isCustomElementName(localName)) &&
1071
+ !(node.disabled && node.hasAttribute('disabled'))) {
1072
+ matched.add(node);
1073
+ }
1074
+ break;
1075
+ }
1076
+ case 'read-only': {
1077
+ switch (localName) {
1078
+ case 'textarea': {
1079
+ if (node.readonly || node.hasAttribute('readonly') ||
1080
+ node.disabled || node.hasAttribute('disabled')) {
1081
+ matched.add(node);
1082
+ }
1083
+ break;
1084
+ }
1085
+ case 'input': {
1086
+ if ((!node.type || regTypeDate.test(node.type) ||
1087
+ regTypeText.test(node.type)) &&
1088
+ (node.readonly || node.hasAttribute('readonly') ||
1089
+ node.disabled || node.hasAttribute('disabled'))) {
1090
+ matched.add(node);
1091
+ }
1092
+ break;
1093
+ }
1094
+ default: {
1095
+ if (!isContentEditable(node)) {
1096
+ matched.add(node);
1097
+ }
1098
+ }
1099
+ }
1100
+ break;
1101
+ }
1102
+ case 'read-write': {
1103
+ switch (localName) {
1104
+ case 'textarea': {
1105
+ if (!(node.readonly || node.hasAttribute('readonly') ||
1106
+ node.disabled || node.hasAttribute('disabled'))) {
1107
+ matched.add(node);
1108
+ }
1109
+ break;
1110
+ }
1111
+ case 'input': {
1112
+ if ((!node.type || regTypeDate.test(node.type) ||
1113
+ regTypeText.test(node.type)) &&
1114
+ !(node.readonly || node.hasAttribute('readonly') ||
1115
+ node.disabled || node.hasAttribute('disabled'))) {
1116
+ matched.add(node);
1117
+ }
1118
+ break;
1119
+ }
1120
+ default: {
1121
+ if (isContentEditable(node)) {
1122
+ matched.add(node);
1123
+ }
1124
+ }
1125
+ }
1126
+ break;
1127
+ }
1128
+ case 'placeholder-shown': {
1129
+ let targetNode;
1130
+ if (localName === 'textarea') {
1131
+ targetNode = node;
1132
+ } else if (localName === 'input') {
1133
+ if (node.hasAttribute('type')) {
1134
+ if (regTypeText.test(node.getAttribute('type'))) {
1135
+ targetNode = node;
1136
+ }
1137
+ } else {
1138
+ targetNode = node;
1139
+ }
1140
+ }
1141
+ if (targetNode && node.value === '' &&
1142
+ node.hasAttribute('placeholder') &&
1143
+ node.getAttribute('placeholder').trim().length) {
1144
+ matched.add(node);
1145
+ }
1146
+ break;
1147
+ }
1148
+ case 'checked': {
1149
+ if ((node.checked && localName === 'input' &&
1150
+ node.hasAttribute('type') &&
1151
+ regTypeCheck.test(node.getAttribute('type'))) ||
1152
+ (node.selected && localName === 'option')) {
1153
+ matched.add(node);
1154
+ }
1155
+ break;
1156
+ }
1157
+ case 'indeterminate': {
1158
+ if ((node.indeterminate && localName === 'input' &&
1159
+ node.type === 'checkbox') ||
1160
+ (localName === 'progress' && !node.hasAttribute('value'))) {
1161
+ matched.add(node);
1162
+ } else if (localName === 'input' && node.type === 'radio' &&
1163
+ !node.hasAttribute('checked')) {
1164
+ const nodeName = node.name;
1165
+ let parent = node.parentNode;
1166
+ while (parent) {
1167
+ if (parent.localName === 'form') {
1168
+ break;
1169
+ }
1170
+ parent = parent.parentNode;
1171
+ }
1172
+ if (!parent) {
1173
+ parent = this.#document.documentElement;
1174
+ }
1175
+ let checked;
1176
+ const items = parent.getElementsByTagName('input');
1177
+ const l = items.length;
1178
+ if (l) {
1179
+ for (let i = 0; i < l; i++) {
1180
+ const item = items[i];
1181
+ if (item.getAttribute('type') === 'radio') {
1182
+ if (nodeName) {
1183
+ if (item.getAttribute('name') === nodeName) {
1184
+ checked = !!item.checked;
1185
+ }
1186
+ } else if (!item.hasAttribute('name')) {
1187
+ checked = !!item.checked;
1188
+ }
1189
+ if (checked) {
1190
+ break;
1191
+ }
1192
+ }
1193
+ }
1194
+ }
1195
+ if (!checked) {
1196
+ matched.add(node);
1197
+ }
1198
+ }
1199
+ break;
1200
+ }
1201
+ case 'default': {
1202
+ const regTypeReset = /^(?:button|reset)$/;
1203
+ const regTypeSubmit = /^(?:image|submit)$/;
1204
+ // button[type="submit"], input[type="submit"], input[type="image"]
1205
+ if ((localName === 'button' &&
1206
+ !(node.hasAttribute('type') &&
1207
+ regTypeReset.test(node.getAttribute('type')))) ||
1208
+ (localName === 'input' && node.hasAttribute('type') &&
1209
+ regTypeSubmit.test(node.getAttribute('type')))) {
1210
+ let form = node.parentNode;
1211
+ while (form) {
1212
+ if (form.localName === 'form') {
1213
+ break;
1214
+ }
1215
+ form = form.parentNode;
1216
+ }
1217
+ if (form) {
1218
+ const walker =
1219
+ this.#document.createTreeWalker(form, SHOW_ELEMENT);
1220
+ let nextNode = walker.firstChild();
1221
+ while (nextNode) {
1222
+ const nodeName = nextNode.localName;
1223
+ let m;
1224
+ if (nodeName === 'button') {
1225
+ m = !(nextNode.hasAttribute('type') &&
1226
+ regTypeReset.test(nextNode.getAttribute('type')));
1227
+ } else if (nodeName === 'input') {
1228
+ m = nextNode.hasAttribute('type') &&
1229
+ regTypeSubmit.test(nextNode.getAttribute('type'));
1230
+ }
1231
+ if (m) {
1232
+ if (nextNode === node) {
1233
+ matched.add(node);
1234
+ }
1235
+ break;
1236
+ }
1237
+ nextNode = walker.nextNode();
1238
+ }
1239
+ }
1240
+ // input[type="checkbox"], input[type="radio"]
1241
+ } else if (localName === 'input' && node.hasAttribute('type') &&
1242
+ regTypeCheck.test(node.getAttribute('type')) &&
1243
+ (node.checked || node.hasAttribute('checked'))) {
1244
+ matched.add(node);
1245
+ // option
1246
+ } else if (localName === 'option') {
1247
+ let isMultiple = false;
1248
+ let parent = parentNode;
1249
+ while (parent) {
1250
+ if (parent.localName === 'datalist') {
1251
+ break;
1252
+ } else if (parent.localName === 'select') {
1253
+ if (parent.multiple || parent.hasAttribute('multiple')) {
1254
+ isMultiple = true;
1255
+ }
1256
+ break;
1257
+ }
1258
+ parent = parent.parentNode;
1259
+ }
1260
+ if (isMultiple) {
1261
+ if (node.selected || node.hasAttribute('selected')) {
1262
+ matched.add(node);
1263
+ }
1264
+ } else {
1265
+ const defaultOpt = new Set();
1266
+ const walker =
1267
+ this.#document.createTreeWalker(parentNode, SHOW_ELEMENT);
1268
+ let refNode = walker.firstChild();
1269
+ while (refNode) {
1270
+ if (refNode.selected || refNode.hasAttribute('selected')) {
1271
+ defaultOpt.add(refNode);
1272
+ break;
1273
+ }
1274
+ refNode = walker.nextSibling();
1275
+ }
1276
+ if (defaultOpt.size) {
1277
+ if (defaultOpt.has(node)) {
1278
+ matched.add(node);
1279
+ }
1280
+ }
1281
+ }
1282
+ }
1283
+ break;
1284
+ }
1285
+ case 'valid': {
1286
+ if (regFormValidity.test(localName)) {
1287
+ if (node.checkValidity()) {
1288
+ matched.add(node);
1289
+ }
1290
+ } else if (localName === 'fieldset') {
1291
+ let bool;
1292
+ const walker = this.#document.createTreeWalker(node, SHOW_ELEMENT);
1293
+ let refNode = walker.firstChild();
1294
+ while (refNode) {
1295
+ if (regFormValidity.test(refNode.localName)) {
1296
+ bool = refNode.checkValidity();
1297
+ if (!bool) {
1298
+ break;
1299
+ }
1300
+ }
1301
+ refNode = walker.nextNode();
1302
+ }
1303
+ if (bool) {
1304
+ matched.add(node);
1305
+ }
1306
+ }
1307
+ break;
1308
+ }
1309
+ case 'invalid': {
1310
+ if (regFormValidity.test(localName)) {
1311
+ if (!node.checkValidity()) {
1312
+ matched.add(node);
1313
+ }
1314
+ } else if (localName === 'fieldset') {
1315
+ let bool;
1316
+ const walker = this.#document.createTreeWalker(node, SHOW_ELEMENT);
1317
+ let refNode = walker.firstChild();
1318
+ while (refNode) {
1319
+ if (regFormValidity.test(refNode.localName)) {
1320
+ bool = refNode.checkValidity();
1321
+ if (!bool) {
1322
+ break;
1323
+ }
1324
+ }
1325
+ refNode = walker.nextNode();
1326
+ }
1327
+ if (!bool) {
1328
+ matched.add(node);
1329
+ }
1330
+ }
1331
+ break;
1332
+ }
1333
+ case 'in-range': {
1334
+ if (localName === 'input' &&
1335
+ !(node.readonly || node.hasAttribute('readonly')) &&
1336
+ !(node.disabled || node.hasAttribute('disabled')) &&
1337
+ node.hasAttribute('type') &&
1338
+ regTypeRange.test(node.getAttribute('type')) &&
1339
+ !(node.validity.rangeUnderflow ||
1340
+ node.validity.rangeOverflow) &&
1341
+ (node.hasAttribute('min') || node.hasAttribute('max') ||
1342
+ node.getAttribute('type') === 'range')) {
1343
+ matched.add(node);
1344
+ }
1345
+ break;
1346
+ }
1347
+ case 'out-of-range': {
1348
+ if (localName === 'input' &&
1349
+ !(node.readonly || node.hasAttribute('readonly')) &&
1350
+ !(node.disabled || node.hasAttribute('disabled')) &&
1351
+ node.hasAttribute('type') &&
1352
+ regTypeRange.test(node.getAttribute('type')) &&
1353
+ (node.validity.rangeUnderflow || node.validity.rangeOverflow)) {
1354
+ matched.add(node);
1355
+ }
1356
+ break;
1357
+ }
1358
+ case 'required': {
1359
+ let targetNode;
1360
+ if (/^(?:select|textarea)$/.test(localName)) {
1361
+ targetNode = node;
1362
+ } else if (localName === 'input') {
1363
+ if (node.hasAttribute('type')) {
1364
+ const inputType = node.getAttribute('type');
1365
+ if (inputType === 'file' || regTypeCheck.test(inputType) ||
1366
+ regTypeDate.test(inputType) || regTypeText.test(inputType)) {
1367
+ targetNode = node;
1368
+ }
1369
+ } else {
1370
+ targetNode = node;
1371
+ }
1372
+ }
1373
+ if (targetNode &&
1374
+ (node.required || node.hasAttribute('required'))) {
1375
+ matched.add(node);
1376
+ }
1377
+ break;
1378
+ }
1379
+ case 'optional': {
1380
+ let targetNode;
1381
+ if (/^(?:select|textarea)$/.test(localName)) {
1382
+ targetNode = node;
1383
+ } else if (localName === 'input') {
1384
+ if (node.hasAttribute('type')) {
1385
+ const inputType = node.getAttribute('type');
1386
+ if (inputType === 'file' || regTypeCheck.test(inputType) ||
1387
+ regTypeDate.test(inputType) || regTypeText.test(inputType)) {
1388
+ targetNode = node;
1389
+ }
1390
+ } else {
1391
+ targetNode = node;
1392
+ }
1393
+ }
1394
+ if (targetNode &&
1395
+ !(node.required || node.hasAttribute('required'))) {
1396
+ matched.add(node);
1397
+ }
1398
+ break;
1399
+ }
1400
+ case 'root': {
1401
+ if (node === this.#document.documentElement) {
1402
+ matched.add(node);
1403
+ }
1404
+ break;
1405
+ }
1406
+ case 'empty': {
1407
+ if (node.hasChildNodes()) {
1408
+ let bool;
1409
+ const walker = this.#document.createTreeWalker(node, SHOW_ALL);
1410
+ let refNode = walker.firstChild();
1411
+ while (refNode) {
1412
+ bool = refNode.nodeType !== ELEMENT_NODE &&
1413
+ refNode.nodeType !== TEXT_NODE;
1414
+ if (!bool) {
1415
+ break;
1416
+ }
1417
+ refNode = walker.nextSibling();
1418
+ }
1419
+ if (bool) {
1420
+ matched.add(node);
1421
+ }
1422
+ } else {
1423
+ matched.add(node);
1424
+ }
1425
+ break;
1426
+ }
1427
+ case 'first-child': {
1428
+ if ((parentNode && node === parentNode.firstElementChild) ||
1429
+ (node === this.#root && this.#root.nodeType === ELEMENT_NODE)) {
1430
+ matched.add(node);
1431
+ }
1432
+ break;
1433
+ }
1434
+ case 'last-child': {
1435
+ if ((parentNode && node === parentNode.lastElementChild) ||
1436
+ (node === this.#root && this.#root.nodeType === ELEMENT_NODE)) {
1437
+ matched.add(node);
1438
+ }
1439
+ break;
1440
+ }
1441
+ case 'only-child': {
1442
+ if ((parentNode &&
1443
+ node === parentNode.firstElementChild &&
1444
+ node === parentNode.lastElementChild) ||
1445
+ (node === this.#root && this.#root.nodeType === ELEMENT_NODE)) {
1446
+ matched.add(node);
1447
+ }
1448
+ break;
1449
+ }
1450
+ case 'first-of-type': {
1451
+ if (parentNode) {
1452
+ const [node1] = this._collectNthOfType({
1453
+ a: 0,
1454
+ b: 1
1455
+ }, node);
1456
+ if (node1) {
1457
+ matched.add(node1);
1458
+ }
1459
+ } else if (node === this.#root &&
1460
+ this.#root.nodeType === ELEMENT_NODE) {
1461
+ matched.add(node);
1462
+ }
1463
+ break;
1464
+ }
1465
+ case 'last-of-type': {
1466
+ if (parentNode) {
1467
+ const [node1] = this._collectNthOfType({
1468
+ a: 0,
1469
+ b: 1,
1470
+ reverse: true
1471
+ }, node);
1472
+ if (node1) {
1473
+ matched.add(node1);
1474
+ }
1475
+ } else if (node === this.#root &&
1476
+ this.#root.nodeType === ELEMENT_NODE) {
1477
+ matched.add(node);
1478
+ }
1479
+ break;
1480
+ }
1481
+ case 'only-of-type': {
1482
+ if (parentNode) {
1483
+ const [node1] = this._collectNthOfType({
1484
+ a: 0,
1485
+ b: 1
1486
+ }, node);
1487
+ if (node1 === node) {
1488
+ const [node2] = this._collectNthOfType({
1489
+ a: 0,
1490
+ b: 1,
1491
+ reverse: true
1492
+ }, node);
1493
+ if (node2 === node) {
1494
+ matched.add(node);
1495
+ }
1496
+ }
1497
+ } else if (node === this.#root &&
1498
+ this.#root.nodeType === ELEMENT_NODE) {
1499
+ matched.add(node);
1500
+ }
1501
+ break;
1502
+ }
1503
+ case 'host':
1504
+ case 'host-context': {
1505
+ // ignore
1506
+ break;
1507
+ }
1508
+ // legacy pseudo-elements
1509
+ case 'after':
1510
+ case 'before':
1511
+ case 'first-letter':
1512
+ case 'first-line': {
1513
+ if (warn) {
1514
+ const msg = `Unsupported pseudo-element ::${astName}`;
1515
+ throw new DOMException(msg, NOT_SUPPORTED_ERR);
1516
+ }
1517
+ break;
1518
+ }
1519
+ case 'active':
1520
+ case 'autofill':
1521
+ case 'blank':
1522
+ case 'buffering':
1523
+ case 'current':
1524
+ case 'defined':
1525
+ case 'focus-visible':
1526
+ case 'fullscreen':
1527
+ case 'future':
1528
+ case 'hover':
1529
+ case 'modal':
1530
+ case 'muted':
1531
+ case 'past':
1532
+ case 'paused':
1533
+ case 'picture-in-picture':
1534
+ case 'playing':
1535
+ case 'seeking':
1536
+ case 'stalled':
1537
+ case 'user-invalid':
1538
+ case 'user-valid':
1539
+ case 'volume-locked':
1540
+ case '-webkit-autofill': {
1541
+ if (warn) {
1542
+ const msg = `Unsupported pseudo-class :${astName}`;
1543
+ throw new DOMException(msg, NOT_SUPPORTED_ERR);
1544
+ }
1545
+ break;
1546
+ }
1547
+ default: {
1548
+ if (astName.startsWith('-webkit-')) {
1549
+ if (warn) {
1550
+ const msg = `Unsupported pseudo-class :${astName}`;
1551
+ throw new DOMException(msg, NOT_SUPPORTED_ERR);
1552
+ }
1553
+ } else if (!forgive) {
1554
+ const msg = `Unknown pseudo-class :${astName}`;
1555
+ throw new DOMException(msg, SYNTAX_ERR);
1556
+ }
1557
+ }
1558
+ }
1559
+ }
1560
+ return matched;
1561
+ }
1562
+
1563
+ /**
1564
+ * match shadow host pseudo class
1565
+ * @private
1566
+ * @param {object} ast - AST
1567
+ * @param {object} node - DocumentFragment node
1568
+ * @returns {?object} - matched node
1569
+ */
1570
+ _matchShadowHostPseudoClass(ast, node) {
1571
+ const { children: astChildren } = ast;
1572
+ const astName = unescapeSelector(ast.name);
1573
+ let res;
1574
+ if (Array.isArray(astChildren)) {
1575
+ const [branch] = walkAST(astChildren[0]);
1576
+ const [...leaves] = branch;
1577
+ const { host } = node;
1578
+ if (astName === 'host') {
1579
+ let bool;
1580
+ for (const leaf of leaves) {
1581
+ const { type: leafType } = leaf;
1582
+ if (leafType === COMBINATOR) {
1583
+ const css = generateCSS(ast);
1584
+ const msg = `Invalid selector ${css}`;
1585
+ throw new DOMException(msg, SYNTAX_ERR);
1586
+ }
1587
+ bool = this._matchSelector(leaf, host).has(host);
1588
+ if (!bool) {
1589
+ break;
1590
+ }
1591
+ }
1592
+ if (bool) {
1593
+ res = node;
1594
+ }
1595
+ } else if (astName === 'host-context') {
1596
+ let bool;
1597
+ let parent = host;
1598
+ while (parent) {
1599
+ for (const leaf of leaves) {
1600
+ const { type: leafType } = leaf;
1601
+ if (leafType === COMBINATOR) {
1602
+ const css = generateCSS(ast);
1603
+ const msg = `Invalid selector ${css}`;
1604
+ throw new DOMException(msg, SYNTAX_ERR);
1605
+ }
1606
+ bool = this._matchSelector(leaf, parent).has(parent);
1607
+ if (!bool) {
1608
+ break;
1609
+ }
1610
+ }
1611
+ if (bool) {
1612
+ break;
1613
+ } else {
1614
+ parent = parent.parentNode;
1615
+ }
1616
+ }
1617
+ if (bool) {
1618
+ res = node;
1619
+ }
1620
+ }
1621
+ } else if (astName === 'host') {
1622
+ res = node;
1623
+ } else {
1624
+ const msg = `Invalid selector :${astName}`;
1625
+ throw new DOMException(msg, SYNTAX_ERR);
1626
+ }
1627
+ return res ?? null;
1628
+ }
1629
+
1630
+ /**
1631
+ * match selector
1632
+ * @private
1633
+ * @param {object} ast - AST
1634
+ * @param {object} node - Document, DocumentFragment, Element node
1635
+ * @param {object} [opt] - options
1636
+ * @returns {Set.<object>} - collection of matched nodes
1637
+ */
1638
+ _matchSelector(ast, node, opt) {
1639
+ const { type: astType } = ast;
1640
+ const astName = unescapeSelector(ast.name);
1641
+ let matched = new Set();
1642
+ if (node.nodeType === ELEMENT_NODE) {
1643
+ switch (astType) {
1644
+ case SELECTOR_PSEUDO_CLASS: {
1645
+ const nodes = this._matchPseudoClassSelector(ast, node, opt);
1646
+ if (nodes.size) {
1647
+ matched = nodes;
1648
+ }
1649
+ break;
1650
+ }
1651
+ case SELECTOR_PSEUDO_ELEMENT: {
1652
+ matchPseudoElementSelector(astName, opt);
1653
+ break;
1654
+ }
1655
+ default: {
1656
+ const res = matchSelector(ast, node, opt);
1657
+ if (res) {
1658
+ matched.add(res);
1659
+ }
1660
+ }
1661
+ }
1662
+ } else if (this.#shadow && astType === SELECTOR_PSEUDO_CLASS &&
1663
+ node.nodeType === DOCUMENT_FRAGMENT_NODE) {
1664
+ if (astName !== 'has' && REG_LOGICAL_PSEUDO.test(astName)) {
1665
+ const nodes = this._matchPseudoClassSelector(ast, node, opt);
1666
+ if (nodes.size) {
1667
+ matched = nodes;
1668
+ }
1669
+ } else if (REG_SHADOW_HOST.test(astName)) {
1670
+ const res = this._matchShadowHostPseudoClass(ast, node, opt);
1671
+ if (res) {
1672
+ matched.add(res);
1673
+ }
1674
+ }
1675
+ }
1676
+ return matched;
1677
+ }
1678
+
1679
+ /**
1680
+ * match leaves
1681
+ * @private
1682
+ * @param {Array.<object>} leaves - AST leaves
1683
+ * @param {object} node - node
1684
+ * @param {object} opt - options
1685
+ * @returns {boolean} - result
1686
+ */
1687
+ _matchLeaves(leaves, node, opt) {
1688
+ let bool;
1689
+ for (const leaf of leaves) {
1690
+ bool = this._matchSelector(leaf, node, opt).has(node);
1691
+ if (!bool) {
1692
+ break;
1693
+ }
1694
+ }
1695
+ return !!bool;
1696
+ }
1697
+
1698
+ /**
1699
+ * find descendant nodes
1700
+ * @private
1701
+ * @param {Array.<object>} leaves - AST leaves
1702
+ * @param {object} baseNode - base Element node
1703
+ * @param {object} opt - options
1704
+ * @returns {object} - collection of nodes and pending state
1705
+ */
1706
+ _findDescendantNodes(leaves, baseNode, opt) {
1707
+ const [leaf, ...filterLeaves] = leaves;
1708
+ const { type: leafType } = leaf;
1709
+ const leafName = unescapeSelector(leaf.name);
1710
+ const compound = filterLeaves.length > 0;
1711
+ let nodes = new Set();
1712
+ let pending = false;
1713
+ if (this.#shadow) {
1714
+ pending = true;
1715
+ } else {
1716
+ switch (leafType) {
1717
+ case SELECTOR_ID: {
1718
+ if (this.#root.nodeType === ELEMENT_NODE) {
1719
+ pending = true;
1720
+ } else {
1721
+ const node = this.#root.getElementById(leafName);
1722
+ if (node && node !== baseNode && baseNode.contains(node)) {
1723
+ if (compound) {
1724
+ const bool = this._matchLeaves(filterLeaves, node, opt);
1725
+ if (bool) {
1726
+ nodes.add(node);
1727
+ }
1728
+ } else {
1729
+ nodes.add(node);
1730
+ }
1731
+ }
1732
+ }
1733
+ break;
1734
+ }
1735
+ case SELECTOR_CLASS: {
1736
+ const items = baseNode.getElementsByClassName(leafName);
1737
+ const l = items.length;
1738
+ if (l) {
1739
+ if (compound) {
1740
+ for (let i = 0; i < l; i++) {
1741
+ const item = items[i];
1742
+ const bool = this._matchLeaves(filterLeaves, item, opt);
1743
+ if (bool) {
1744
+ nodes.add(item);
1745
+ }
1746
+ }
1747
+ } else {
1748
+ const arr = [].slice.call(items);
1749
+ nodes = new Set(arr);
1750
+ }
1751
+ }
1752
+ break;
1753
+ }
1754
+ case SELECTOR_TYPE: {
1755
+ if (this.#document.contentType === 'text/html' &&
1756
+ !/[*|]/.test(leafName)) {
1757
+ const items = baseNode.getElementsByTagName(leafName);
1758
+ const l = items.length;
1759
+ if (l) {
1760
+ if (compound) {
1761
+ for (let i = 0; i < l; i++) {
1762
+ const item = items[i];
1763
+ const bool = this._matchLeaves(filterLeaves, item, opt);
1764
+ if (bool) {
1765
+ nodes.add(item);
1766
+ }
1767
+ }
1768
+ } else {
1769
+ const arr = [].slice.call(items);
1770
+ nodes = new Set(arr);
1771
+ }
1772
+ }
1773
+ } else {
1774
+ pending = true;
1775
+ }
1776
+ break;
1777
+ }
1778
+ case SELECTOR_PSEUDO_ELEMENT: {
1779
+ matchPseudoElementSelector(leafName, opt);
1780
+ break;
1781
+ }
1782
+ default: {
1783
+ pending = true;
1784
+ }
1785
+ }
1786
+ }
1787
+ return {
1788
+ nodes,
1789
+ pending
1790
+ };
1791
+ }
1792
+
1793
+ /**
1794
+ * match combinator
1795
+ * @private
1796
+ * @param {object} twig - twig
1797
+ * @param {object} node - Element node
1798
+ * @param {object} opt - option
1799
+ * @returns {Set.<object>} - collection of matched nodes
1800
+ */
1801
+ _matchCombinator(twig, node, opt = {}) {
1802
+ const { combo, leaves } = twig;
1803
+ const { name: comboName } = combo;
1804
+ const { dir } = opt;
1805
+ let matched = new Set();
1806
+ if (dir === DIR_NEXT) {
1807
+ switch (comboName) {
1808
+ case '+': {
1809
+ const refNode = node.nextElementSibling;
1810
+ if (refNode) {
1811
+ const bool = this._matchLeaves(leaves, refNode, opt);
1812
+ if (bool) {
1813
+ matched.add(refNode);
1814
+ }
1815
+ }
1816
+ break;
1817
+ }
1818
+ case '~': {
1819
+ const { parentNode } = node;
1820
+ if (parentNode) {
1821
+ const walker =
1822
+ this.#document.createTreeWalker(parentNode, SHOW_ELEMENT);
1823
+ let refNode = this._traverse(node, walker);
1824
+ if (refNode === node) {
1825
+ refNode = walker.nextSibling();
1826
+ }
1827
+ while (refNode) {
1828
+ const bool = this._matchLeaves(leaves, refNode, opt);
1829
+ if (bool) {
1830
+ matched.add(refNode);
1831
+ }
1832
+ refNode = walker.nextSibling();
1833
+ }
1834
+ }
1835
+ break;
1836
+ }
1837
+ case '>': {
1838
+ const walker = this.#document.createTreeWalker(node, SHOW_ELEMENT);
1839
+ let refNode = walker.firstChild();
1840
+ while (refNode) {
1841
+ const bool = this._matchLeaves(leaves, refNode, opt);
1842
+ if (bool) {
1843
+ matched.add(refNode);
1844
+ }
1845
+ refNode = walker.nextSibling();
1846
+ }
1847
+ break;
1848
+ }
1849
+ case ' ':
1850
+ default: {
1851
+ const { nodes, pending } = this._findDescendantNodes(leaves, node);
1852
+ if (nodes.size) {
1853
+ matched = nodes;
1854
+ } else if (pending) {
1855
+ const walker = this.#document.createTreeWalker(node, SHOW_ELEMENT);
1856
+ let refNode = walker.nextNode();
1857
+ while (refNode) {
1858
+ const bool = this._matchLeaves(leaves, refNode, opt);
1859
+ if (bool) {
1860
+ matched.add(refNode);
1861
+ }
1862
+ refNode = walker.nextNode();
1863
+ }
1864
+ }
1865
+ }
1866
+ }
1867
+ } else {
1868
+ switch (comboName) {
1869
+ case '+': {
1870
+ const refNode = node.previousElementSibling;
1871
+ if (refNode) {
1872
+ const bool = this._matchLeaves(leaves, refNode, opt);
1873
+ if (bool) {
1874
+ matched.add(refNode);
1875
+ }
1876
+ }
1877
+ break;
1878
+ }
1879
+ case '~': {
1880
+ const walker =
1881
+ this.#document.createTreeWalker(node.parentNode, SHOW_ELEMENT);
1882
+ let refNode = walker.firstChild();
1883
+ while (refNode) {
1884
+ if (refNode === node) {
1885
+ break;
1886
+ } else {
1887
+ const bool = this._matchLeaves(leaves, refNode, opt);
1888
+ if (bool) {
1889
+ matched.add(refNode);
1890
+ }
1891
+ }
1892
+ refNode = walker.nextSibling();
1893
+ }
1894
+ break;
1895
+ }
1896
+ case '>': {
1897
+ const refNode = node.parentNode;
1898
+ if (refNode) {
1899
+ const bool = this._matchLeaves(leaves, refNode, opt);
1900
+ if (bool) {
1901
+ matched.add(refNode);
1902
+ }
1903
+ }
1904
+ break;
1905
+ }
1906
+ case ' ':
1907
+ default: {
1908
+ const arr = [];
1909
+ let refNode = node.parentNode;
1910
+ while (refNode) {
1911
+ const bool = this._matchLeaves(leaves, refNode, opt);
1912
+ if (bool) {
1913
+ arr.push(refNode);
1914
+ }
1915
+ refNode = refNode.parentNode;
1916
+ }
1917
+ if (arr.length) {
1918
+ matched = new Set(arr.reverse());
1919
+ }
1920
+ }
1921
+ }
1922
+ }
1923
+ return matched;
1924
+ }
1925
+
1926
+ /**
1927
+ * find matched node from finder
1928
+ * @private
1929
+ * @param {Array.<object>} leaves - AST leaves
1930
+ * @param {object} [opt] - options
1931
+ * @param {object} [opt.node] - node to start from
1932
+ * @returns {?object} - matched node
1933
+ */
1934
+ _findNode(leaves, opt = {}) {
1935
+ const { node } = opt;
1936
+ let matchedNode;
1937
+ let refNode = this._traverse(node, this.#finder);
1938
+ if (refNode) {
1939
+ if (refNode.nodeType !== ELEMENT_NODE) {
1940
+ refNode = this.#finder.nextNode();
1941
+ } else if (refNode === node) {
1942
+ if (refNode !== this.#root) {
1943
+ refNode = this.#finder.nextNode();
1944
+ }
1945
+ }
1946
+ while (refNode) {
1947
+ let bool;
1948
+ if (this.#node.nodeType === ELEMENT_NODE) {
1949
+ if (refNode === this.#node) {
1950
+ bool = true;
1951
+ } else {
1952
+ bool = this.#node.contains(refNode);
1953
+ }
1954
+ } else {
1955
+ bool = true;
1956
+ }
1957
+ if (bool) {
1958
+ const matched = this._matchLeaves(leaves, refNode, {
1959
+ warn: this.#warn
1960
+ });
1961
+ if (matched) {
1962
+ matchedNode = refNode;
1963
+ break;
1964
+ }
1965
+ }
1966
+ refNode = this.#finder.nextNode();
1967
+ }
1968
+ }
1969
+ return matchedNode ?? null;
1970
+ }
1971
+
1972
+ /**
1973
+ * find entry nodes
1974
+ * @private
1975
+ * @param {object} twig - twig
1976
+ * @param {string} targetType - target type
1977
+ * @param {boolean} complex - complex selector
1978
+ * @returns {object} - collection of nodes etc.
1979
+ */
1980
+ _findEntryNodes(twig, targetType, complex) {
1981
+ const { leaves } = twig;
1982
+ const [leaf, ...filterLeaves] = leaves;
1983
+ const { type: leafType } = leaf;
1984
+ const leafName = unescapeSelector(leaf.name);
1985
+ const compound = filterLeaves.length > 0;
1986
+ let nodes = [];
1987
+ let filtered = false;
1988
+ let pending = false;
1989
+ if (targetType === TARGET_SELF) {
1990
+ const bool = this._matchLeaves(leaves, this.#node, {
1991
+ warn: this.#warn
1992
+ });
1993
+ if (bool) {
1994
+ nodes.push(this.#node);
1995
+ filtered = true;
1996
+ }
1997
+ } else if (targetType === TARGET_LINEAL) {
1998
+ let refNode = this.#node;
1999
+ while (refNode) {
2000
+ const bool = this._matchLeaves(leaves, refNode, {
2001
+ warn: this.#warn
2002
+ });
2003
+ if (bool) {
2004
+ nodes.push(refNode);
2005
+ filtered = true;
2006
+ if (!complex) {
2007
+ break;
2008
+ }
2009
+ }
2010
+ refNode = refNode.parentNode;
2011
+ }
2012
+ } else {
2013
+ switch (leafType) {
2014
+ case SELECTOR_PSEUDO_ELEMENT: {
2015
+ matchPseudoElementSelector(leafName, {
2016
+ warn: this.#warn
2017
+ });
2018
+ break;
2019
+ }
2020
+ case SELECTOR_ID: {
2021
+ if (targetType === TARGET_FIRST &&
2022
+ this.#root.nodeType !== ELEMENT_NODE) {
2023
+ const node = this.#root.getElementById(leafName);
2024
+ if (node) {
2025
+ if (compound) {
2026
+ const bool = this._matchLeaves(filterLeaves, node, {
2027
+ warn: this.#warn
2028
+ });
2029
+ if (bool) {
2030
+ nodes.push(node);
2031
+ }
2032
+ } else {
2033
+ nodes.push(node);
2034
+ }
2035
+ filtered = true;
2036
+ }
2037
+ } else {
2038
+ pending = true;
2039
+ }
2040
+ break;
2041
+ }
2042
+ case SELECTOR_CLASS: {
2043
+ if (targetType === TARGET_FIRST) {
2044
+ const node = this._findNode(leaves, {
2045
+ node: this.#node
2046
+ });
2047
+ if (node) {
2048
+ nodes.push(node);
2049
+ filtered = true;
2050
+ }
2051
+ } else if (this.#root.nodeType === DOCUMENT_NODE) {
2052
+ const items = this.#root.getElementsByClassName(leafName);
2053
+ const l = items.length;
2054
+ if (l) {
2055
+ if (this.#node.nodeType === ELEMENT_NODE) {
2056
+ for (let i = 0; i < l; i++) {
2057
+ const node = items[i];
2058
+ if (node === this.#node || isInclusive(node, this.#node)) {
2059
+ if (compound) {
2060
+ const bool = this._matchLeaves(filterLeaves, node, {
2061
+ warn: this.#warn
2062
+ });
2063
+ if (bool) {
2064
+ nodes.push(node);
2065
+ filtered = true;
2066
+ }
2067
+ } else {
2068
+ nodes.push(node);
2069
+ filtered = true;
2070
+ }
2071
+ }
2072
+ }
2073
+ } else {
2074
+ nodes = [].slice.call(items);
2075
+ if (!compound) {
2076
+ filtered = true;
2077
+ }
2078
+ }
2079
+ }
2080
+ } else {
2081
+ pending = true;
2082
+ }
2083
+ break;
2084
+ }
2085
+ case SELECTOR_TYPE: {
2086
+ if (targetType === TARGET_FIRST) {
2087
+ const node = this._findNode(leaves, {
2088
+ node: this.#node
2089
+ });
2090
+ if (node) {
2091
+ nodes.push(node);
2092
+ filtered = true;
2093
+ }
2094
+ } else if (this.#document.contentType === 'text/html' &&
2095
+ this.#root.nodeType === DOCUMENT_NODE &&
2096
+ !/[*|]/.test(leafName)) {
2097
+ const items = this.#root.getElementsByTagName(leafName);
2098
+ const l = items.length;
2099
+ if (l) {
2100
+ if (this.#node.nodeType === ELEMENT_NODE) {
2101
+ for (let i = 0; i < l; i++) {
2102
+ const node = items[i];
2103
+ if (node === this.#node || isInclusive(node, this.#node)) {
2104
+ if (compound) {
2105
+ const bool = this._matchLeaves(filterLeaves, node, {
2106
+ warn: this.#warn
2107
+ });
2108
+ if (bool) {
2109
+ nodes.push(node);
2110
+ filtered = true;
2111
+ }
2112
+ } else {
2113
+ nodes.push(node);
2114
+ filtered = true;
2115
+ }
2116
+ }
2117
+ }
2118
+ } else {
2119
+ nodes = [].slice.call(items);
2120
+ if (!compound) {
2121
+ filtered = true;
2122
+ }
2123
+ }
2124
+ }
2125
+ } else {
2126
+ pending = true;
2127
+ }
2128
+ break;
2129
+ }
2130
+ default: {
2131
+ if (REG_SHADOW_HOST.test(leafName)) {
2132
+ if (this.#shadow &&
2133
+ this.#node.nodeType === DOCUMENT_FRAGMENT_NODE) {
2134
+ const node = this._matchShadowHostPseudoClass(leaf, this.#node);
2135
+ if (node) {
2136
+ nodes.push(node);
2137
+ }
2138
+ }
2139
+ } else if (targetType === TARGET_FIRST) {
2140
+ const node = this._findNode(leaves, {
2141
+ node: this.#node
2142
+ });
2143
+ if (node) {
2144
+ nodes.push(node);
2145
+ filtered = true;
2146
+ }
2147
+ } else {
2148
+ pending = true;
2149
+ }
2150
+ }
2151
+ }
2152
+ }
2153
+ return {
2154
+ compound,
2155
+ filtered,
2156
+ nodes,
2157
+ pending
2158
+ };
2159
+ }
2160
+
2161
+ /**
2162
+ * get entry twig
2163
+ * @private
2164
+ * @param {Array.<object>} branch - AST branch
2165
+ * @param {string} targetType - target type
2166
+ * @returns {object} - direction and twig
2167
+ */
2168
+ _getEntryTwig(branch, targetType) {
2169
+ const branchLen = branch.length;
2170
+ const complex = branchLen > 1;
2171
+ const firstTwig = branch[0];
2172
+ let dir;
2173
+ let twig;
2174
+ if (complex) {
2175
+ const {
2176
+ combo: firstCombo,
2177
+ leaves: [{
2178
+ name: firstName,
2179
+ type: firstType
2180
+ }]
2181
+ } = firstTwig;
2182
+ const lastTwig = branch[branchLen - 1];
2183
+ const {
2184
+ leaves: [{
2185
+ name: lastName,
2186
+ type: lastType
2187
+ }]
2188
+ } = lastTwig;
2189
+ if (lastType === SELECTOR_PSEUDO_ELEMENT || lastType === SELECTOR_ID) {
2190
+ dir = DIR_PREV;
2191
+ twig = lastTwig;
2192
+ } else if (firstType === SELECTOR_PSEUDO_ELEMENT ||
2193
+ firstType === SELECTOR_ID) {
2194
+ dir = DIR_NEXT;
2195
+ twig = firstTwig;
2196
+ } else if (targetType === TARGET_ALL) {
2197
+ if (firstName === '*' && firstType === SELECTOR_TYPE) {
2198
+ dir = DIR_PREV;
2199
+ twig = lastTwig;
2200
+ } else if (lastName === '*' && lastType === SELECTOR_TYPE) {
2201
+ dir = DIR_NEXT;
2202
+ twig = firstTwig;
2203
+ } else if (branchLen === 2) {
2204
+ const { name: comboName } = firstCombo;
2205
+ if (/^[+~]$/.test(comboName)) {
2206
+ dir = DIR_PREV;
2207
+ twig = lastTwig;
2208
+ } else {
2209
+ dir = DIR_NEXT;
2210
+ twig = firstTwig;
2211
+ }
2212
+ } else {
2213
+ dir = DIR_NEXT;
2214
+ twig = firstTwig;
2215
+ }
2216
+ } else if (lastName === '*' && lastType === SELECTOR_TYPE) {
2217
+ dir = DIR_NEXT;
2218
+ twig = firstTwig;
2219
+ } else if (firstName === '*' && firstType === SELECTOR_TYPE) {
2220
+ dir = DIR_PREV;
2221
+ twig = lastTwig;
2222
+ } else {
2223
+ let bool;
2224
+ let sibling;
2225
+ for (const { combo, leaves: [leaf] } of branch) {
2226
+ const { type: leafType } = leaf;
2227
+ const leafName = unescapeSelector(leaf.name);
2228
+ if (leafType === SELECTOR_PSEUDO_CLASS && leafName === 'dir') {
2229
+ bool = false;
2230
+ break;
2231
+ }
2232
+ if (combo && !sibling) {
2233
+ const { name: comboName } = combo;
2234
+ if (/^[+~]$/.test(comboName)) {
2235
+ bool = true;
2236
+ sibling = true;
2237
+ }
2238
+ }
2239
+ }
2240
+ if (bool) {
2241
+ dir = DIR_NEXT;
2242
+ twig = firstTwig;
2243
+ } else {
2244
+ dir = DIR_PREV;
2245
+ twig = lastTwig;
2246
+ }
2247
+ }
2248
+ } else {
2249
+ dir = DIR_PREV;
2250
+ twig = firstTwig;
2251
+ }
2252
+ return {
2253
+ complex,
2254
+ dir,
2255
+ twig
2256
+ };
2257
+ }
2258
+
2259
+ /**
2260
+ * collect nodes
2261
+ * @private
2262
+ * @param {string} targetType - target type
2263
+ * @returns {Array.<Array.<object|undefined>>} - #ast and #nodes
2264
+ */
2265
+ _collectNodes(targetType) {
2266
+ const ast = this.#ast.values();
2267
+ if (targetType === TARGET_ALL || targetType === TARGET_FIRST) {
2268
+ const pendingItems = new Set();
2269
+ let i = 0;
2270
+ for (const { branch } of ast) {
2271
+ const { complex, dir, twig } = this._getEntryTwig(branch, targetType);
2272
+ const {
2273
+ compound, filtered, nodes, pending
2274
+ } = this._findEntryNodes(twig, targetType, complex);
2275
+ if (nodes.length) {
2276
+ this.#ast[i].find = true;
2277
+ this.#nodes[i] = nodes;
2278
+ } else if (pending) {
2279
+ pendingItems.add(new Map([
2280
+ ['index', i],
2281
+ ['twig', twig]
2282
+ ]));
2283
+ }
2284
+ this.#ast[i].dir = dir;
2285
+ this.#ast[i].filtered = filtered || !compound;
2286
+ i++;
2287
+ }
2288
+ if (pendingItems.size) {
2289
+ let node;
2290
+ let walker;
2291
+ if (this.#node !== this.#root && this.#node.nodeType === ELEMENT_NODE) {
2292
+ node = this.#node;
2293
+ walker = this.#finder;
2294
+ } else {
2295
+ node = this.#root;
2296
+ walker = this.#tree;
2297
+ }
2298
+ let nextNode = this._traverse(node, walker);
2299
+ while (nextNode) {
2300
+ let bool = false;
2301
+ if (this.#node.nodeType === ELEMENT_NODE) {
2302
+ if (nextNode === this.#node) {
2303
+ bool = true;
2304
+ } else {
2305
+ bool = this.#node.contains(nextNode);
2306
+ }
2307
+ } else {
2308
+ bool = true;
2309
+ }
2310
+ if (bool) {
2311
+ for (const pendingItem of pendingItems) {
2312
+ const { leaves } = pendingItem.get('twig');
2313
+ const matched = this._matchLeaves(leaves, nextNode, {
2314
+ warn: this.#warn
2315
+ });
2316
+ if (matched) {
2317
+ const index = pendingItem.get('index');
2318
+ this.#ast[index].filtered = true;
2319
+ this.#ast[index].find = true;
2320
+ this.#nodes[index].push(nextNode);
2321
+ }
2322
+ }
2323
+ }
2324
+ nextNode = walker.nextNode();
2325
+ }
2326
+ }
2327
+ } else {
2328
+ let i = 0;
2329
+ for (const { branch } of ast) {
2330
+ const twig = branch[branch.length - 1];
2331
+ const complex = branch.length > 1;
2332
+ const {
2333
+ compound, filtered, nodes
2334
+ } = this._findEntryNodes(twig, targetType, complex);
2335
+ if (nodes.length) {
2336
+ this.#ast[i].find = true;
2337
+ this.#nodes[i] = nodes;
2338
+ }
2339
+ this.#ast[i].dir = DIR_PREV;
2340
+ this.#ast[i].filtered = filtered || !compound;
2341
+ i++;
2342
+ }
2343
+ }
2344
+ return [
2345
+ this.#ast,
2346
+ this.#nodes
2347
+ ];
2348
+ }
2349
+
2350
+ /**
2351
+ * match nodes
2352
+ * @private
2353
+ * @param {string} targetType - target type
2354
+ * @returns {Set.<object>} - collection of matched nodes
2355
+ */
2356
+ _matchNodes(targetType) {
2357
+ const [...branches] = this.#ast;
2358
+ const l = branches.length;
2359
+ let nodes = new Set();
2360
+ for (let i = 0; i < l; i++) {
2361
+ const { branch, dir, filtered, find } = branches[i];
2362
+ const branchLen = branch.length;
2363
+ if (branchLen && find) {
2364
+ const entryNodes = this.#nodes[i];
2365
+ const entryNodesLen = entryNodes.length;
2366
+ const lastIndex = branchLen - 1;
2367
+ if (lastIndex === 0) {
2368
+ const { leaves: [, ...filterLeaves] } = branch[0];
2369
+ if ((targetType === TARGET_ALL || targetType === TARGET_FIRST) &&
2370
+ this.#node.nodeType === ELEMENT_NODE) {
2371
+ for (let j = 0; j < entryNodesLen; j++) {
2372
+ const node = entryNodes[j];
2373
+ const bool = filtered || this._matchLeaves(filterLeaves, node, {
2374
+ warn: this.#warn
2375
+ });
2376
+ if (bool && node !== this.#node && this.#node.contains(node)) {
2377
+ nodes.add(node);
2378
+ if (targetType !== TARGET_ALL) {
2379
+ break;
2380
+ }
2381
+ }
2382
+ }
2383
+ } else if (filterLeaves.length) {
2384
+ for (let j = 0; j < entryNodesLen; j++) {
2385
+ const node = entryNodes[j];
2386
+ const bool = filtered || this._matchLeaves(filterLeaves, node, {
2387
+ warn: this.#warn
2388
+ });
2389
+ if (bool) {
2390
+ nodes.add(node);
2391
+ if (targetType !== TARGET_ALL) {
2392
+ break;
2393
+ }
2394
+ }
2395
+ }
2396
+ } else if (targetType === TARGET_ALL) {
2397
+ if (nodes.size) {
2398
+ const n = [...nodes];
2399
+ nodes = new Set([...n, ...entryNodes]);
2400
+ this.#sort = true;
2401
+ } else {
2402
+ nodes = new Set([...entryNodes]);
2403
+ }
2404
+ } else {
2405
+ const [node] = [...entryNodes];
2406
+ nodes.add(node);
2407
+ }
2408
+ } else if (dir === DIR_NEXT) {
2409
+ let { combo, leaves: entryLeaves } = branch[0];
2410
+ const [, ...filterLeaves] = entryLeaves;
2411
+ let matched;
2412
+ for (let j = 0; j < entryNodesLen; j++) {
2413
+ const node = entryNodes[j];
2414
+ const bool = filtered || this._matchLeaves(filterLeaves, node, {
2415
+ warn: this.#warn
2416
+ });
2417
+ if (bool) {
2418
+ let nextNodes = new Set([node]);
2419
+ for (let j = 1; j < branchLen; j++) {
2420
+ const { combo: nextCombo, leaves } = branch[j];
2421
+ const arr = [];
2422
+ for (const nextNode of nextNodes) {
2423
+ const twig = {
2424
+ combo,
2425
+ leaves
2426
+ };
2427
+ const m = this._matchCombinator(twig, nextNode, {
2428
+ dir,
2429
+ warn: this.#warn
2430
+ });
2431
+ if (m.size) {
2432
+ arr.push(...m);
2433
+ }
2434
+ }
2435
+ if (arr.length) {
2436
+ if (j === lastIndex) {
2437
+ if (targetType === TARGET_ALL) {
2438
+ if (nodes.size) {
2439
+ const n = [...nodes];
2440
+ nodes = new Set([...n, ...arr]);
2441
+ } else {
2442
+ nodes = new Set([...arr]);
2443
+ }
2444
+ this.#sort = true;
2445
+ } else {
2446
+ const [node] = sortNodes(arr);
2447
+ nodes.add(node);
2448
+ }
2449
+ matched = true;
2450
+ } else {
2451
+ combo = nextCombo;
2452
+ nextNodes = new Set(arr);
2453
+ matched = false;
2454
+ }
2455
+ } else {
2456
+ matched = false;
2457
+ break;
2458
+ }
2459
+ }
2460
+ } else {
2461
+ matched = false;
2462
+ }
2463
+ if (matched && targetType !== TARGET_ALL) {
2464
+ break;
2465
+ }
2466
+ }
2467
+ if (!matched && targetType === TARGET_FIRST) {
2468
+ const [entryNode] = [...entryNodes];
2469
+ let refNode = this._findNode(entryLeaves, {
2470
+ node: entryNode
2471
+ });
2472
+ while (refNode) {
2473
+ let nextNodes = new Set([refNode]);
2474
+ for (let j = 1; j < branchLen; j++) {
2475
+ const { combo: nextCombo, leaves } = branch[j];
2476
+ const arr = [];
2477
+ for (const nextNode of nextNodes) {
2478
+ const twig = {
2479
+ combo,
2480
+ leaves
2481
+ };
2482
+ const m = this._matchCombinator(twig, nextNode, {
2483
+ dir,
2484
+ warn: this.#warn
2485
+ });
2486
+ if (m.size) {
2487
+ arr.push(...m);
2488
+ }
2489
+ }
2490
+ if (arr.length) {
2491
+ if (j === lastIndex) {
2492
+ const [node] = sortNodes(arr);
2493
+ nodes.add(node);
2494
+ matched = true;
2495
+ } else {
2496
+ combo = nextCombo;
2497
+ nextNodes = new Set(arr);
2498
+ matched = false;
2499
+ }
2500
+ } else {
2501
+ matched = false;
2502
+ break;
2503
+ }
2504
+ }
2505
+ if (matched) {
2506
+ break;
2507
+ }
2508
+ refNode = this._findNode(entryLeaves, {
2509
+ node: refNode
2510
+ });
2511
+ nextNodes = new Set([refNode]);
2512
+ }
2513
+ }
2514
+ } else {
2515
+ const { leaves: entryLeaves } = branch[lastIndex];
2516
+ const [, ...filterLeaves] = entryLeaves;
2517
+ let matched;
2518
+ for (let j = 0; j < entryNodesLen; j++) {
2519
+ const node = entryNodes[j];
2520
+ const bool = filtered || this._matchLeaves(filterLeaves, node, {
2521
+ warn: this.#warn
2522
+ });
2523
+ if (bool) {
2524
+ let nextNodes = new Set([node]);
2525
+ for (let j = lastIndex - 1; j >= 0; j--) {
2526
+ const twig = branch[j];
2527
+ const arr = [];
2528
+ for (const nextNode of nextNodes) {
2529
+ const m = this._matchCombinator(twig, nextNode, {
2530
+ dir,
2531
+ warn: this.#warn
2532
+ });
2533
+ if (m.size) {
2534
+ arr.push(...m);
2535
+ }
2536
+ }
2537
+ if (arr.length) {
2538
+ if (j === 0) {
2539
+ nodes.add(node);
2540
+ matched = true;
2541
+ if (targetType === TARGET_ALL &&
2542
+ branchLen > 1 && nodes.size > 1) {
2543
+ this.#sort = true;
2544
+ }
2545
+ } else {
2546
+ nextNodes = new Set(arr);
2547
+ matched = false;
2548
+ }
2549
+ } else {
2550
+ matched = false;
2551
+ break;
2552
+ }
2553
+ }
2554
+ }
2555
+ if (matched && targetType !== TARGET_ALL) {
2556
+ break;
2557
+ }
2558
+ }
2559
+ if (!matched && targetType === TARGET_FIRST) {
2560
+ const [entryNode] = [...entryNodes];
2561
+ let refNode = this._findNode(entryLeaves, {
2562
+ node: entryNode
2563
+ });
2564
+ while (refNode) {
2565
+ let nextNodes = new Set([refNode]);
2566
+ for (let j = lastIndex - 1; j >= 0; j--) {
2567
+ const twig = branch[j];
2568
+ const arr = [];
2569
+ for (const nextNode of nextNodes) {
2570
+ const m = this._matchCombinator(twig, nextNode, {
2571
+ dir,
2572
+ warn: this.#warn
2573
+ });
2574
+ if (m.size) {
2575
+ arr.push(...m);
2576
+ }
2577
+ }
2578
+ if (arr.length) {
2579
+ if (j === 0) {
2580
+ nodes.add(refNode);
2581
+ matched = true;
2582
+ } else {
2583
+ nextNodes = new Set(arr);
2584
+ matched = false;
2585
+ }
2586
+ } else {
2587
+ matched = false;
2588
+ break;
2589
+ }
2590
+ }
2591
+ if (matched) {
2592
+ break;
2593
+ }
2594
+ refNode = this._findNode(entryLeaves, {
2595
+ node: refNode
2596
+ });
2597
+ nextNodes = new Set([refNode]);
2598
+ }
2599
+ }
2600
+ }
2601
+ }
2602
+ }
2603
+ return nodes;
2604
+ }
2605
+
2606
+ /**
2607
+ * find matched nodes
2608
+ * @private
2609
+ * @param {string} targetType - target type
2610
+ * @param {object} node - Document, DocumentFragment, Element node
2611
+ * @param {string} selector - CSS selector
2612
+ * @param {object} opt - options
2613
+ * @returns {Set.<object>} - collection of matched nodes
2614
+ */
2615
+ _find(targetType, node, selector, opt) {
2616
+ this._setup(node, selector, opt);
2617
+ if (targetType === TARGET_ALL || targetType === TARGET_FIRST) {
2618
+ this.#tree = this.#document.createTreeWalker(this.#root, WALKER_FILTER);
2619
+ this.#finder = this.#document.createTreeWalker(this.#node, WALKER_FILTER);
2620
+ this.#sort = false;
2621
+ // TARGET_SELF, TARGET_LINEAL
2622
+ } else if (node.nodeType !== ELEMENT_NODE) {
2623
+ const msg = `Unexpected node ${node.nodeName}`;
2624
+ throw new TypeError(msg);
2625
+ }
2626
+ this._collectNodes(targetType);
2627
+ const nodes = this._matchNodes(targetType);
2628
+ return nodes;
2629
+ }
2630
+
2631
+ /**
2632
+ * matches
2633
+ * @param {object} node - Element node
2634
+ * @param {string} selector - CSS selector
2635
+ * @param {object} opt - options
2636
+ * @returns {boolean} - `true` if matched `false` otherwise
2637
+ */
2638
+ matches(node, selector, opt) {
2639
+ let res;
2640
+ try {
2641
+ const nodes = this._find(TARGET_SELF, node, selector, opt);
2642
+ if (nodes.size) {
2643
+ res = nodes.has(this.#node);
2644
+ }
2645
+ } catch (e) {
2646
+ this._onError(e);
2647
+ }
2648
+ return !!res;
2649
+ }
2650
+
2651
+ /**
2652
+ * closest
2653
+ * @param {object} node - Element node
2654
+ * @param {string} selector - CSS selector
2655
+ * @param {object} opt - options
2656
+ * @returns {?object} - matched node
2657
+ */
2658
+ closest(node, selector, opt) {
2659
+ let res;
2660
+ try {
2661
+ const nodes = this._find(TARGET_LINEAL, node, selector, opt);
2662
+ let refNode = this.#node;
2663
+ while (refNode) {
2664
+ if (nodes.has(refNode)) {
2665
+ res = refNode;
2666
+ break;
2667
+ }
2668
+ refNode = refNode.parentNode;
2669
+ }
2670
+ } catch (e) {
2671
+ this._onError(e);
2672
+ }
2673
+ return res ?? null;
2674
+ }
2675
+
2676
+ /**
2677
+ * query selector
2678
+ * @param {object} node - Document, DocumentFragment, Element node
2679
+ * @param {string} selector - CSS selector
2680
+ * @param {object} opt - options
2681
+ * @returns {?object} - matched node
2682
+ */
2683
+ querySelector(node, selector, opt) {
2684
+ let res;
2685
+ try {
2686
+ const nodes = this._find(TARGET_FIRST, node, selector, opt);
2687
+ nodes.delete(this.#node);
2688
+ if (nodes.size) {
2689
+ [res] = sortNodes(nodes);
2690
+ }
2691
+ } catch (e) {
2692
+ this._onError(e);
2693
+ }
2694
+ return res ?? null;
2695
+ }
2696
+
2697
+ /**
2698
+ * query selector all
2699
+ * NOTE: returns Array, not NodeList
2700
+ * @param {object} node - Document, DocumentFragment, Element node
2701
+ * @param {string} selector - CSS selector
2702
+ * @param {object} opt - options
2703
+ * @returns {Array.<object|undefined>} - collection of matched nodes
2704
+ */
2705
+ querySelectorAll(node, selector, opt) {
2706
+ let res;
2707
+ try {
2708
+ const nodes = this._find(TARGET_ALL, node, selector, opt);
2709
+ nodes.delete(this.#node);
2710
+ if (nodes.size) {
2711
+ if (this.#sort) {
2712
+ res = sortNodes(nodes);
2713
+ } else {
2714
+ res = [...nodes];
2715
+ }
2716
+ }
2717
+ } catch (e) {
2718
+ this._onError(e);
2719
+ }
2720
+ return res ?? [];
2721
+ }
2722
+ };