@asamuzakjp/dom-selector 8.1.4 → 8.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/js/finder.js CHANGED
@@ -3,15 +3,8 @@
3
3
  */
4
4
 
5
5
  /* import */
6
- import {
7
- matchAttributeSelector,
8
- matchDirectionPseudoClass,
9
- matchDisabledPseudoClass,
10
- matchLanguagePseudoClass,
11
- matchPseudoElementSelector,
12
- matchReadOnlyPseudoClass,
13
- matchTypeSelector
14
- } from './matcher.js';
6
+ import { Evaluator } from './evaluator.js';
7
+ import { matchPseudoElementSelector } from './matcher.js';
15
8
  import {
16
9
  generateCSS,
17
10
  parseSelector,
@@ -20,2285 +13,195 @@ import {
20
13
  walkAST
21
14
  } from './parser.js';
22
15
  import { createHasValidator, isInvalidCombinator } from './selector.js';
23
- import {
24
- filterNodesByAnB,
25
- findBestSeed,
26
- generateException,
27
- isCustomElement,
28
- isFocusVisible,
29
- isFocusableArea,
30
- isVisible,
31
- populateHasAllowlist,
32
- resolveContent,
33
- sortNodes,
34
- traverseNode
35
- } from './utility.js';
16
+ import { generateException, sortNodes, traverseNode } from './utility.js';
36
17
 
37
18
  /* constants */
38
19
  import {
39
- ATTR_SELECTOR,
40
20
  CLASS_SELECTOR,
41
21
  COMBINATOR,
42
22
  DOCUMENT_FRAGMENT_NODE,
43
23
  ELEMENT_NODE,
44
- FORM_PARTS,
45
- HEX,
46
24
  ID_SELECTOR,
47
- INPUT_CHECK,
48
- INPUT_DATE,
49
- INPUT_EDIT,
50
- INPUT_TEXT,
51
- KEYS_LOGICAL,
52
- NOT_SUPPORTED_ERR,
53
- PS_CLASS_SELECTOR,
54
25
  PS_ELEMENT_SELECTOR,
55
- SHOW_ALL,
56
- SHOW_CONTAINER,
57
26
  SYNTAX_ERR,
58
27
  TARGET_ALL,
59
28
  TARGET_FIRST,
60
29
  TARGET_LINEAL,
61
30
  TARGET_SELF,
62
- TEXT_NODE,
63
31
  TYPE_SELECTOR
64
32
  } from './constant.js';
65
- const ANB_FIRST = { a: 0, b: 1 };
66
- const ANB_LAST = { a: 0, b: 1, reverse: true };
67
33
  const DIR_NEXT = 'next';
68
34
  const DIR_PREV = 'prev';
69
- const KEYS_FORM = new Set([...FORM_PARTS, 'fieldset', 'form']);
70
- const KEYS_FORM_PS_VALID = new Set([...FORM_PARTS, 'form']);
71
- const KEYS_INPUT_CHECK = new Set(INPUT_CHECK);
72
- const KEYS_INPUT_PLACEHOLDER = new Set([...INPUT_TEXT, 'number']);
73
- const KEYS_INPUT_RANGE = new Set([...INPUT_DATE, 'number', 'range']);
74
- const KEYS_INPUT_REQUIRED = new Set([...INPUT_CHECK, ...INPUT_EDIT, 'file']);
75
- const KEYS_INPUT_RESET = new Set(['button', 'reset']);
76
- const KEYS_INPUT_SUBMIT = new Set(['image', 'submit']);
77
- const KEYS_MODIFIER = new Set([
78
- 'Alt',
79
- 'AltGraph',
80
- 'CapsLock',
81
- 'Control',
82
- 'Fn',
83
- 'FnLock',
84
- 'Hyper',
85
- 'Meta',
86
- 'NumLock',
87
- 'ScrollLock',
88
- 'Shift',
89
- 'Super',
90
- 'Symbol',
91
- 'SymbolLock'
92
- ]);
93
- const KEYS_PS_UNCACHE = new Set([
94
- 'any-link',
95
- 'defined',
96
- 'dir',
97
- 'link',
98
- 'scope'
99
- ]);
100
- const KEYS_PS_NTH_OF_TYPE = new Set([
101
- 'first-of-type',
102
- 'last-of-type',
103
- 'only-of-type'
104
- ]);
105
35
 
106
36
  /**
107
37
  * Finder
108
38
  * NOTE: #ast[i] corresponds to #nodes[i]
109
39
  */
110
- export class Finder {
40
+ export class Finder extends Evaluator {
111
41
  /* private fields */
112
- #anbCache;
113
42
  #ast;
114
- #astCache = new WeakMap();
115
- #check;
116
- #descendant;
117
- #document;
118
- #documentCache = new WeakMap();
119
- #documentURL;
120
- #event;
121
- #eventHandlers;
122
- #filterLeavesCache;
123
- #focus;
124
- #focusWithinCache;
125
- #invalidate;
126
- #invalidateResults;
127
- #lastFocusVisible;
128
- #node;
129
43
  #nodeWalker;
130
44
  #nodes;
131
- #noexcept;
132
- #nthChildCache;
133
- #nthChildOfCache;
134
- #nthChildResultCache;
135
- #nthOfTypeCache;
136
- #nthOfTypeResultCache;
137
- #psDefaultCache;
138
- #psDirCache;
139
- #psHasFilterCache;
140
- #psIndeterminateCache;
141
- #psLangCache;
142
- #psValidCache;
143
- #pseudoElement;
144
- #results;
145
- #root;
146
45
  #rootWalker;
147
46
  #scoped;
148
47
  #selector;
149
48
  #selectorAST;
150
- #shadow;
151
- #targetWithinCache;
152
- #verifyShadowHost;
153
- #walkers;
154
- #warn;
155
- #window;
156
-
157
- /**
158
- * constructor
159
- * @param {object} window - The window object.
160
- */
161
- constructor(window) {
162
- this.#window = window;
163
- this.#eventHandlers = new Set([
164
- {
165
- keys: ['focus', 'focusin'],
166
- handler: this._handleFocusEvent
167
- },
168
- {
169
- keys: ['keydown', 'keyup'],
170
- handler: this._handleKeyboardEvent
171
- },
172
- {
173
- keys: ['mouseover', 'mousedown', 'mouseup', 'click', 'mouseout'],
174
- handler: this._handleMouseEvent
175
- }
176
- ]);
177
- this._registerEventListeners();
178
- this.clearResults(true);
179
- }
180
-
181
- /**
182
- * Handles errors.
183
- * @param {Error} e - The error object.
184
- * @param {object} [opt] - Options.
185
- * @param {boolean} [opt.noexcept] - If true, exceptions are not thrown.
186
- * @throws {Error} Throws an error.
187
- * @returns {void}
188
- */
189
- onError = (e, opt = {}) => {
190
- const noexcept = opt.noexcept ?? this.#noexcept;
191
- if (noexcept) {
192
- return;
193
- }
194
- const isDOMException =
195
- e instanceof DOMException || e instanceof this.#window.DOMException;
196
- if (isDOMException) {
197
- if (e.name === NOT_SUPPORTED_ERR) {
198
- if (this.#warn) {
199
- console.warn(e.message);
200
- }
201
- return;
202
- }
203
- throw new this.#window.DOMException(e.message, e.name);
204
- }
205
- if (e.name in this.#window) {
206
- throw new this.#window[e.name](e.message, { cause: e });
207
- }
208
- throw e;
209
- };
210
49
 
211
50
  /**
212
51
  * Sets up the finder.
213
52
  * @param {string} selector - The CSS selector.
214
53
  * @param {object} node - Document, DocumentFragment, or Element.
215
54
  * @param {object} [opt] - Options.
216
- * @param {boolean} [opt.check] - Indicates if running in internal check().
217
- * @param {boolean} [opt.noexcept] - If true, exceptions are not thrown.
218
- * @param {boolean} [opt.warn] - If true, console warnings are enabled.
55
+ * @param {boolean} [opt.check] - True if running in internal check.
56
+ * @param {boolean} [opt.noexcept] - True to suppress exceptions.
57
+ * @param {boolean} [opt.warn] - True to enable console warnings.
219
58
  * @returns {object} The finder instance.
220
- */
221
- setup = (selector, node, opt = {}) => {
222
- const { check, noexcept, warn } = opt;
223
- this.#check = !!check;
224
- this.#noexcept = !!noexcept;
225
- this.#warn = !!warn;
226
- [this.#document, this.#root, this.#shadow] = resolveContent(node);
227
- this.#documentURL = null;
228
- this.#node = node;
229
- this.#scoped =
230
- this.#node !== this.#root && this.#node.nodeType === ELEMENT_NODE;
231
- this.#selector = selector;
232
- this.#pseudoElement = [];
233
- this.#walkers = null;
234
- this.#nodeWalker = null;
235
- this.#rootWalker = null;
236
- this.#verifyShadowHost = null;
237
- this.clearResults();
238
- return this;
239
- };
240
-
241
- /**
242
- * Clear cached results.
243
- * @param {boolean} all - Clear all results.
244
- * @returns {void}
245
- */
246
- clearResults = (all = false) => {
247
- this.#anbCache = null;
248
- this.#focusWithinCache = null;
249
- this.#invalidateResults = null;
250
- this.#nthChildCache = null;
251
- this.#nthChildOfCache = null;
252
- this.#nthChildResultCache = null;
253
- this.#nthOfTypeCache = null;
254
- this.#nthOfTypeResultCache = null;
255
- this.#psDefaultCache = null;
256
- this.#psDirCache = null;
257
- this.#psHasFilterCache = null;
258
- this.#psIndeterminateCache = null;
259
- this.#psLangCache = null;
260
- this.#psValidCache = null;
261
- this.#targetWithinCache = null;
262
- if (all) {
263
- this.#filterLeavesCache = null;
264
- this.#results = new WeakMap();
265
- }
266
- };
267
-
268
- /**
269
- * Handles focus events.
270
- * @private
271
- * @param {Event} evt - The event object.
272
- * @returns {void}
273
- */
274
- _handleFocusEvent = evt => {
275
- this.#focus = evt;
276
- };
277
-
278
- /**
279
- * Handles keyboard events.
280
- * @private
281
- * @param {Event} evt - The event object.
282
- * @returns {void}
283
- */
284
- _handleKeyboardEvent = evt => {
285
- const { key } = evt;
286
- if (!KEYS_MODIFIER.has(key)) {
287
- this.#event = evt;
288
- }
289
- };
290
-
291
- /**
292
- * Handles mouse events.
293
- * @private
294
- * @param {Event} evt - The event object.
295
- * @returns {void}
296
- */
297
- _handleMouseEvent = evt => {
298
- this.#event = evt;
299
- };
300
-
301
- /**
302
- * Registers event listeners.
303
- * @private
304
- * @returns {Array.<void>} An array of return values from addEventListener.
305
- */
306
- _registerEventListeners = () => {
307
- const func = [];
308
- for (const eventHandler of this.#eventHandlers) {
309
- const { keys, handler } = eventHandler;
310
- const l = keys.length;
311
- for (let i = 0; i < l; i++) {
312
- const key = keys[i];
313
- func.push(
314
- this.#window.addEventListener(key, handler, {
315
- capture: true,
316
- passive: true
317
- })
318
- );
319
- }
320
- }
321
- return func;
322
- };
323
-
324
- /**
325
- * Processes selector branches into the internal AST structure.
326
- * @private
327
- * @param {Array.<Array.<object>>} branches - The branches from walkAST.
328
- * @param {string} selector - The original selector for error reporting.
329
- * @returns {{ast: Array, descendant: boolean}} An object with the AST, descendant flag.
330
- */
331
- _processSelectorBranches = (branches, selector) => {
332
- let descendant = false;
333
- const ast = [];
334
- for (const items of branches) {
335
- const branch = [];
336
- let prevType = null;
337
- const itemsLen = items.length;
338
- if (itemsLen) {
339
- const leaves = new Set();
340
- for (let j = 0; j < itemsLen; j++) {
341
- const item = items[j];
342
- const isLast = j === itemsLen - 1;
343
- if (isInvalidCombinator(item.type, prevType, isLast)) {
344
- const msg = `Invalid selector ${selector}`;
345
- this.onError(generateException(msg, SYNTAX_ERR, this.#window));
346
- return { ast: [], descendant: false, invalidate: false };
347
- }
348
- if (item.type === COMBINATOR) {
349
- if (item.name === ' ' || item.name === '>') {
350
- descendant = true;
351
- }
352
- branch.push({ combo: item, leaves: sortAST(leaves) });
353
- leaves.clear();
354
- } else {
355
- if (item.name && typeof item.name === 'string') {
356
- const unescapedName = unescapeSelector(item.name);
357
- if (unescapedName !== item.name) {
358
- item.name = unescapedName;
359
- }
360
- if (/[|:]/.test(unescapedName)) {
361
- item.namespace = true;
362
- }
363
- }
364
- leaves.add(item);
365
- }
366
- prevType = item.type;
367
- if (isLast) {
368
- branch.push({ combo: null, leaves: sortAST(leaves) });
369
- leaves.clear();
370
- }
371
- }
372
- }
373
- ast.push({ branch, dir: null, filtered: false, find: false });
374
- }
375
- return { ast, descendant };
376
- };
377
-
378
- /**
379
- * Corresponds AST and nodes.
380
- * @private
381
- * @param {string} selector - The CSS selector.
382
- * @returns {Array.<Array.<object>>} An array with the AST and nodes.
383
- */
384
- _correspond = selector => {
385
- const nodes = [];
386
- this.#descendant = false;
387
- this.#invalidate = false;
388
- let ast;
389
- if (this.#documentCache.has(this.#document)) {
390
- const cachedItem = this.#documentCache.get(this.#document);
391
- if (cachedItem && cachedItem.has(`${selector}`)) {
392
- const item = cachedItem.get(`${selector}`);
393
- ast = item.ast;
394
- this.#descendant = item.descendant;
395
- this.#invalidate = item.invalidate;
396
- this.#selectorAST = item.selectorAST;
397
- }
398
- }
399
- if (ast) {
400
- const l = ast.length;
401
- for (let i = 0; i < l; i++) {
402
- ast[i].dir = null;
403
- ast[i].filtered = false;
404
- ast[i].find = false;
405
- nodes[i] = [];
406
- }
407
- } else {
408
- this.#selectorAST = parseSelector(selector);
409
- const { branches, info } = walkAST(
410
- this.#selectorAST,
411
- true,
412
- createHasValidator(this.#window)
413
- );
414
- const {
415
- hasHasPseudoFunc,
416
- hasLogicalPseudoFunc,
417
- hasNthChildOfSelector,
418
- hasStatePseudoClass,
419
- hasUnsupportedPseudoClass
420
- } = info;
421
- this.#invalidate =
422
- hasHasPseudoFunc ||
423
- hasStatePseudoClass ||
424
- hasUnsupportedPseudoClass ||
425
- !!(hasLogicalPseudoFunc && hasNthChildOfSelector);
426
- const processed = this._processSelectorBranches(branches, selector);
427
- ast = processed.ast;
428
- this.#descendant = processed.descendant;
429
- let cachedItem;
430
- if (this.#documentCache.has(this.#document)) {
431
- cachedItem = this.#documentCache.get(this.#document);
432
- } else {
433
- cachedItem = new Map();
434
- }
435
- cachedItem.set(`${selector}`, {
436
- ast,
437
- descendant: this.#descendant,
438
- invalidate: this.#invalidate,
439
- selectorAST: this.#selectorAST
440
- });
441
- this.#documentCache.set(this.#document, cachedItem);
442
- // Initialize nodes array for each branch.
443
- for (let i = 0; i < ast.length; i++) {
444
- nodes[i] = [];
445
- }
446
- }
447
- return [ast, nodes];
448
- };
449
-
450
- /**
451
- * Creates a TreeWalker.
452
- * @private
453
- * @param {object} node - The Document, DocumentFragment, or Element node.
454
- * @param {object} [opt] - Options.
455
- * @param {boolean} [opt.force] - Force creation of a new TreeWalker.
456
- * @param {number} [opt.whatToShow] - The NodeFilter whatToShow value.
457
- * @returns {object} The TreeWalker object.
458
- */
459
- _createTreeWalker = (node, opt = {}) => {
460
- const { force = false, whatToShow = SHOW_CONTAINER } = opt;
461
- if (force) {
462
- return this.#document.createTreeWalker(node, whatToShow);
463
- }
464
- if (!this.#walkers) {
465
- this.#walkers = new WeakMap();
466
- }
467
- if (this.#walkers.has(node)) {
468
- return this.#walkers.get(node);
469
- }
470
- const walker = this.#document.createTreeWalker(node, whatToShow);
471
- this.#walkers.set(node, walker);
472
- return walker;
473
- };
474
-
475
- /**
476
- * Gets selector branches from cache or parses them.
477
- * @private
478
- * @param {object} selector - The AST.
479
- * @returns {Array.<Array.<object>>} The selector branches.
480
- */
481
- _getSelectorBranches = selector => {
482
- if (this.#astCache.has(selector)) {
483
- return this.#astCache.get(selector);
484
- }
485
- const { branches } = walkAST(selector);
486
- this.#astCache.set(selector, branches);
487
- return branches;
488
- };
489
-
490
- /**
491
- * Gets the children of a node, optionally filtered by a selector.
492
- * @private
493
- * @param {object} parentNode - The parent element.
494
- * @param {Array.<Array.<object>>} selectorBranches - The selector branches.
495
- * @param {object} opt - Options.
496
- * @returns {Array.<object>} An array of child nodes.
497
- */
498
- _getFilteredChildren = (parentNode, selectorBranches, opt) => {
499
- const children = [];
500
- let childNode = parentNode.firstElementChild;
501
- while (childNode) {
502
- if (selectorBranches) {
503
- let isMatch = false;
504
- const l = selectorBranches.length;
505
- for (let i = 0; i < l; i++) {
506
- const leaves = selectorBranches[i];
507
- if (this._matchLeaves(leaves, childNode, opt)) {
508
- isMatch = true;
509
- break;
510
- }
511
- }
512
- if (isMatch) {
513
- if (this.#node === childNode) {
514
- children.push(childNode);
515
- } else if (isVisible(childNode)) {
516
- children.push(childNode);
517
- }
518
- }
519
- } else {
520
- children.push(childNode);
521
- }
522
- childNode = childNode.nextElementSibling;
523
- }
524
- return children;
525
- };
526
-
527
- /**
528
- * Collects nth-child nodes.
529
- * @private
530
- * @param {object} anb - An+B options.
531
- * @param {number} anb.a - The 'a' value.
532
- * @param {number} anb.b - The 'b' value.
533
- * @param {boolean} [anb.reverse] - If true, reverses the order.
534
- * @param {object} [anb.selector] - The selector for 'of S'.
535
- * @param {object} node - The Element node.
536
- * @param {object} opt - Options.
537
- * @returns {Set.<object>} A collection of matched nodes.
538
- */
539
- _collectNthChild = (anb, node, opt) => {
540
- const { a, b, selector } = anb;
541
- const { parentNode } = node;
542
- if (!parentNode) {
543
- const matchedNode = new Set();
544
- if (node === this.#root && a * 1 + b * 1 === 1) {
545
- if (selector) {
546
- const selectorBranches = this._getSelectorBranches(selector);
547
- const l = selectorBranches.length;
548
- for (let i = 0; i < l; i++) {
549
- const leaves = selectorBranches[i];
550
- if (this._matchLeaves(leaves, node, opt)) {
551
- matchedNode.add(node);
552
- break;
553
- }
554
- }
555
- } else {
556
- matchedNode.add(node);
557
- }
558
- }
559
- return matchedNode;
560
- }
561
- if (!this.#nthChildResultCache) {
562
- this.#nthChildResultCache = new WeakMap();
563
- }
564
- let parentResultCache = this.#nthChildResultCache.get(parentNode);
565
- if (!parentResultCache) {
566
- parentResultCache = new WeakMap();
567
- this.#nthChildResultCache.set(parentNode, parentResultCache);
568
- }
569
- const cachedSet = parentResultCache.get(anb);
570
- if (cachedSet) {
571
- return cachedSet;
572
- }
573
- let siblings;
574
- if (selector) {
575
- if (!this.#nthChildOfCache) {
576
- this.#nthChildOfCache = new WeakMap();
577
- }
578
- let parentOfCacheMap = this.#nthChildOfCache.get(parentNode);
579
- if (!parentOfCacheMap) {
580
- parentOfCacheMap = new Map();
581
- this.#nthChildOfCache.set(parentNode, parentOfCacheMap);
582
- }
583
- siblings = parentOfCacheMap.get(selector);
584
- if (!siblings) {
585
- const selectorBranches = this._getSelectorBranches(selector);
586
- siblings = this._getFilteredChildren(parentNode, selectorBranches, opt);
587
- parentOfCacheMap.set(selector, siblings);
588
- }
589
- } else {
590
- if (!this.#nthChildCache) {
591
- this.#nthChildCache = new WeakMap();
592
- }
593
- siblings = this.#nthChildCache.get(parentNode);
594
- if (!siblings) {
595
- siblings = this._getFilteredChildren(parentNode, null, opt);
596
- this.#nthChildCache.set(parentNode, siblings);
597
- }
598
- }
599
- const matchedNodes = filterNodesByAnB(siblings, anb);
600
- const resultSet = new Set(matchedNodes);
601
- parentResultCache.set(anb, resultSet);
602
- return resultSet;
603
- };
604
-
605
- /**
606
- * Collects nth-of-type nodes.
607
- * @private
608
- * @param {object} anb - An+B options.
609
- * @param {number} anb.a - The 'a' value.
610
- * @param {number} anb.b - The 'b' value.
611
- * @param {boolean} [anb.reverse] - If true, reverses the order.
612
- * @param {object} node - The Element node.
613
- * @returns {Set.<object>} A collection of matched nodes.
614
- */
615
- _collectNthOfType = (anb, node) => {
616
- const { localName, namespaceURI, parentNode, prefix } = node;
617
- if (!parentNode) {
618
- if (node === this.#root && anb.a * 1 + anb.b * 1 === 1) {
619
- return new Set([node]);
620
- }
621
- return new Set();
622
- }
623
- if (!this.#nthOfTypeResultCache) {
624
- this.#nthOfTypeResultCache = new WeakMap();
625
- }
626
- let parentResultCache = this.#nthOfTypeResultCache.get(parentNode);
627
- if (!parentResultCache) {
628
- parentResultCache = new WeakMap();
629
- this.#nthOfTypeResultCache.set(parentNode, parentResultCache);
630
- }
631
- let typeResultMap = parentResultCache.get(anb);
632
- if (!typeResultMap) {
633
- typeResultMap = new Map();
634
- parentResultCache.set(anb, typeResultMap);
635
- }
636
- const typeKey = `${namespaceURI || ''}|${prefix || ''}|${localName}`;
637
- const cachedSet = typeResultMap.get(typeKey);
638
- if (cachedSet) {
639
- return cachedSet;
640
- }
641
- if (!this.#nthOfTypeCache) {
642
- this.#nthOfTypeCache = new WeakMap();
643
- }
644
- let typeMap = this.#nthOfTypeCache.get(parentNode);
645
- if (!typeMap) {
646
- typeMap = new Map();
647
- this.#nthOfTypeCache.set(parentNode, typeMap);
648
- }
649
- let typedSiblings = typeMap.get(typeKey);
650
- if (!typedSiblings) {
651
- typedSiblings = [];
652
- let sibling = parentNode.firstElementChild;
653
- while (sibling) {
654
- if (
655
- sibling.localName === localName &&
656
- sibling.namespaceURI === namespaceURI &&
657
- sibling.prefix === prefix
658
- ) {
659
- typedSiblings.push(sibling);
660
- }
661
- sibling = sibling.nextElementSibling;
662
- }
663
- typeMap.set(typeKey, typedSiblings);
664
- }
665
- const matchedNodes = filterNodesByAnB(typedSiblings, anb);
666
- const resultSet = new Set(matchedNodes);
667
- typeResultMap.set(typeKey, resultSet);
668
- return resultSet;
669
- };
670
-
671
- /**
672
- * Matches An+B.
673
- * @private
674
- * @param {object} ast - The AST.
675
- * @param {object} node - The Element node.
676
- * @param {string} nthName - The name of the nth pseudo-class.
677
- * @param {object} opt - Options.
678
- * @returns {boolean} True if matches, otherwise false.
679
- */
680
- _matchAnPlusB = (ast, node, nthName, opt) => {
681
- if (!this.#anbCache) {
682
- this.#anbCache = new WeakMap();
683
- }
684
- let anb = this.#anbCache.get(ast);
685
- if (!anb) {
686
- const {
687
- nth: { a, b, name: nthIdentName },
688
- selector
689
- } = ast;
690
- const anbMap = new Map();
691
- if (nthIdentName) {
692
- if (nthIdentName === 'even') {
693
- anbMap.set('a', 2);
694
- anbMap.set('b', 0);
695
- } else if (nthIdentName === 'odd') {
696
- anbMap.set('a', 2);
697
- anbMap.set('b', 1);
698
- }
699
- if (nthName.indexOf('last') > -1) {
700
- anbMap.set('reverse', true);
701
- }
702
- } else {
703
- if (typeof a === 'string' && /-?\d+/.test(a)) {
704
- anbMap.set('a', a * 1);
705
- } else {
706
- anbMap.set('a', 0);
707
- }
708
- if (typeof b === 'string' && /-?\d+/.test(b)) {
709
- anbMap.set('b', b * 1);
710
- } else {
711
- anbMap.set('b', 0);
712
- }
713
- if (nthName.indexOf('last') > -1) {
714
- anbMap.set('reverse', true);
715
- }
716
- }
717
- if (nthName === 'nth-child' || nthName === 'nth-last-child') {
718
- if (selector) {
719
- anbMap.set('selector', selector);
720
- }
721
- }
722
- anb = Object.fromEntries(anbMap);
723
- this.#anbCache.set(ast, anb);
724
- }
725
- if (nthName === 'nth-child' || nthName === 'nth-last-child') {
726
- const nodes = this._collectNthChild(anb, node, opt);
727
- return nodes.has(node);
728
- } else if (nthName === 'nth-of-type' || nthName === 'nth-last-of-type') {
729
- const nodes = this._collectNthOfType(anb, node);
730
- return nodes.has(node);
731
- }
732
- return false;
733
- };
734
-
735
- /**
736
- * Matches the :has() pseudo-class function.
737
- * @private
738
- * @param {Array.<object>} astLeaves - The AST leaves.
739
- * @param {object} node - The Element node.
740
- * @param {object} [opt] - Options.
741
- * @returns {boolean} The result.
742
- */
743
- _matchHasPseudoFunc = (astLeaves, node, opt = {}) => {
744
- const l = astLeaves.length;
745
- if (!l) {
746
- return false;
747
- }
748
- let startIndex = 0;
749
- let combo;
750
- if (astLeaves[0].type === COMBINATOR) {
751
- combo = astLeaves[0];
752
- startIndex = 1;
753
- } else {
754
- combo = {
755
- name: ' ',
756
- type: COMBINATOR
757
- };
758
- startIndex = 0;
759
- }
760
- const twigLeaves = [];
761
- let nextComboIndex = startIndex;
762
- for (; nextComboIndex < l; nextComboIndex++) {
763
- if (astLeaves[nextComboIndex].type === COMBINATOR) {
764
- break;
765
- }
766
- twigLeaves.push(astLeaves[nextComboIndex]);
767
- }
768
- const twig = {
769
- combo,
770
- leaves: twigLeaves
771
- };
772
- opt.dir = DIR_NEXT;
773
- const nodes = this._collectCombinatorMatches(twig, node, opt, []);
774
- if (nodes.length) {
775
- if (nextComboIndex < l) {
776
- let bool = false;
777
- const remainingLeaves = astLeaves.slice(nextComboIndex);
778
- for (const nextNode of nodes) {
779
- bool = this._matchHasPseudoFunc(remainingLeaves, nextNode, opt);
780
- if (bool) {
781
- break;
782
- }
783
- }
784
- return bool;
785
- }
786
- return true;
787
- }
788
- return false;
789
- };
790
-
791
- /**
792
- * Builds an Allowlist for the :has() branch using a sparse seed element.
793
- * @private
794
- * @param {Array} leaves - The AST leaves of the selector branch.
795
- * @returns {object|null} The wrapper object containing the WeakSet, or null.
796
- */
797
- _buildHasAllowlist = leaves => {
798
- const { seed } = findBestSeed(leaves);
799
- if (!seed) {
800
- return null;
801
- }
802
- if (this.#shadow || this.#node.nodeType === DOCUMENT_FRAGMENT_NODE) {
803
- return null;
804
- }
805
- let seedElements = null;
806
- let isSingleNode = false;
807
- if (seed.type === 'id') {
808
- if (typeof this.#root.getElementById === 'function') {
809
- const node = this.#root.getElementById(seed.value);
810
- if (node) {
811
- seedElements = node;
812
- isSingleNode = true;
813
- }
814
- }
815
- } else if (seed.type === 'class') {
816
- if (typeof this.#root.getElementsByClassName === 'function') {
817
- seedElements = this.#root.getElementsByClassName(seed.value);
818
- }
819
- } else if (seed.type === 'tag') {
820
- if (typeof this.#root.getElementsByTagName === 'function') {
821
- seedElements = this.#root.getElementsByTagName(seed.value);
822
- }
823
- }
824
- if (!seedElements) {
825
- return null;
826
- }
827
- const len = isSingleNode ? 1 : seedElements.length;
828
- if (len === 0 || len > HEX * HEX) {
829
- return null;
830
- }
831
- const filterResult = {
832
- seeded: true,
833
- set: new WeakSet()
834
- };
835
- const list = filterResult.set;
836
- const visitedAncestors = new Set();
837
- if (this.#node) {
838
- list.add(this.#node);
839
- }
840
- for (let i = 0; i < len; i++) {
841
- const current = isSingleNode ? seedElements : seedElements[i];
842
- if (current) {
843
- populateHasAllowlist(current, list, visitedAncestors);
844
- }
845
- }
846
- return filterResult;
847
- };
848
-
849
- /**
850
- * Evaluates :has() pseudo-class.
851
- * @private
852
- * @param {object} astData - The AST data.
853
- * @param {object} node - The Element node.
854
- * @param {object} [opt] - Options.
855
- * @returns {?object} The matched node.
856
- */
857
- _evaluateHasPseudo = (astData, node, opt = {}) => {
858
- const { branches } = astData;
859
- let bool = false;
860
- if (!this.#psHasFilterCache) {
861
- this.#psHasFilterCache = new WeakMap();
862
- }
863
- let rootCache = this.#psHasFilterCache.get(this.#root);
864
- if (!rootCache) {
865
- rootCache = new WeakMap();
866
- this.#psHasFilterCache.set(this.#root, rootCache);
867
- }
868
- for (const leaves of branches) {
869
- if (!rootCache.has(leaves)) {
870
- const filterResult = this._buildHasAllowlist(leaves);
871
- rootCache.set(leaves, filterResult);
872
- }
873
- const allowlist = rootCache.get(leaves);
874
- if (
875
- allowlist &&
876
- allowlist.seeded &&
877
- node.nodeType !== DOCUMENT_FRAGMENT_NODE &&
878
- !allowlist.set.has(node)
879
- ) {
880
- continue;
881
- }
882
- bool = this._matchHasPseudoFunc(leaves, node, opt);
883
- if (bool) {
884
- break;
885
- }
886
- }
887
- if (!bool) {
888
- return null;
889
- }
890
- if (
891
- (opt.isShadowRoot || this.#shadow) &&
892
- node.nodeType === DOCUMENT_FRAGMENT_NODE
893
- ) {
894
- return this.#verifyShadowHost ? node : null;
895
- }
896
- return node;
897
- };
898
-
899
- /**
900
- * Matches logical pseudo-class functions.
901
- * @private
902
- * @param {object} astData - The AST data.
903
- * @param {object} node - The Element node.
904
- * @param {object} [opt] - Options.
905
- * @returns {boolean} Tru if matches, otherwise false.
906
- */
907
- _matchLogicalPseudoFunc = (astData, node, opt = {}) => {
908
- const { astName, branches, twigBranches } = astData;
909
- // Handle :has().
910
- if (astName === 'has') {
911
- return this._evaluateHasPseudo(astData, node, opt) === node;
912
- }
913
- // Handle :is(), :not(), :where().
914
- const isShadowRoot =
915
- (opt.isShadowRoot || this.#shadow) &&
916
- node.nodeType === DOCUMENT_FRAGMENT_NODE;
917
- // Check for invalid shadow root.
918
- if (isShadowRoot) {
919
- let invalid = false;
920
- for (const branch of branches) {
921
- if (branch.length > 1) {
922
- invalid = true;
923
- break;
924
- } else if (astName === 'not') {
925
- const [{ type: childAstType }] = branch;
926
- if (childAstType !== PS_CLASS_SELECTOR) {
927
- invalid = true;
928
- break;
929
- }
930
- }
931
- }
932
- if (invalid) {
933
- return false;
934
- }
935
- }
936
- opt.forgive = astName === 'is' || astName === 'where';
937
- const l = twigBranches.length;
938
- let bool;
939
- for (let i = 0; i < l; i++) {
940
- const branch = twigBranches[i];
941
- const lastIndex = branch.length - 1;
942
- const { leaves } = branch[lastIndex];
943
- bool = this._matchLeaves(leaves, node, opt);
944
- if (bool && lastIndex > 0) {
945
- let nextNodes = new Set([node]);
946
- for (let j = lastIndex - 1; j >= 0; j--) {
947
- const twig = branch[j];
948
- const arr = [];
949
- opt.dir = DIR_PREV;
950
- for (const nextNode of nextNodes) {
951
- this._collectCombinatorMatches(twig, nextNode, opt, arr);
952
- }
953
- if (arr.length) {
954
- if (j === 0) {
955
- bool = true;
956
- } else {
957
- nextNodes = new Set(arr);
958
- }
959
- } else {
960
- bool = false;
961
- break;
962
- }
963
- }
964
- }
965
- if (bool) {
966
- break;
967
- }
968
- }
969
- if (astName === 'not') {
970
- return !bool;
971
- }
972
- return bool;
973
- };
974
-
975
- /**
976
- * Evaluates logical pseudo-class selector.
977
- * @private
978
- * @param {object} ast - The AST.
979
- * @param {object} node - The Element node.
980
- * @param {object} [opt] - Options.
981
- * @param {boolean} [opt.forgive] - Ignores unknown or invalid selectors.
982
- * @param {boolean} [opt.warn] - If true, console warnings are enabled.
983
- * @returns {boolean} True if matches, otherwise false.
984
- */
985
- _evaluateLogicalPseudo(ast, node, opt = {}) {
986
- const { children: astChildren, name: astName } = ast;
987
- if (!astChildren.length && astName !== 'is' && astName !== 'where') {
988
- const css = generateCSS(ast);
989
- const msg = `Invalid selector ${css}`;
990
- this.onError(generateException(msg, SYNTAX_ERR, this.#window));
991
- return false;
992
- }
993
- let astData;
994
- if (this.#astCache.has(ast)) {
995
- astData = this.#astCache.get(ast);
996
- } else {
997
- const { branches } = walkAST(ast);
998
- if (astName === 'has') {
999
- astData = {
1000
- astName,
1001
- branches
1002
- };
1003
- } else {
1004
- const twigBranches = [];
1005
- const l = branches.length;
1006
- for (let i = 0; i < l; i++) {
1007
- const leaves = branches[i];
1008
- const branch = [];
1009
- const leavesSet = new Set();
1010
- const leavesLen = leaves.length;
1011
- for (let j = 0; j < leavesLen; j++) {
1012
- const item = leaves[j];
1013
- if (item.type === COMBINATOR) {
1014
- branch.push({
1015
- combo: item,
1016
- leaves: [...leavesSet]
1017
- });
1018
- leavesSet.clear();
1019
- } else {
1020
- leavesSet.add(item);
1021
- }
1022
- if (j === leavesLen - 1) {
1023
- branch.push({
1024
- combo: null,
1025
- leaves: [...leavesSet]
1026
- });
1027
- leavesSet.clear();
1028
- }
1029
- }
1030
- twigBranches.push(branch);
1031
- }
1032
- astData = {
1033
- astName,
1034
- branches,
1035
- twigBranches
1036
- };
1037
- }
1038
- this.#astCache.set(ast, astData);
1039
- }
1040
- return this._matchLogicalPseudoFunc(astData, node, opt);
1041
- }
1042
-
1043
- /**
1044
- * Evaluates pseudo-class function.
1045
- * @private
1046
- * @see https://html.spec.whatwg.org/#pseudo-classes
1047
- * @param {object} ast - The AST.
1048
- * @param {object} node - The Element node.
1049
- * @param {object} [opt] - Options.
1050
- * @param {boolean} [opt.forgive] - Ignores unknown or invalid selectors.
1051
- * @param {boolean} [opt.warn] - If true, console warnings are enabled.
1052
- * @returns {boolean} True if matches, otherwise false.
1053
- */
1054
- _evaluatePseudoClassFunc(ast, node, opt = {}) {
1055
- const { children: astChildren, name: astName } = ast;
1056
- const { forgive, warn = this.#warn } = opt;
1057
- // :nth-child(), :nth-last-child(), nth-of-type(), :nth-last-of-type()
1058
- if (/^nth-(?:last-)?(?:child|of-type)$/.test(astName)) {
1059
- if (astChildren.length !== 1) {
1060
- const css = generateCSS(ast);
1061
- this.onError(
1062
- generateException(`Invalid selector ${css}`, SYNTAX_ERR, this.#window)
1063
- );
1064
- return false;
1065
- }
1066
- const [branch] = astChildren;
1067
- return this._matchAnPlusB(branch, node, astName, opt);
1068
- }
1069
- switch (astName) {
1070
- // :dir()
1071
- case 'dir': {
1072
- if (astChildren.length !== 1) {
1073
- const css = generateCSS(ast);
1074
- this.onError(
1075
- generateException(
1076
- `Invalid selector ${css}`,
1077
- SYNTAX_ERR,
1078
- this.#window
1079
- )
1080
- );
1081
- return false;
1082
- }
1083
- const [astChild] = astChildren;
1084
- if (!this.#psDirCache) {
1085
- this.#psDirCache = new WeakMap();
1086
- }
1087
- const res = matchDirectionPseudoClass(astChild, node, this.#psDirCache);
1088
- if (res) {
1089
- return true;
1090
- }
1091
- break;
1092
- }
1093
- // :lang()
1094
- case 'lang': {
1095
- if (!astChildren.length) {
1096
- const css = generateCSS(ast);
1097
- this.onError(
1098
- generateException(
1099
- `Invalid selector ${css}`,
1100
- SYNTAX_ERR,
1101
- this.#window
1102
- )
1103
- );
1104
- return false;
1105
- }
1106
- if (!this.#psLangCache) {
1107
- this.#psLangCache = new WeakMap();
1108
- }
1109
- let bool;
1110
- for (const astChild of astChildren) {
1111
- bool = matchLanguagePseudoClass(astChild, node, this.#psLangCache);
1112
- if (bool) {
1113
- break;
1114
- }
1115
- }
1116
- if (bool) {
1117
- return true;
1118
- }
1119
- break;
1120
- }
1121
- // :state()
1122
- case 'state': {
1123
- if (isCustomElement(node)) {
1124
- const [{ value: stateValue }] = astChildren;
1125
- if (stateValue) {
1126
- if (node[stateValue]) {
1127
- return true;
1128
- }
1129
- for (const i in node) {
1130
- const prop = node[i];
1131
- if (prop instanceof this.#window.ElementInternals) {
1132
- if (prop?.states?.has(stateValue)) {
1133
- return true;
1134
- }
1135
- break;
1136
- }
1137
- }
1138
- }
1139
- }
1140
- break;
1141
- }
1142
- case 'current':
1143
- case 'heading':
1144
- case 'nth-col':
1145
- case 'nth-last-col': {
1146
- if (warn) {
1147
- this.onError(
1148
- generateException(
1149
- `Unsupported pseudo-class :${astName}()`,
1150
- NOT_SUPPORTED_ERR,
1151
- this.#window
1152
- )
1153
- );
1154
- }
1155
- break;
1156
- }
1157
- // Ignore :host() and :host-context().
1158
- case 'host':
1159
- case 'host-context': {
1160
- break;
1161
- }
1162
- // Deprecated in CSS Selectors 3.
1163
- case 'contains': {
1164
- if (warn) {
1165
- this.onError(
1166
- generateException(
1167
- `Unknown pseudo-class :${astName}()`,
1168
- NOT_SUPPORTED_ERR,
1169
- this.#window
1170
- )
1171
- );
1172
- }
1173
- break;
1174
- }
1175
- default: {
1176
- if (!forgive) {
1177
- this.onError(
1178
- generateException(
1179
- `Unknown pseudo-class :${astName}()`,
1180
- SYNTAX_ERR,
1181
- this.#window
1182
- )
1183
- );
1184
- }
1185
- }
1186
- }
1187
- return false;
1188
- }
1189
-
1190
- /**
1191
- * Evaluates *-of-type pseudo-class selector.
1192
- * @private
1193
- * @param {string} astName - The AST name.
1194
- * @param {object} node - The Element node.
1195
- * @returns {boolean} True if matches, otherwise false.
1196
- */
1197
-
1198
- /**
1199
- * Matches pseudo-class selector.
1200
- * @private
1201
- * @see https://html.spec.whatwg.org/#pseudo-classes
1202
- * @param {object} ast - The AST.
1203
- * @param {object} node - The Element node.
1204
- * @param {object} [opt] - Options.
1205
- * @param {boolean} [opt.forgive] - Ignores unknown or invalid selectors.
1206
- * @param {boolean} [opt.warn] - If true, console warnings are enabled.
1207
- * @returns {Set.<object>|boolean} A collection of matched nodes.
1208
- */
1209
- _matchPseudoClassSelector(ast, node, opt = {}) {
1210
- const { children: astChildren, name: astName } = ast;
1211
- const { localName, parentNode } = node;
1212
- const { forgive, warn = this.#warn } = opt;
1213
- if (Array.isArray(astChildren)) {
1214
- // :has(), :is(), :not(), :where()
1215
- if (KEYS_LOGICAL.has(astName)) {
1216
- return this._evaluateLogicalPseudo(ast, node, opt);
1217
- }
1218
- return this._evaluatePseudoClassFunc(ast, node, opt);
1219
- }
1220
- if (KEYS_PS_NTH_OF_TYPE.has(astName)) {
1221
- if (!parentNode) {
1222
- return node === this.#root;
1223
- }
1224
- switch (astName) {
1225
- case 'first-of-type': {
1226
- const [node1] = this._collectNthOfType(ANB_FIRST, node);
1227
- return node1 === node;
1228
- }
1229
- case 'last-of-type': {
1230
- const [node1] = this._collectNthOfType(ANB_LAST, node);
1231
- return node1 === node;
1232
- }
1233
- // 'only-of-type' is handled by default.
1234
- default: {
1235
- const [node1] = this._collectNthOfType(ANB_FIRST, node);
1236
- if (node1 === node) {
1237
- const [node2] = this._collectNthOfType(ANB_LAST, node);
1238
- return node2 === node;
1239
- }
1240
- }
1241
- }
1242
- return false;
1243
- }
1244
- switch (astName) {
1245
- /* Elemental pseudo-classes */
1246
- case 'defined': {
1247
- if (node.hasAttribute('is') || localName.includes('-')) {
1248
- return isCustomElement(node);
1249
- }
1250
- return (
1251
- node instanceof this.#window.HTMLElement ||
1252
- node instanceof this.#window.SVGElement
1253
- );
1254
- }
1255
- /* Element display state pseudo-classes */
1256
- case 'open': {
1257
- // <select> and <input type="color"> are not supported.
1258
- return (
1259
- (localName === 'details' || localName === 'dialog') &&
1260
- node.hasAttribute('open')
1261
- );
1262
- }
1263
- case 'popover-open': {
1264
- // FIXME: Not implemented in jsdom
1265
- // @see https://github.com/jsdom/jsdom/issues/3721
1266
- // return node.popover && isVisible(node);
1267
- break;
1268
- }
1269
- /* Input pseudo-classes */
1270
- case 'disabled':
1271
- case 'enabled': {
1272
- return matchDisabledPseudoClass(astName, node);
1273
- }
1274
- case 'read-only':
1275
- case 'read-write': {
1276
- return matchReadOnlyPseudoClass(astName, node);
1277
- }
1278
- case 'placeholder-shown': {
1279
- let placeholder;
1280
- if (node.placeholder) {
1281
- placeholder = node.placeholder;
1282
- } else if (node.hasAttribute('placeholder')) {
1283
- placeholder = node.getAttribute('placeholder');
1284
- }
1285
- if (typeof placeholder === 'string' && !/[\r\n]/.test(placeholder)) {
1286
- let targetNode;
1287
- if (localName === 'textarea') {
1288
- targetNode = node;
1289
- } else if (localName === 'input') {
1290
- if (node.hasAttribute('type')) {
1291
- if (KEYS_INPUT_PLACEHOLDER.has(node.getAttribute('type'))) {
1292
- targetNode = node;
1293
- }
1294
- } else {
1295
- targetNode = node;
1296
- }
1297
- }
1298
- if (targetNode) {
1299
- return node.value === '';
1300
- }
1301
- }
1302
- break;
1303
- }
1304
- case 'default': {
1305
- // option
1306
- if (localName === 'option') {
1307
- return node.hasAttribute('selected');
1308
- }
1309
- const attrType = node.getAttribute('type');
1310
- // input[type="checkbox"], input[type="radio"]
1311
- if (
1312
- localName === 'input' &&
1313
- node.hasAttribute('type') &&
1314
- node.hasAttribute('checked')
1315
- ) {
1316
- return KEYS_INPUT_CHECK.has(attrType);
1317
- }
1318
- // button[type="submit"], input[type="submit"], input[type="image"]
1319
- if (
1320
- (localName === 'button' &&
1321
- !(node.hasAttribute('type') && KEYS_INPUT_RESET.has(attrType))) ||
1322
- (localName === 'input' &&
1323
- node.hasAttribute('type') &&
1324
- KEYS_INPUT_SUBMIT.has(attrType))
1325
- ) {
1326
- let form = node.parentNode;
1327
- while (form) {
1328
- if (form.localName === 'form') {
1329
- break;
1330
- }
1331
- form = form.parentNode;
1332
- }
1333
- if (form) {
1334
- if (!this.#psDefaultCache) {
1335
- this.#psDefaultCache = new WeakMap();
1336
- }
1337
- let defaultSubmit = this.#psDefaultCache.get(form);
1338
- if (!defaultSubmit) {
1339
- const walker = this._createTreeWalker(form, { force: true });
1340
- let refNode = traverseNode(form, walker);
1341
- refNode = walker.firstChild();
1342
- while (refNode) {
1343
- const nodeName = refNode.localName;
1344
- const nodeAttrType = refNode.getAttribute('type');
1345
- let m;
1346
- if (nodeName === 'button') {
1347
- m = !(
1348
- refNode.hasAttribute('type') &&
1349
- KEYS_INPUT_RESET.has(nodeAttrType)
1350
- );
1351
- } else if (nodeName === 'input') {
1352
- m =
1353
- refNode.hasAttribute('type') &&
1354
- KEYS_INPUT_SUBMIT.has(nodeAttrType);
1355
- }
1356
- if (m) {
1357
- defaultSubmit = refNode;
1358
- break;
1359
- }
1360
- refNode = walker.nextNode();
1361
- }
1362
- this.#psDefaultCache.set(form, defaultSubmit);
1363
- }
1364
- return defaultSubmit === node;
1365
- }
1366
- }
1367
- break;
1368
- }
1369
- case 'checked': {
1370
- if (localName === 'option') {
1371
- return node.selected;
1372
- }
1373
- if (localName === 'input') {
1374
- const attrType = node.getAttribute('type');
1375
- return (
1376
- node.checked && (attrType === 'checkbox' || attrType === 'radio')
1377
- );
1378
- }
1379
- break;
1380
- }
1381
- case 'indeterminate': {
1382
- if (localName === 'progress') {
1383
- return !node.hasAttribute('value');
1384
- }
1385
- if (localName === 'input' && node.type === 'checkbox') {
1386
- return node.indeterminate;
1387
- }
1388
- if (localName === 'input' && node.type === 'radio') {
1389
- if (node.checked || node.hasAttribute('checked')) {
1390
- return false;
1391
- }
1392
- const nodeName = node.name;
1393
- let parent = node.parentNode;
1394
- while (parent) {
1395
- if (parent.localName === 'form') {
1396
- break;
1397
- }
1398
- parent = parent.parentNode;
1399
- }
1400
- if (!parent) {
1401
- parent = this.#document.documentElement;
1402
- }
1403
- if (!this.#psIndeterminateCache) {
1404
- this.#psIndeterminateCache = new WeakMap();
1405
- }
1406
- let parentCache = this.#psIndeterminateCache.get(parent);
1407
- if (!parentCache) {
1408
- parentCache = new Map();
1409
- this.#psIndeterminateCache.set(parent, parentCache);
1410
- }
1411
- let checked = parentCache.get(nodeName);
1412
- if (checked === undefined) {
1413
- const walker = this._createTreeWalker(parent, { force: true });
1414
- let refNode = traverseNode(parent, walker);
1415
- refNode = walker.firstChild();
1416
- while (refNode) {
1417
- if (
1418
- refNode.localName === 'input' &&
1419
- refNode.getAttribute('type') === 'radio'
1420
- ) {
1421
- if (refNode.hasAttribute('name')) {
1422
- if (refNode.getAttribute('name') === nodeName) {
1423
- checked = !!refNode.checked;
1424
- }
1425
- } else {
1426
- checked = !!refNode.checked;
1427
- }
1428
- if (checked) {
1429
- break;
1430
- }
1431
- }
1432
- refNode = walker.nextNode();
1433
- }
1434
- checked = !!checked;
1435
- parentCache.set(nodeName, checked);
1436
- }
1437
- return !checked;
1438
- }
1439
- break;
1440
- }
1441
- case 'valid':
1442
- case 'invalid': {
1443
- if (KEYS_FORM_PS_VALID.has(localName)) {
1444
- let valid = false;
1445
- if (node.checkValidity()) {
1446
- if (node.maxLength >= 0) {
1447
- if (node.maxLength >= node.value.length) {
1448
- valid = true;
1449
- }
1450
- } else {
1451
- valid = true;
1452
- }
1453
- }
1454
- if (astName === 'invalid') {
1455
- return !valid;
1456
- }
1457
- return valid;
1458
- }
1459
- if (localName === 'fieldset') {
1460
- if (!this.#psValidCache) {
1461
- this.#psValidCache = new WeakMap();
1462
- }
1463
- let valid = this.#psValidCache.get(node);
1464
- if (valid === undefined && !this.#psValidCache.has(node)) {
1465
- const walker = this._createTreeWalker(node, { force: true });
1466
- let refNode = traverseNode(node, walker);
1467
- refNode = walker.firstChild();
1468
- if (!refNode) {
1469
- valid = true;
1470
- } else {
1471
- while (refNode) {
1472
- if (KEYS_FORM_PS_VALID.has(refNode.localName)) {
1473
- if (refNode.checkValidity()) {
1474
- if (refNode.maxLength >= 0) {
1475
- valid = refNode.maxLength >= refNode.value.length;
1476
- } else {
1477
- valid = true;
1478
- }
1479
- } else {
1480
- valid = false;
1481
- }
1482
- if (!valid) {
1483
- break;
1484
- }
1485
- }
1486
- refNode = walker.nextNode();
1487
- }
1488
- }
1489
- this.#psValidCache.set(node, valid);
1490
- }
1491
- if (astName === 'invalid') {
1492
- return !valid;
1493
- }
1494
- return valid;
1495
- }
1496
- break;
1497
- }
1498
- case 'in-range':
1499
- case 'out-of-range': {
1500
- const attrType = node.getAttribute('type');
1501
- if (
1502
- localName === 'input' &&
1503
- !(node.readOnly || node.hasAttribute('readonly')) &&
1504
- !(node.disabled || node.hasAttribute('disabled')) &&
1505
- KEYS_INPUT_RANGE.has(attrType)
1506
- ) {
1507
- const flowed =
1508
- node.validity.rangeUnderflow || node.validity.rangeOverflow;
1509
- if (astName === 'out-of-range') {
1510
- return flowed;
1511
- }
1512
- return flowed
1513
- ? false
1514
- : node.hasAttribute('min') ||
1515
- node.hasAttribute('max') ||
1516
- attrType === 'range';
1517
- }
1518
- break;
1519
- }
1520
- case 'required':
1521
- case 'optional': {
1522
- let required = false;
1523
- if (localName === 'select' || localName === 'textarea') {
1524
- if (node.required || node.hasAttribute('required')) {
1525
- required = true;
1526
- }
1527
- } else if (localName === 'input') {
1528
- if (node.hasAttribute('type')) {
1529
- const attrType = node.getAttribute('type');
1530
- if (KEYS_INPUT_REQUIRED.has(attrType)) {
1531
- if (node.required || node.hasAttribute('required')) {
1532
- required = true;
1533
- }
1534
- }
1535
- } else if (node.required || node.hasAttribute('required')) {
1536
- required = true;
1537
- }
1538
- }
1539
- if (astName === 'optional') {
1540
- return !required;
1541
- }
1542
- return required;
1543
- }
1544
- /* Location pseudo-classes */
1545
- case 'any-link':
1546
- case 'link': {
1547
- return (
1548
- (localName === 'a' || localName === 'area') &&
1549
- node.hasAttribute('href')
1550
- );
1551
- }
1552
- case 'local-link': {
1553
- if (
1554
- (localName === 'a' || localName === 'area') &&
1555
- node.hasAttribute('href')
1556
- ) {
1557
- if (!this.#documentURL) {
1558
- this.#documentURL = new URL(this.#document.URL);
1559
- }
1560
- const { href, origin, pathname } = this.#documentURL;
1561
- const attrURL = new URL(node.getAttribute('href'), href);
1562
- return attrURL.origin === origin && attrURL.pathname === pathname;
1563
- }
1564
- break;
1565
- }
1566
- case 'visited': {
1567
- // prevent fingerprinting
1568
- break;
1569
- }
1570
- case 'target': {
1571
- if (!this.#documentURL) {
1572
- this.#documentURL = new URL(this.#document.URL);
1573
- }
1574
- const { hash } = this.#documentURL;
1575
- return hash && hash === `#${node.id}` && this.#document.contains(node);
1576
- }
1577
- case 'scope': {
1578
- if (this.#node.nodeType === ELEMENT_NODE) {
1579
- return !this.#shadow && node === this.#node;
1580
- }
1581
- return node === this.#document.documentElement;
1582
- }
1583
- /* Tree-structural pseudo-classes */
1584
- case 'root': {
1585
- return node === this.#document.documentElement;
1586
- }
1587
- case 'empty': {
1588
- if (!node.hasChildNodes()) {
1589
- return true;
1590
- }
1591
- const walker = this._createTreeWalker(node, {
1592
- force: true,
1593
- whatToShow: SHOW_ALL
1594
- });
1595
- let refNode = walker.firstChild();
1596
- let bool;
1597
- while (refNode) {
1598
- bool =
1599
- refNode.nodeType !== ELEMENT_NODE && refNode.nodeType !== TEXT_NODE;
1600
- if (!bool) {
1601
- break;
1602
- }
1603
- refNode = walker.nextSibling();
1604
- }
1605
- return bool;
1606
- }
1607
- case 'first-child':
1608
- case 'last-child':
1609
- case 'only-child': {
1610
- if (!parentNode) {
1611
- return node === this.#root;
1612
- }
1613
- if (astName === 'first-child') {
1614
- return node === parentNode.firstElementChild;
1615
- }
1616
- if (astName === 'last-child') {
1617
- return node === parentNode.lastElementChild;
1618
- }
1619
- return (
1620
- node === parentNode.firstElementChild &&
1621
- node === parentNode.lastElementChild
1622
- );
1623
- }
1624
- /* User action pseudo-classes */
1625
- case 'hover': {
1626
- const { target, type } = this.#event ?? {};
1627
- return (
1628
- /^(?:click|mouse(?:down|over|up))$/.test(type) &&
1629
- target?.nodeType === ELEMENT_NODE &&
1630
- node.contains(target)
1631
- );
1632
- }
1633
- case 'active': {
1634
- const { buttons, target, type } = this.#event ?? {};
1635
- return (
1636
- type === 'mousedown' &&
1637
- buttons & 1 &&
1638
- target?.nodeType === ELEMENT_NODE &&
1639
- node.contains(target)
1640
- );
1641
- }
1642
- case 'focus': {
1643
- const activeElement = this.#document.activeElement;
1644
- if (activeElement.shadowRoot) {
1645
- const activeShadowElement = activeElement.shadowRoot.activeElement;
1646
- let current = activeShadowElement;
1647
- while (current) {
1648
- if (current.nodeType === DOCUMENT_FRAGMENT_NODE) {
1649
- const { host } = current;
1650
- if (host === activeElement) {
1651
- if (isFocusableArea(node)) {
1652
- return true;
1653
- }
1654
- return host === node;
1655
- }
1656
- }
1657
- current = current.parentNode;
1658
- }
1659
- }
1660
- return node === activeElement && isFocusableArea(node);
1661
- }
1662
- case 'focus-visible': {
1663
- if (node === this.#document.activeElement && isFocusableArea(node)) {
1664
- let bool;
1665
- if (isFocusVisible(node)) {
1666
- bool = true;
1667
- } else if (this.#focus) {
1668
- const { relatedTarget, target: focusTarget } = this.#focus;
1669
- if (focusTarget === node) {
1670
- if (isFocusVisible(relatedTarget)) {
1671
- bool = true;
1672
- } else if (this.#event) {
1673
- const {
1674
- altKey: eventAltKey,
1675
- ctrlKey: eventCtrlKey,
1676
- key: eventKey,
1677
- metaKey: eventMetaKey,
1678
- target: eventTarget,
1679
- type: eventType
1680
- } = this.#event;
1681
- // this.#event is irrelevant if eventTarget === relatedTarget
1682
- if (eventTarget === relatedTarget) {
1683
- if (!this.#lastFocusVisible) {
1684
- bool = true;
1685
- } else if (focusTarget === this.#lastFocusVisible) {
1686
- bool = true;
1687
- }
1688
- } else if (eventKey === 'Tab') {
1689
- if (
1690
- (eventType === 'keydown' && eventTarget !== node) ||
1691
- (eventType === 'keyup' && eventTarget === node)
1692
- ) {
1693
- if (eventTarget === focusTarget) {
1694
- if (!this.#lastFocusVisible) {
1695
- bool = true;
1696
- } else if (
1697
- eventTarget === this.#lastFocusVisible &&
1698
- relatedTarget === null
1699
- ) {
1700
- bool = true;
1701
- }
1702
- } else {
1703
- bool = true;
1704
- }
1705
- }
1706
- } else if (eventKey) {
1707
- if (
1708
- (eventType === 'keydown' || eventType === 'keyup') &&
1709
- !eventAltKey &&
1710
- !eventCtrlKey &&
1711
- !eventMetaKey &&
1712
- eventTarget === node
1713
- ) {
1714
- bool = true;
1715
- }
1716
- }
1717
- } else if (
1718
- relatedTarget === null ||
1719
- relatedTarget === this.#lastFocusVisible
1720
- ) {
1721
- bool = true;
1722
- }
1723
- }
1724
- }
1725
- if (bool) {
1726
- this.#lastFocusVisible = node;
1727
- return bool;
1728
- }
1729
- if (this.#lastFocusVisible === node) {
1730
- this.#lastFocusVisible = null;
1731
- }
1732
- }
1733
- break;
1734
- }
1735
- case 'focus-within': {
1736
- if (!this.#focusWithinCache) {
1737
- this.#focusWithinCache = new Set();
1738
- let currentFocus = this.#document.activeElement;
1739
- if (currentFocus && isFocusableArea(currentFocus)) {
1740
- while (currentFocus) {
1741
- this.#focusWithinCache.add(currentFocus);
1742
- if (currentFocus.parentNode) {
1743
- currentFocus = currentFocus.parentNode;
1744
- } else if (
1745
- currentFocus.nodeType === DOCUMENT_FRAGMENT_NODE &&
1746
- currentFocus.host
1747
- ) {
1748
- currentFocus = currentFocus.host;
1749
- } else {
1750
- break;
1751
- }
1752
- }
1753
- } else if (currentFocus && currentFocus.shadowRoot) {
1754
- let shadowFocus = currentFocus.shadowRoot.activeElement;
1755
- if (shadowFocus) {
1756
- while (shadowFocus) {
1757
- this.#focusWithinCache.add(shadowFocus);
1758
- if (shadowFocus.parentNode) {
1759
- shadowFocus = shadowFocus.parentNode;
1760
- } else if (
1761
- shadowFocus.nodeType === DOCUMENT_FRAGMENT_NODE &&
1762
- shadowFocus.host
1763
- ) {
1764
- shadowFocus = shadowFocus.host;
1765
- } else {
1766
- break;
1767
- }
1768
- }
1769
- }
1770
- }
1771
- }
1772
- return this.#focusWithinCache.has(node);
1773
- }
1774
- // Ignore :host.
1775
- case 'host': {
1776
- break;
1777
- }
1778
- // Legacy pseudo-elements.
1779
- case 'after':
1780
- case 'before':
1781
- case 'first-letter':
1782
- case 'first-line': {
1783
- if (warn) {
1784
- this.onError(
1785
- generateException(
1786
- `Unsupported pseudo-element ::${astName}`,
1787
- NOT_SUPPORTED_ERR,
1788
- this.#window
1789
- )
1790
- );
1791
- }
1792
- break;
1793
- }
1794
- // Not supported.
1795
- case 'autofill':
1796
- case 'blank':
1797
- case 'buffering':
1798
- case 'current':
1799
- case 'fullscreen':
1800
- case 'future':
1801
- case 'has-slotted':
1802
- case 'heading':
1803
- case 'modal':
1804
- case 'muted':
1805
- case 'past':
1806
- case 'paused':
1807
- case 'picture-in-picture':
1808
- case 'playing':
1809
- case 'seeking':
1810
- case 'stalled':
1811
- case 'user-invalid':
1812
- case 'user-valid':
1813
- case 'volume-locked':
1814
- case '-webkit-autofill': {
1815
- if (warn) {
1816
- this.onError(
1817
- generateException(
1818
- `Unsupported pseudo-class :${astName}`,
1819
- NOT_SUPPORTED_ERR,
1820
- this.#window
1821
- )
1822
- );
1823
- }
1824
- break;
1825
- }
1826
- default: {
1827
- if (astName.startsWith('-webkit-')) {
1828
- if (warn) {
1829
- this.onError(
1830
- generateException(
1831
- `Unsupported pseudo-class :${astName}`,
1832
- NOT_SUPPORTED_ERR,
1833
- this.#window
1834
- )
1835
- );
1836
- }
1837
- } else if (!forgive) {
1838
- this.onError(
1839
- generateException(
1840
- `Unknown pseudo-class :${astName}`,
1841
- SYNTAX_ERR,
1842
- this.#window
1843
- )
1844
- );
1845
- }
1846
- }
1847
- }
1848
- return false;
1849
- }
1850
-
1851
- /**
1852
- * Evaluates the :host() pseudo-class.
1853
- * @private
1854
- * @param {Array.<object>} leaves - The AST leaves.
1855
- * @param {object} host - The host element.
1856
- * @param {object} ast - The original AST for error reporting.
1857
- * @returns {boolean} True if matches, otherwise false.
1858
- */
1859
- _evaluateHostPseudo = (leaves, host, ast) => {
1860
- const l = leaves.length;
1861
- for (let i = 0; i < l; i++) {
1862
- const leaf = leaves[i];
1863
- if (leaf.type === COMBINATOR) {
1864
- const css = generateCSS(ast);
1865
- const msg = `Invalid selector ${css}`;
1866
- this.onError(generateException(msg, SYNTAX_ERR, this.#window));
1867
- return false;
1868
- }
1869
- if (!this._matchSelector(leaf, host)) {
1870
- return false;
1871
- }
1872
- }
1873
- return true;
1874
- };
1875
-
1876
- /**
1877
- * Evaluates the :host-context() pseudo-class.
1878
- * @private
1879
- * @param {Array.<object>} leaves - The AST leaves.
1880
- * @param {object} host - The host element.
1881
- * @param {object} ast - The original AST for error reporting.
1882
- * @returns {boolean} True if matched.
1883
- */
1884
- _evaluateHostContextPseudo = (leaves, host, ast) => {
1885
- let parent = host;
1886
- while (parent) {
1887
- let bool;
1888
- const l = leaves.length;
1889
- for (let i = 0; i < l; i++) {
1890
- const leaf = leaves[i];
1891
- if (leaf.type === COMBINATOR) {
1892
- const css = generateCSS(ast);
1893
- const msg = `Invalid selector ${css}`;
1894
- this.onError(generateException(msg, SYNTAX_ERR, this.#window));
1895
- return false;
1896
- }
1897
- bool = this._matchSelector(leaf, parent);
1898
- if (!bool) {
1899
- break;
1900
- }
1901
- }
1902
- if (bool) {
1903
- return true;
1904
- }
1905
- parent = parent.parentNode;
1906
- }
1907
- return false;
1908
- };
1909
-
1910
- /**
1911
- * Evaluates shadow host pseudo-classes.
1912
- * @private
1913
- * @param {object} ast - The AST.
1914
- * @param {object} node - The DocumentFragment node.
1915
- * @returns {boolean} True if matches, otherwise false.
1916
- */
1917
- _evaluateShadowHost = (ast, node) => {
1918
- const { children: astChildren, name: astName } = ast;
1919
- // Handle simple pseudo-class (no arguments).
1920
- if (!Array.isArray(astChildren)) {
1921
- if (astName === 'host') {
1922
- return true;
1923
- }
1924
- const msg = `Invalid selector :${astName}`;
1925
- this.onError(generateException(msg, SYNTAX_ERR, this.#window));
1926
- return false;
1927
- }
1928
- // Handle functional pseudo-class like :host(...).
1929
- if (astName !== 'host' && astName !== 'host-context') {
1930
- const msg = `Invalid selector :${astName}()`;
1931
- this.onError(generateException(msg, SYNTAX_ERR, this.#window));
1932
- return false;
1933
- }
1934
- if (astChildren.length !== 1) {
1935
- const css = generateCSS(ast);
1936
- const msg = `Invalid selector ${css}`;
1937
- this.onError(generateException(msg, SYNTAX_ERR, this.#window));
1938
- return false;
1939
- }
1940
- const { host } = node;
1941
- const { branches } = walkAST(astChildren[0]);
1942
- const [branch] = branches;
1943
- const [...leaves] = branch;
1944
- if (astName === 'host' && this._evaluateHostPseudo(leaves, host, ast)) {
1945
- return true;
1946
- } else if (
1947
- astName === 'host-context' &&
1948
- this._evaluateHostContextPseudo(leaves, host, ast)
1949
- ) {
1950
- return true;
1951
- }
1952
- return false;
1953
- };
1954
-
1955
- /**
1956
- * Matches a selector for element nodes.
1957
- * @private
1958
- * @param {object} ast - The AST.
1959
- * @param {object} node - The Element node.
1960
- * @param {object} opt - Options.
1961
- * @returns {boolean} True if matches, otherwise false.
1962
- */
1963
- _matchSelectorForElement = (ast, node, opt) => {
1964
- const { type: astType } = ast;
1965
- const astName = unescapeSelector(ast.name);
1966
- switch (astType) {
1967
- case ATTR_SELECTOR: {
1968
- return matchAttributeSelector(ast, node, opt);
1969
- }
1970
- case ID_SELECTOR: {
1971
- return node.id === astName;
1972
- }
1973
- case CLASS_SELECTOR: {
1974
- return node.classList.contains(astName);
1975
- }
1976
- case PS_CLASS_SELECTOR: {
1977
- return this._matchPseudoClassSelector(ast, node, opt);
1978
- }
1979
- case TYPE_SELECTOR: {
1980
- return matchTypeSelector(ast, node, opt);
1981
- }
1982
- // PS_ELEMENT_SELECTOR is handled by default.
1983
- default: {
1984
- try {
1985
- if (this.#check) {
1986
- const css = generateCSS(ast);
1987
- this.#pseudoElement.push(css);
1988
- return true;
1989
- } else {
1990
- matchPseudoElementSelector(astName, astType, opt);
1991
- }
1992
- } catch (e) {
1993
- this.onError(e);
1994
- }
1995
- }
1996
- }
1997
- return false;
1998
- };
1999
-
2000
- /**
2001
- * Matches a selector for a shadow root.
2002
- * @private
2003
- * @param {object} ast - The AST.
2004
- * @param {object} node - The DocumentFragment node.
2005
- * @param {object} [opt] - Options.
2006
- * @returns {boolean} True if matches, otherwise false.
2007
- */
2008
- _matchSelectorForShadowRoot = (ast, node, opt = {}) => {
2009
- const { name: astName } = ast;
2010
- if (KEYS_LOGICAL.has(astName)) {
2011
- opt.isShadowRoot = true;
2012
- return this._matchPseudoClassSelector(ast, node, opt);
2013
- }
2014
- if (astName === 'host' || astName === 'host-context') {
2015
- const matches = this._evaluateShadowHost(ast, node, opt);
2016
- if (matches) {
2017
- this.#verifyShadowHost = true;
2018
- return true;
2019
- }
2020
- }
2021
- return false;
2022
- };
2023
-
2024
- /**
2025
- * Matches a selector.
2026
- * @private
2027
- * @param {object} ast - The AST.
2028
- * @param {object} node - The Document, DocumentFragment, or Element node.
2029
- * @param {object} opt - Options.
2030
- * @returns {boolean} True if matches, otherwise false.
2031
- */
2032
- _matchSelector = (ast, node, opt) => {
2033
- if (node.nodeType === ELEMENT_NODE) {
2034
- return this._matchSelectorForElement(ast, node, opt);
2035
- }
2036
- if (
2037
- this.#shadow &&
2038
- node.nodeType === DOCUMENT_FRAGMENT_NODE &&
2039
- ast.type === PS_CLASS_SELECTOR
2040
- ) {
2041
- return this._matchSelectorForShadowRoot(ast, node, opt);
2042
- }
2043
- return false;
2044
- };
2045
-
2046
- /**
2047
- * Matches leaves.
2048
- * @private
2049
- * @param {Array.<object>} leaves - The AST leaves.
2050
- * @param {object} node - The node.
2051
- * @param {object} opt - Options.
2052
- * @returns {boolean} The result.
2053
- */
2054
- _matchLeaves = (leaves, node, opt) => {
2055
- if (!this.#invalidateResults) {
2056
- this.#invalidateResults = new WeakMap();
2057
- }
2058
- const results = this.#invalidate ? this.#invalidateResults : this.#results;
2059
- let result = results.get(leaves);
2060
- if (result && result.has(node)) {
2061
- const { matched } = result.get(node);
2062
- return matched;
2063
- }
2064
- let cacheable = true;
2065
- if (node.nodeType === ELEMENT_NODE && KEYS_FORM.has(node.localName)) {
2066
- cacheable = false;
2067
- }
2068
- let bool;
2069
- const l = leaves.length;
2070
- for (let i = 0; i < l; i++) {
2071
- const leaf = leaves[i];
2072
- switch (leaf.type) {
2073
- case ATTR_SELECTOR:
2074
- case ID_SELECTOR: {
2075
- cacheable = false;
2076
- break;
2077
- }
2078
- case PS_CLASS_SELECTOR: {
2079
- if (KEYS_PS_UNCACHE.has(leaf.name)) {
2080
- cacheable = false;
2081
- }
2082
- break;
2083
- }
2084
- default: {
2085
- // No action needed for other types.
2086
- }
2087
- }
2088
- bool = this._matchSelector(leaf, node, opt);
2089
- if (!bool) {
2090
- break;
2091
- }
2092
- }
2093
- if (cacheable) {
2094
- if (!result) {
2095
- result = new WeakMap();
2096
- }
2097
- result.set(node, {
2098
- matched: bool
2099
- });
2100
- results.set(leaves, result);
2101
- }
2102
- return bool;
2103
- };
2104
-
2105
- /**
2106
- * Returns a cached slice of the leaves array (excluding the first item).
2107
- * @private
2108
- * @param {Array.<object>} leaves - The original AST leaves array.
2109
- * @returns {Array.<object>} The filtered leaves.
2110
- */
2111
- _getFilterLeaves = leaves => {
2112
- if (!this.#filterLeavesCache) {
2113
- this.#filterLeavesCache = new WeakMap();
2114
- }
2115
- if (this.#filterLeavesCache.has(leaves)) {
2116
- return this.#filterLeavesCache.get(leaves);
2117
- }
2118
- const filterLeaves = leaves.slice(1);
2119
- this.#filterLeavesCache.set(leaves, filterLeaves);
2120
- return filterLeaves;
2121
- };
2122
-
2123
- /**
2124
- * Traverses all descendant nodes and collects matches.
2125
- * @private
2126
- * @param {object} baseNode - The base Element node or Element.shadowRoot.
2127
- * @param {Array.<object>} leaves - The AST leaves.
2128
- * @param {object} opt - Options.
2129
- * @returns {Set.<object>} A collection of matched nodes.
2130
- */
2131
- _traverseAllDescendants = (baseNode, leaves, opt) => {
2132
- const walker = this._createTreeWalker(baseNode);
2133
- traverseNode(baseNode, walker);
2134
- let currentNode = walker.firstChild();
2135
- const nodes = new Set();
2136
- while (currentNode) {
2137
- if (this._matchLeaves(leaves, currentNode, opt)) {
2138
- nodes.add(currentNode);
2139
- }
2140
- currentNode = walker.nextNode();
2141
- }
2142
- return nodes;
2143
- };
59
+ */
60
+ setup(selector, node, opt = {}) {
61
+ super.setup(selector, node, opt);
62
+ this.#ast = null;
63
+ this.#nodes = null;
64
+ this.#scoped =
65
+ this.node !== this.root && this.node.nodeType === ELEMENT_NODE;
66
+ this.#selector = selector;
67
+ this.#selectorAST = null;
68
+ this.#nodeWalker = null;
69
+ this.#rootWalker = null;
70
+ return this;
71
+ }
2144
72
 
2145
73
  /**
2146
- * Finds descendant nodes.
74
+ * Processes selector branches into the internal AST structure.
2147
75
  * @private
2148
- * @param {Array.<object>} leaves - The AST leaves.
2149
- * @param {object} baseNode - The base Element node or Element.shadowRoot.
2150
- * @param {object} opt - Options.
2151
- * @returns {Set.<object>} A collection of matched nodes.
76
+ * @param {Array.<object>} branches - The selector branches to process.
77
+ * @param {string} selector - The CSS selector string.
78
+ * @returns {object} Object containing ast and descendant flags.
2152
79
  */
2153
- _findDescendantNodes = (leaves, baseNode, opt) => {
2154
- const [leaf] = leaves;
2155
- const filterLeaves = this._getFilterLeaves(leaves);
2156
- const { type: leafType } = leaf;
2157
- switch (leafType) {
2158
- case ID_SELECTOR: {
2159
- const canUseGetElementById =
2160
- !this.#shadow &&
2161
- baseNode.nodeType === ELEMENT_NODE &&
2162
- this.#root.nodeType !== ELEMENT_NODE;
2163
- if (canUseGetElementById) {
2164
- const leafName = unescapeSelector(leaf.name);
2165
- const nodes = new Set();
2166
- const foundNode = this.#root.getElementById(leafName);
2167
- if (
2168
- foundNode &&
2169
- foundNode !== baseNode &&
2170
- baseNode.contains(foundNode)
2171
- ) {
2172
- const isCompoundSelector = filterLeaves.length > 0;
2173
- if (
2174
- !isCompoundSelector ||
2175
- this._matchLeaves(filterLeaves, foundNode, opt)
2176
- ) {
2177
- nodes.add(foundNode);
80
+ _processSelectorBranches = (branches, selector) => {
81
+ let descendant = false;
82
+ const ast = [];
83
+ for (const items of branches) {
84
+ const branch = [];
85
+ let prevType = null;
86
+ const itemsLen = items.length;
87
+ if (itemsLen) {
88
+ const leaves = new Set();
89
+ for (let j = 0; j < itemsLen; j++) {
90
+ const item = items[j];
91
+ const isLast = j === itemsLen - 1;
92
+ if (isInvalidCombinator(item.type, prevType, isLast)) {
93
+ const msg = `Invalid selector ${selector}`;
94
+ this.onError(generateException(msg, SYNTAX_ERR, this.window));
95
+ return { ast: [], descendant: false, invalidate: false };
96
+ }
97
+ if (item.type === COMBINATOR) {
98
+ if (item.name === ' ' || item.name === '>') {
99
+ descendant = true;
2178
100
  }
101
+ branch.push({ combo: item, leaves: sortAST(leaves) });
102
+ leaves.clear();
103
+ } else {
104
+ if (item.name && typeof item.name === 'string') {
105
+ const unescapedName = unescapeSelector(item.name);
106
+ if (unescapedName !== item.name) {
107
+ item.name = unescapedName;
108
+ }
109
+ if (/[|:]/.test(unescapedName)) {
110
+ item.namespace = true;
111
+ }
112
+ }
113
+ leaves.add(item);
114
+ }
115
+ prevType = item.type;
116
+ if (isLast) {
117
+ branch.push({ combo: null, leaves: sortAST(leaves) });
118
+ leaves.clear();
2179
119
  }
2180
- return nodes;
2181
120
  }
2182
- // Fallback to default traversal if fast path is not applicable.
2183
- return this._traverseAllDescendants(baseNode, leaves, opt);
2184
- }
2185
- case PS_ELEMENT_SELECTOR: {
2186
- const leafName = unescapeSelector(leaf.name);
2187
- matchPseudoElementSelector(leafName, leafType, opt);
2188
- return new Set();
2189
- }
2190
- default: {
2191
- return this._traverseAllDescendants(baseNode, leaves, opt);
2192
121
  }
122
+ ast.push({ branch, dir: null, filtered: false, find: false });
2193
123
  }
124
+ return { ast, descendant };
2194
125
  };
2195
126
 
2196
127
  /**
2197
- * Collects combinator matches into an array.
128
+ * Corresponds AST and DOM nodes for the given selector.
2198
129
  * @private
2199
- * @param {object} twig - The twig object.
2200
- * @param {object} node - The Element node.
2201
- * @param {object} [opt] - Options.
2202
- * @param {string} [opt.dir] - The find direction.
2203
- * @param {Array.<object>} matched - The collector array.
2204
- * @returns {Array.<object>} The collector array.
130
+ * @param {string} selector - The CSS selector string.
131
+ * @returns {Array} An array containing the AST and empty nodes array.
2205
132
  */
2206
- _collectCombinatorMatches = (twig, node, opt = {}, matched = []) => {
2207
- const {
2208
- combo: { name: comboName },
2209
- leaves
2210
- } = twig;
2211
- const { dir } = opt;
2212
- switch (comboName) {
2213
- case '+': {
2214
- const refNode =
2215
- dir === DIR_NEXT
2216
- ? node.nextElementSibling
2217
- : node.previousElementSibling;
2218
- if (refNode && this._matchLeaves(leaves, refNode, opt)) {
2219
- matched.push(refNode);
2220
- }
2221
- break;
133
+ _correspond = selector => {
134
+ const nodes = [];
135
+ let descendant = false;
136
+ this.invalidate = false;
137
+ let ast;
138
+ if (this.documentCache.has(this.document)) {
139
+ const cachedItem = this.documentCache.get(this.document);
140
+ if (cachedItem && cachedItem.has(`${selector}`)) {
141
+ const item = cachedItem.get(`${selector}`);
142
+ ast = item.ast;
143
+ descendant = item.descendant;
144
+ this.invalidate = item.invalidate;
145
+ this.#selectorAST = item.selectorAST;
2222
146
  }
2223
- case '~': {
2224
- let refNode =
2225
- dir === DIR_NEXT
2226
- ? node.nextElementSibling
2227
- : node.previousElementSibling;
2228
- while (refNode) {
2229
- if (this._matchLeaves(leaves, refNode, opt)) {
2230
- matched.push(refNode);
2231
- }
2232
- refNode =
2233
- dir === DIR_NEXT
2234
- ? refNode.nextElementSibling
2235
- : refNode.previousElementSibling;
2236
- }
2237
- break;
147
+ }
148
+ if (ast) {
149
+ const l = ast.length;
150
+ for (let i = 0; i < l; i++) {
151
+ ast[i].dir = null;
152
+ ast[i].filtered = false;
153
+ ast[i].find = false;
154
+ nodes[i] = [];
2238
155
  }
2239
- case '>': {
2240
- if (dir === DIR_NEXT) {
2241
- let refNode = node.firstElementChild;
2242
- while (refNode) {
2243
- if (this._matchLeaves(leaves, refNode, opt)) {
2244
- matched.push(refNode);
2245
- }
2246
- refNode = refNode.nextElementSibling;
2247
- }
2248
- } else {
2249
- const { parentNode } = node;
2250
- if (parentNode && this._matchLeaves(leaves, parentNode, opt)) {
2251
- matched.push(parentNode);
2252
- }
2253
- }
2254
- break;
156
+ } else {
157
+ this.#selectorAST = parseSelector(selector);
158
+ const { branches, info } = walkAST(
159
+ this.#selectorAST,
160
+ true,
161
+ createHasValidator(this.window)
162
+ );
163
+ const {
164
+ hasHasPseudoFunc,
165
+ hasLogicalPseudoFunc,
166
+ hasNthChildOfSelector,
167
+ hasStatePseudoClass,
168
+ hasUnsupportedPseudoClass
169
+ } = info;
170
+ this.invalidate =
171
+ hasHasPseudoFunc ||
172
+ hasStatePseudoClass ||
173
+ hasUnsupportedPseudoClass ||
174
+ !!(hasLogicalPseudoFunc && hasNthChildOfSelector);
175
+ const processed = this._processSelectorBranches(branches, selector);
176
+ ast = processed.ast;
177
+ descendant = processed.descendant;
178
+ let cachedItem;
179
+ if (this.documentCache.has(this.document)) {
180
+ cachedItem = this.documentCache.get(this.document);
181
+ } else {
182
+ cachedItem = new Map();
2255
183
  }
2256
- case ' ':
2257
- default: {
2258
- if (dir === DIR_NEXT) {
2259
- for (const refNode of this._findDescendantNodes(leaves, node, opt)) {
2260
- matched.push(refNode);
2261
- }
2262
- } else {
2263
- const ancestors = [];
2264
- let refNode = node.parentNode;
2265
- while (refNode) {
2266
- if (this._matchLeaves(leaves, refNode, opt)) {
2267
- ancestors.push(refNode);
2268
- }
2269
- refNode = refNode.parentNode;
2270
- }
2271
- if (ancestors.length) {
2272
- matched.push(...ancestors.reverse());
2273
- }
2274
- }
184
+ cachedItem.set(`${selector}`, {
185
+ ast,
186
+ descendant,
187
+ invalidate: this.invalidate,
188
+ selectorAST: this.#selectorAST
189
+ });
190
+ this.documentCache.set(this.document, cachedItem);
191
+ for (let i = 0; i < ast.length; i++) {
192
+ nodes[i] = [];
2275
193
  }
2276
194
  }
2277
- return matched;
195
+ return [ast, nodes];
2278
196
  };
2279
197
 
2280
198
  /**
2281
- * Matches a combinator.
2282
- * @private
2283
- * @param {object} twig - The twig object.
2284
- * @param {object} node - The Element node.
2285
- * @param {object} opt - Options.
2286
- * @returns {Set.<object>} A collection of matched nodes.
2287
- */
2288
- _matchCombinator = (twig, node, opt) =>
2289
- new Set(this._collectCombinatorMatches(twig, node, opt));
2290
-
2291
- /**
2292
- * Traverses with a TreeWalker and collects nodes matching the leaves.
199
+ * Traverses and collects nodes matching leaves.
2293
200
  * @private
2294
- * @param {TreeWalker} walker - The TreeWalker instance to use.
2295
- * @param {Array} leaves - The AST leaves to match against.
2296
- * @param {object} [opt] - Traversal options.
2297
- * @param {Node} [opt.boundaryNode] - The node to stop traversal at.
2298
- * @param {boolean} [opt.force] - Force traversal to the next node.
2299
- * @param {Node} [opt.startNode] - The node to start traversal from.
2300
- * @param {string} [opt.targetType] - The type of target ('all' or 'first').
2301
- * @returns {Array.<Node>} An array of matched nodes.
201
+ * @param {object} walker - The TreeWalker instance.
202
+ * @param {Array.<object>} leaves - The AST leaves to match.
203
+ * @param {object} [opt] - Options for traversal.
204
+ * @returns {Array.<object>} An array of collected nodes.
2302
205
  */
2303
206
  _traverseAndCollectNodes = (walker, leaves, opt = {}) => {
2304
207
  const { boundaryNode, force, startNode, targetType } = opt;
@@ -2310,14 +213,10 @@ export class Finder {
2310
213
  // Adjust starting node.
2311
214
  if (currentNode.nodeType !== ELEMENT_NODE) {
2312
215
  currentNode = walker.nextNode();
2313
- } else if (currentNode === startNode && currentNode !== this.#root) {
216
+ } else if (currentNode === startNode && currentNode !== this.root) {
2314
217
  currentNode = walker.nextNode();
2315
218
  }
2316
- const matchOpt = {
2317
- warn: this.#warn
2318
- };
2319
219
  while (currentNode) {
2320
- // Stop when we reach the boundary.
2321
220
  if (boundaryNode) {
2322
221
  if (currentNode === boundaryNode) {
2323
222
  break;
@@ -2329,11 +228,10 @@ export class Finder {
2329
228
  }
2330
229
  }
2331
230
  if (
2332
- this._matchLeaves(leaves, currentNode, matchOpt) &&
2333
- currentNode !== this.#node
231
+ this.matchLeaves(leaves, currentNode, this.matchOpts) &&
232
+ currentNode !== this.node
2334
233
  ) {
2335
234
  collectedNodes.push(currentNode);
2336
- // Stop after the first match if not collecting all.
2337
235
  if (targetType !== TARGET_ALL) {
2338
236
  break;
2339
237
  }
@@ -2344,47 +242,44 @@ export class Finder {
2344
242
  };
2345
243
 
2346
244
  /**
2347
- * Finds matched node(s) preceding this.#node.
245
+ * Finds matching nodes preceding the current node.
2348
246
  * @private
2349
- * @param {Array.<object>} leaves - The AST leaves.
2350
- * @param {object} node - The node to start from.
2351
- * @param {object} [opt] - Options.
2352
- * @param {boolean} [opt.force] - If true, traverses only to the next node.
2353
- * @param {string} [opt.targetType] - The target type.
2354
- * @returns {Array.<object>} A collection of matched nodes.
247
+ * @param {Array.<object>} leaves - The AST leaves to match.
248
+ * @param {object} node - The starting node.
249
+ * @param {object} [opt] - Options for finding.
250
+ * @returns {Array.<object>} An array of matched nodes.
2355
251
  */
2356
252
  _findPrecede = (leaves, node, opt = {}) => {
2357
253
  const { force, targetType } = opt;
2358
254
  if (!this.#rootWalker) {
2359
- this.#rootWalker = this._createTreeWalker(this.#root);
255
+ this.#rootWalker = this.createTreeWalker(this.root);
2360
256
  }
2361
257
  return this._traverseAndCollectNodes(this.#rootWalker, leaves, {
2362
258
  force,
2363
259
  targetType,
2364
- boundaryNode: this.#node,
260
+ boundaryNode: this.node,
2365
261
  startNode: node
2366
262
  });
2367
263
  };
2368
264
 
2369
265
  /**
2370
- * Finds matched node(s) in #nodeWalker.
266
+ * Finds matching nodes using TreeWalker.
2371
267
  * @private
2372
268
  * @param {Array.<object>} leaves - The AST leaves.
2373
- * @param {object} node - The node to start from.
2374
- * @param {object} [opt] - Options.
2375
- * @param {boolean} [opt.precede] - If true, finds preceding nodes.
2376
- * @returns {Array.<object>} A collection of matched nodes.
269
+ * @param {object} node - The starting node.
270
+ * @param {object} [opt] - Traversal options.
271
+ * @returns {Array.<object>} An array of matched nodes.
2377
272
  */
2378
273
  _findNodeWalker = (leaves, node, opt = {}) => {
2379
274
  const { precede, ...traversalOpts } = opt;
2380
275
  if (precede) {
2381
- const precedeNodes = this._findPrecede(leaves, this.#root, opt);
276
+ const precedeNodes = this._findPrecede(leaves, this.root, opt);
2382
277
  if (precedeNodes.length) {
2383
278
  return precedeNodes;
2384
279
  }
2385
280
  }
2386
281
  if (!this.#nodeWalker) {
2387
- this.#nodeWalker = this._createTreeWalker(this.#node);
282
+ this.#nodeWalker = this.createTreeWalker(this.node);
2388
283
  }
2389
284
  return this._traverseAndCollectNodes(this.#nodeWalker, leaves, {
2390
285
  ...traversalOpts,
@@ -2393,40 +288,38 @@ export class Finder {
2393
288
  };
2394
289
 
2395
290
  /**
2396
- * Matches the node itself.
291
+ * Matches the current node itself against leaves.
2397
292
  * @private
2398
- * @param {Array} leaves - The AST leaves.
2399
- * @returns {Array} An array containing [nodes, filtered, pseudoElement].
293
+ * @param {Array.<object>} leaves - The AST leaves.
294
+ * @returns {Array} Array with nodes, match boolean, and pseudo-elements.
2400
295
  */
2401
296
  _matchSelf = leaves => {
2402
- const matched = this._matchLeaves(leaves, this.#node, {
2403
- check: this.#check,
2404
- warn: this.#warn
297
+ const matched = this.matchLeaves(leaves, this.node, {
298
+ check: this.check,
299
+ warn: this.warn
2405
300
  });
2406
- const nodes = matched ? [this.#node] : [];
2407
- return [nodes, matched, this.#pseudoElement];
301
+ const nodes = matched ? [this.node] : [];
302
+ return [nodes, matched, this.pseudoElements];
2408
303
  };
2409
304
 
2410
305
  /**
2411
- * Finds lineal nodes (self and ancestors).
306
+ * Finds lineal matching nodes (self and ancestors).
2412
307
  * @private
2413
- * @param {Array} leaves - The AST leaves.
2414
- * @param {object} [opt] - Options.
2415
- * @param {boolean} [opt.complex] - If true, the selector is complex.
2416
- * @returns {Array} An array containing [nodes, filtered].
308
+ * @param {Array.<object>} leaves - The AST leaves.
309
+ * @param {object} [opt] - Options like complex flag.
310
+ * @returns {Array} Array containing nodes and filtered boolean.
2417
311
  */
2418
312
  _findLineal = (leaves, opt = {}) => {
2419
313
  const { complex } = opt;
2420
314
  const nodes = [];
2421
- const matchOpts = { warn: this.#warn };
2422
- const selfMatched = this._matchLeaves(leaves, this.#node, matchOpts);
315
+ const selfMatched = this.matchLeaves(leaves, this.node, this.matchOpts);
2423
316
  if (selfMatched) {
2424
- nodes.push(this.#node);
317
+ nodes.push(this.node);
2425
318
  }
2426
319
  if (!selfMatched || complex) {
2427
- let currentNode = this.#node.parentNode;
320
+ let currentNode = this.node.parentNode;
2428
321
  while (currentNode) {
2429
- if (this._matchLeaves(leaves, currentNode, matchOpts)) {
322
+ if (this.matchLeaves(leaves, currentNode, this.matchOpts)) {
2430
323
  nodes.push(currentNode);
2431
324
  }
2432
325
  currentNode = currentNode.parentNode;
@@ -2437,157 +330,202 @@ export class Finder {
2437
330
  };
2438
331
 
2439
332
  /**
2440
- * Finds entry nodes for pseudo-element selectors.
333
+ * Finds entry nodes for pseudo-elements.
2441
334
  * @private
2442
- * @param {object} leaf - The pseudo-element leaf from the AST.
2443
- * @param {Array.<object>} filterLeaves - Leaves for compound selectors.
2444
- * @param {string} targetType - The type of target to find.
2445
- * @returns {object} The result { nodes, filtered, pending }.
335
+ * @param {object} leaf - The AST leaf.
336
+ * @param {Array.<object>} filterLeaves - Leaves for filtering.
337
+ * @param {string} targetType - The target type.
338
+ * @returns {object} Object with nodes, filtered, and pending flags.
2446
339
  */
2447
340
  _findEntryNodesForPseudoElement = (leaf, filterLeaves, targetType) => {
2448
- let nodes = [];
2449
- let filtered = false;
2450
- if (targetType === TARGET_SELF && this.#check) {
341
+ if (targetType === TARGET_SELF && this.check) {
2451
342
  const css = generateCSS(leaf);
2452
- this.#pseudoElement.push(css);
343
+ this.pseudoElements.push(css);
2453
344
  if (filterLeaves.length) {
2454
- [nodes, filtered] = this._matchSelf(filterLeaves);
2455
- } else {
2456
- nodes.push(this.#node);
2457
- filtered = true;
345
+ const [nodes, filtered] = this._matchSelf(filterLeaves);
346
+ return { nodes, filtered, pending: false };
2458
347
  }
2459
- } else {
2460
- matchPseudoElementSelector(leaf.name, leaf.type, { warn: this.#warn });
348
+ return { nodes: [this.node], filtered: true, pending: false };
2461
349
  }
2462
- return { nodes, filtered, pending: false };
350
+ matchPseudoElementSelector(leaf.name, leaf.type, this.matchOpts);
351
+ return { nodes: [], filtered: false, pending: false };
2463
352
  };
2464
353
 
2465
354
  /**
2466
- * Finds entry nodes for ID selectors.
355
+ * Finds entry nodes using ID selector strategy.
2467
356
  * @private
2468
- * @param {object} twig - The current twig from the AST branch.
2469
- * @param {string} targetType - The type of target to find.
2470
- * @param {object} [opt] - Options.
2471
- * @param {boolean} [opt.complex] - If true, the selector is complex.
2472
- * @param {boolean} [opt.precede] - If true, finds preceding nodes.
2473
- * @returns {object} The result { nodes, filtered, pending }.
357
+ * @param {object} twig - The twig object containing leaves.
358
+ * @param {string} targetType - The target type.
359
+ * @param {object} [opt] - Strategy options.
360
+ * @returns {object} Result object with nodes and flags.
2474
361
  */
2475
362
  _findEntryNodesForId = (twig, targetType, opt = {}) => {
2476
363
  const { leaves } = twig;
2477
- const [leaf] = leaves;
2478
- const filterLeaves = this._getFilterLeaves(leaves);
364
+ const filterLeaves = this.getFilterLeaves(leaves);
2479
365
  const { complex, precede } = opt;
2480
- let nodes = [];
2481
- let filtered = false;
2482
366
  if (targetType === TARGET_SELF) {
2483
- [nodes, filtered] = this._matchSelf(leaves);
367
+ const [nodes, filtered] = this._matchSelf(leaves);
368
+ return { nodes, filtered, pending: false };
2484
369
  } else if (targetType === TARGET_LINEAL) {
2485
- [nodes, filtered] = this._findLineal(leaves, { complex });
370
+ const [nodes, filtered] = this._findLineal(leaves, { complex });
371
+ return { nodes, filtered, pending: false };
2486
372
  } else if (
2487
373
  targetType === TARGET_FIRST &&
2488
- this.#root.nodeType !== ELEMENT_NODE
374
+ this.root.nodeType !== ELEMENT_NODE
2489
375
  ) {
2490
- const node = this.#root.getElementById(leaf.name);
376
+ const [leaf] = leaves;
377
+ const node = this.root.getElementById(leaf.name);
378
+ const nodes = [];
2491
379
  if (node) {
2492
380
  if (filterLeaves.length) {
2493
- if (this._matchLeaves(filterLeaves, node, { warn: this.#warn })) {
381
+ if (this.matchLeaves(filterLeaves, node, this.matchOpts)) {
2494
382
  nodes.push(node);
2495
- filtered = true;
2496
383
  }
2497
384
  } else {
2498
385
  nodes.push(node);
2499
- filtered = true;
2500
386
  }
2501
387
  }
2502
- } else {
2503
- nodes = this._findNodeWalker(leaves, this.#node, { precede, targetType });
2504
- filtered = nodes.length > 0;
388
+ return { nodes, filtered: nodes.length > 0, pending: false };
2505
389
  }
2506
- return { nodes, filtered, pending: false };
390
+ const nodes = this._findNodeWalker(leaves, this.node, {
391
+ precede,
392
+ targetType
393
+ });
394
+ return { nodes, filtered: nodes.length > 0, pending: false };
2507
395
  };
2508
396
 
2509
397
  /**
2510
- * Finds entry nodes for class selectors.
398
+ * Finds entry nodes using class selector strategy.
2511
399
  * @private
2512
- * @param {Array.<object>} leaves - The AST leaves for the selector.
2513
- * @param {string} targetType - The type of target to find.
2514
- * @param {object} [opt] - Options.
2515
- * @param {boolean} [opt.complex] - If true, the selector is complex.
2516
- * @param {boolean} [opt.precede] - If true, finds preceding nodes.
2517
- * @returns {object} The result { nodes, filtered, pending }.
400
+ * @param {Array.<object>} leaves - The AST leaves.
401
+ * @param {string} targetType - The target type.
402
+ * @param {object} [opt] - Strategy options.
403
+ * @returns {object} Result object with nodes and flags.
2518
404
  */
2519
405
  _findEntryNodesForClass = (leaves, targetType, opt = {}) => {
2520
406
  const { complex, precede } = opt;
2521
- let nodes = [];
2522
- let filtered = false;
2523
407
  if (targetType === TARGET_SELF) {
2524
- [nodes, filtered] = this._matchSelf(leaves);
408
+ const [nodes, filtered] = this._matchSelf(leaves);
409
+ return { nodes, filtered, pending: false };
2525
410
  } else if (targetType === TARGET_LINEAL) {
2526
- [nodes, filtered] = this._findLineal(leaves, { complex });
2527
- } else {
2528
- nodes = this._findNodeWalker(leaves, this.#node, { precede, targetType });
2529
- filtered = nodes.length > 0;
411
+ const [nodes, filtered] = this._findLineal(leaves, { complex });
412
+ return { nodes, filtered, pending: false };
413
+ } else if (
414
+ targetType !== TARGET_FIRST &&
415
+ !precede &&
416
+ typeof this.node.getElementsByClassName === 'function'
417
+ ) {
418
+ this.matchLeaves(leaves, this.node, this.matchOpts);
419
+ const [leaf] = leaves;
420
+ const className = unescapeSelector(leaf.name);
421
+ const collection = this.node.getElementsByClassName(className);
422
+ const len = collection.length;
423
+ const filterLeaves = this.getFilterLeaves(leaves);
424
+ const hasFilter = filterLeaves.length > 0;
425
+ const nodeArray = [];
426
+ for (let i = 0; i < len; i++) {
427
+ const currentNode = collection[i];
428
+ if (
429
+ !hasFilter ||
430
+ this.matchLeaves(filterLeaves, currentNode, this.matchOpts)
431
+ ) {
432
+ nodeArray.push(currentNode);
433
+ }
434
+ }
435
+ return {
436
+ nodes: nodeArray,
437
+ filtered: nodeArray.length > 0,
438
+ pending: false
439
+ };
2530
440
  }
2531
- return { nodes, filtered, pending: false };
441
+ const nodes = this._findNodeWalker(leaves, this.node, {
442
+ precede,
443
+ targetType
444
+ });
445
+ return { nodes, filtered: nodes.length > 0, pending: false };
2532
446
  };
2533
447
 
2534
448
  /**
2535
- * Finds entry nodes for type selectors.
449
+ * Finds entry nodes using type selector strategy.
2536
450
  * @private
2537
- * @param {Array.<object>} leaves - The AST leaves for the selector.
2538
- * @param {string} targetType - The type of target to find.
2539
- * @param {object} [opt] - Options.
2540
- * @param {boolean} [opt.complex] - If true, the selector is complex.
2541
- * @param {boolean} [opt.precede] - If true, finds preceding nodes.
2542
- * @returns {object} The result { nodes, filtered, pending }.
451
+ * @param {Array.<object>} leaves - The AST leaves.
452
+ * @param {string} targetType - The target type.
453
+ * @param {object} [opt] - Strategy options.
454
+ * @returns {object} Result object with nodes and flags.
2543
455
  */
2544
456
  _findEntryNodesForType = (leaves, targetType, opt = {}) => {
2545
457
  const { complex, precede } = opt;
2546
- let nodes = [];
2547
- let filtered = false;
2548
458
  if (targetType === TARGET_SELF) {
2549
- [nodes, filtered] = this._matchSelf(leaves);
459
+ const [nodes, filtered] = this._matchSelf(leaves);
460
+ return { nodes, filtered, pending: false };
2550
461
  } else if (targetType === TARGET_LINEAL) {
2551
- [nodes, filtered] = this._findLineal(leaves, { complex });
2552
- } else {
2553
- nodes = this._findNodeWalker(leaves, this.#node, { precede, targetType });
2554
- filtered = nodes.length > 0;
462
+ const [nodes, filtered] = this._findLineal(leaves, { complex });
463
+ return { nodes, filtered, pending: false };
464
+ }
465
+ const [leaf] = leaves;
466
+ const tagName = unescapeSelector(leaf.name);
467
+ if (
468
+ targetType !== TARGET_FIRST &&
469
+ !precede &&
470
+ this.document.contentType === 'text/html' &&
471
+ typeof this.node.getElementsByTagName === 'function' &&
472
+ tagName.indexOf('|') === -1
473
+ ) {
474
+ this.matchLeaves(leaves, this.node, this.matchOpts);
475
+ const collection = this.node.getElementsByTagName(tagName);
476
+ const len = collection.length;
477
+ const filterLeaves = this.getFilterLeaves(leaves);
478
+ const hasFilter = filterLeaves.length > 0;
479
+ const nodeArray = [];
480
+ for (let i = 0; i < len; i++) {
481
+ const currentNode = collection[i];
482
+ if (
483
+ !hasFilter ||
484
+ this.matchLeaves(filterLeaves, currentNode, this.matchOpts)
485
+ ) {
486
+ nodeArray.push(currentNode);
487
+ }
488
+ }
489
+ return {
490
+ nodes: nodeArray,
491
+ filtered: nodeArray.length > 0,
492
+ pending: false
493
+ };
2555
494
  }
2556
- return { nodes, filtered, pending: false };
495
+ const nodes = this._findNodeWalker(leaves, this.node, {
496
+ precede,
497
+ targetType
498
+ });
499
+ return { nodes, filtered: nodes.length > 0, pending: false };
2557
500
  };
2558
501
 
2559
502
  /**
2560
- * Finds entry nodes for other selector types (default case).
503
+ * Finds entry nodes for other selector types.
2561
504
  * @private
2562
- * @param {object} twig - The current twig from the AST branch.
2563
- * @param {string} targetType - The type of target to find.
2564
- * @param {object} [opt] - Options.
2565
- * @param {boolean} [opt.complex] - If true, the selector is complex.
2566
- * @param {boolean} [opt.precede] - If true, finds preceding nodes.
2567
- * @returns {object} The result { nodes, filtered, pending }.
505
+ * @param {object} twig - The twig object containing leaves.
506
+ * @param {string} targetType - The target type.
507
+ * @param {object} [opt] - Strategy options.
508
+ * @returns {object} Result object with nodes and flags.
2568
509
  */
2569
510
  _findEntryNodesForOther = (twig, targetType, opt = {}) => {
2570
511
  const { leaves } = twig;
2571
512
  const [leaf] = leaves;
2572
- const filterLeaves = this._getFilterLeaves(leaves);
513
+ const filterLeaves = this.getFilterLeaves(leaves);
2573
514
  const { complex, precede } = opt;
2574
- let nodes = [];
2575
- let filtered = false;
2576
- let pending = false;
2577
515
  if (targetType !== TARGET_LINEAL && /host(?:-context)?/.test(leaf.name)) {
2578
516
  let shadowRoot = null;
2579
517
  if (
2580
- this.#shadow &&
2581
- this.#node.nodeType === DOCUMENT_FRAGMENT_NODE &&
2582
- this._evaluateShadowHost(leaf, this.#node)
518
+ this.shadow &&
519
+ this.node.nodeType === DOCUMENT_FRAGMENT_NODE &&
520
+ this.evaluateShadowHost(leaf, this.node)
2583
521
  ) {
2584
- shadowRoot = this.#node;
522
+ shadowRoot = this.node;
2585
523
  } else if (
2586
524
  filterLeaves.length &&
2587
- this.#node.nodeType === ELEMENT_NODE &&
2588
- this._evaluateShadowHost(leaf, this.#node.shadowRoot)
525
+ this.node.nodeType === ELEMENT_NODE &&
526
+ this.evaluateShadowHost(leaf, this.node.shadowRoot)
2589
527
  ) {
2590
- shadowRoot = this.#node.shadowRoot;
528
+ shadowRoot = this.node.shadowRoot;
2591
529
  }
2592
530
  if (shadowRoot) {
2593
531
  let bool = true;
@@ -2597,11 +535,11 @@ export class Finder {
2597
535
  switch (filterLeaf.name) {
2598
536
  case 'host':
2599
537
  case 'host-context': {
2600
- bool = this._evaluateShadowHost(filterLeaf, shadowRoot);
538
+ bool = this.evaluateShadowHost(filterLeaf, shadowRoot);
2601
539
  break;
2602
540
  }
2603
541
  case 'has': {
2604
- bool = this._matchPseudoClassSelector(filterLeaf, shadowRoot, {});
542
+ bool = this.matchPseudoClassSelector(filterLeaf, shadowRoot, {});
2605
543
  break;
2606
544
  }
2607
545
  default: {
@@ -2612,43 +550,45 @@ export class Finder {
2612
550
  break;
2613
551
  }
2614
552
  }
553
+ const nodes = [];
2615
554
  if (bool) {
2616
555
  nodes.push(shadowRoot);
2617
- filtered = true;
2618
556
  }
557
+ return { nodes, filtered: nodes.length > 0, pending: false };
2619
558
  }
2620
559
  } else if (targetType === TARGET_SELF) {
2621
- [nodes, filtered] = this._matchSelf(leaves);
560
+ const [nodes, filtered] = this._matchSelf(leaves);
561
+ return { nodes, filtered, pending: false };
2622
562
  } else if (targetType === TARGET_LINEAL) {
2623
- [nodes, filtered] = this._findLineal(leaves, { complex });
563
+ const [nodes, filtered] = this._findLineal(leaves, { complex });
564
+ return { nodes, filtered, pending: false };
2624
565
  } else if (targetType === TARGET_FIRST) {
2625
- nodes = this._findNodeWalker(leaves, this.#node, { precede, targetType });
2626
- filtered = nodes.length > 0;
2627
- } else {
2628
- pending = true;
566
+ const nodes = this._findNodeWalker(leaves, this.node, {
567
+ precede,
568
+ targetType
569
+ });
570
+ return { nodes, filtered: nodes.length > 0, pending: false };
2629
571
  }
2630
- return { nodes, filtered, pending };
572
+ return { nodes: [], filtered: false, pending: true };
2631
573
  };
2632
574
 
2633
575
  /**
2634
- * Finds entry nodes.
576
+ * Finds entry nodes based on the selector type.
2635
577
  * @private
2636
- * @param {object} twig - The twig object.
578
+ * @param {object} twig - The twig object containing leaves.
2637
579
  * @param {string} targetType - The target type.
2638
- * @param {object} [opt] - Options.
2639
- * @param {boolean} [opt.complex] - If true, the selector is complex.
2640
- * @param {string} [opt.dir] - The find direction.
2641
- * @returns {object} An object with nodes and their state.
580
+ * @param {object} [opt] - Strategy options.
581
+ * @returns {object} Result object with nodes and flags.
2642
582
  */
2643
583
  _findEntryNodes = (twig, targetType, opt = {}) => {
2644
584
  const { leaves } = twig;
2645
585
  const [leaf] = leaves;
2646
- const filterLeaves = this._getFilterLeaves(leaves);
586
+ const filterLeaves = this.getFilterLeaves(leaves);
2647
587
  const { complex = false, dir = DIR_PREV } = opt;
2648
588
  const precede =
2649
589
  dir === DIR_NEXT &&
2650
- this.#node.nodeType === ELEMENT_NODE &&
2651
- this.#node !== this.#root;
590
+ this.node.nodeType === ELEMENT_NODE &&
591
+ this.node !== this.root;
2652
592
  let result;
2653
593
  switch (leaf.type) {
2654
594
  case PS_ELEMENT_SELECTOR: {
@@ -2696,11 +636,11 @@ export class Finder {
2696
636
  };
2697
637
 
2698
638
  /**
2699
- * Determines the direction and starting twig for a selector branch.
639
+ * Determines the traversal direction and starting twig.
2700
640
  * @private
2701
- * @param {Array.<object>} branch - The AST branch.
2702
- * @param {string} targetType - The type of target to find.
2703
- * @returns {object} An object with the direction and starting twig.
641
+ * @param {Array.<object>} branch - The selector branch.
642
+ * @param {string} targetType - The target type.
643
+ * @returns {object} Object containing dir and twig properties.
2704
644
  */
2705
645
  _determineTraversalStrategy = (branch, targetType) => {
2706
646
  const branchLen = branch.length;
@@ -2709,7 +649,6 @@ export class Finder {
2709
649
  if (branchLen === 1) {
2710
650
  return { dir: DIR_PREV, twig: firstTwig };
2711
651
  }
2712
- // Complex selector (branchLen > 1).
2713
652
  const {
2714
653
  leaves: [{ name: firstName, type: firstType }]
2715
654
  } = firstTwig;
@@ -2748,37 +687,34 @@ export class Finder {
2748
687
  return { dir: DIR_PREV, twig: lastTwig };
2749
688
  }
2750
689
  }
2751
- // Default strategy for complex selectors.
2752
690
  return { dir: DIR_NEXT, twig: firstTwig };
2753
691
  };
2754
692
 
2755
693
  /**
2756
- * Processes pending items not resolved with a direct strategy.
694
+ * Processes pending items to find matches.
2757
695
  * @private
2758
- * @param {Set.<Map>} pendingItems - The set of pending items.
696
+ * @param {Set.<Map>} pendingItems - Set of pending items to process.
697
+ * @returns {void}
2759
698
  */
2760
699
  _processPendingItems = pendingItems => {
2761
700
  if (!pendingItems.size) {
2762
701
  return;
2763
702
  }
2764
703
  if (!this.#rootWalker) {
2765
- this.#rootWalker = this._createTreeWalker(this.#root);
704
+ this.#rootWalker = this.createTreeWalker(this.root);
2766
705
  }
706
+ const node = this.#scoped ? this.node : this.root;
2767
707
  const walker = this.#rootWalker;
2768
- let node = this.#root;
2769
- if (this.#scoped) {
2770
- node = this.#node;
2771
- }
2772
708
  let nextNode = traverseNode(node, walker);
2773
709
  while (nextNode) {
2774
710
  const isWithinScope =
2775
- this.#node.nodeType !== ELEMENT_NODE ||
2776
- nextNode === this.#node ||
2777
- this.#node.contains(nextNode);
711
+ this.node.nodeType !== ELEMENT_NODE ||
712
+ nextNode === this.node ||
713
+ this.node.contains(nextNode);
2778
714
  if (isWithinScope) {
2779
715
  for (const pendingItem of pendingItems) {
2780
716
  const { leaves } = pendingItem.get('twig');
2781
- if (this._matchLeaves(leaves, nextNode, { warn: this.#warn })) {
717
+ if (this.matchLeaves(leaves, nextNode, this.matchOpts)) {
2782
718
  const index = pendingItem.get('index');
2783
719
  this.#ast[index].filtered = true;
2784
720
  this.#ast[index].find = true;
@@ -2793,10 +729,10 @@ export class Finder {
2793
729
  };
2794
730
 
2795
731
  /**
2796
- * Collects nodes.
732
+ * Collects all matching nodes into AST nodes array.
2797
733
  * @private
2798
734
  * @param {string} targetType - The target type.
2799
- * @returns {Array.<Array.<object>>} An array containing the AST and nodes.
735
+ * @returns {Array} Array containing the AST and nodes arrays.
2800
736
  */
2801
737
  _collectNodes = targetType => {
2802
738
  [this.#ast, this.#nodes] = this._correspond(this.#selector);
@@ -2855,35 +791,12 @@ export class Finder {
2855
791
  };
2856
792
 
2857
793
  /**
2858
- * Gets combined nodes.
2859
- * @private
2860
- * @param {object} twig - The twig object.
2861
- * @param {object} nodes - A collection of nodes.
2862
- * @param {string} dir - The direction.
2863
- * @returns {Array.<object>} A collection of matched nodes.
2864
- */
2865
- _getCombinedNodes = (twig, nodes, dir) => {
2866
- const arr = [];
2867
- for (const node of nodes) {
2868
- this._collectCombinatorMatches(
2869
- twig,
2870
- node,
2871
- { dir, warn: this.#warn },
2872
- arr
2873
- );
2874
- }
2875
- return arr;
2876
- };
2877
-
2878
- /**
2879
- * Matches a node in the 'next' direction.
794
+ * Matches a node in the next direction.
2880
795
  * @private
2881
- * @param {Array} branch - The branch.
2882
- * @param {Set.<object>} nodes - A collection of Element nodes.
2883
- * @param {object} [opt] - Options.
2884
- * @param {object} [opt.combo] - The combo object.
2885
- * @param {number} [opt.index] - The index.
2886
- * @returns {?object} The matched node.
796
+ * @param {Array.<object>} branch - The selector branch.
797
+ * @param {Array.<object>|Set.<object>} nodes - The starting nodes.
798
+ * @param {object} [opt] - Options containing combo and index.
799
+ * @returns {object|null} The matched node or null.
2887
800
  */
2888
801
  _matchNodeNext = (branch, nodes, opt = {}) => {
2889
802
  const { combo, index } = opt;
@@ -2892,63 +805,86 @@ export class Finder {
2892
805
  combo,
2893
806
  leaves
2894
807
  };
2895
- const nextNodes = this._getCombinedNodes(twig, nodes, DIR_NEXT);
2896
- if (nextNodes.length) {
2897
- if (index === branch.length - 1) {
2898
- if (nextNodes.length === 1) {
2899
- return nextNodes[0];
808
+ for (const node of nodes) {
809
+ for (const nextNode of this.yieldCombinatorMatches(twig, node, {
810
+ dir: DIR_NEXT
811
+ })) {
812
+ if (index === branch.length - 1) {
813
+ return nextNode;
814
+ }
815
+ const result = this._matchNodeNext(branch, new Set([nextNode]), {
816
+ combo: nextCombo,
817
+ index: index + 1
818
+ });
819
+ if (result) {
820
+ return result;
2900
821
  }
2901
- const [nextNode] = sortNodes(nextNodes);
2902
- return nextNode;
2903
822
  }
2904
- return this._matchNodeNext(branch, nextNodes, {
2905
- combo: nextCombo,
2906
- index: index + 1
2907
- });
2908
823
  }
2909
824
  return null;
2910
825
  };
2911
826
 
2912
827
  /**
2913
- * Matches a node in the 'previous' direction.
828
+ * Recursively checks for a valid backward path.
2914
829
  * @private
2915
- * @param {Array} branch - The branch.
2916
- * @param {object} node - The Element node.
2917
- * @param {object} [opt] - Options.
2918
- * @param {number} [opt.index] - The index.
2919
- * @returns {?object} The node.
830
+ * @param {object} node - The starting node.
831
+ * @param {Array.<object>} branch - The selector branch.
832
+ * @param {number} index - The current branch index.
833
+ * @param {object} opt - The match options.
834
+ * @returns {boolean} True if a valid path exists, otherwise false.
2920
835
  */
2921
- _matchNodePrev = (branch, node, opt = {}) => {
2922
- const { index } = opt;
836
+ _hasValidPathPrev = (node, branch, index, opt) => {
837
+ if (index < 0) {
838
+ return true;
839
+ }
2923
840
  const twig = branch[index];
2924
- const nextNodes = this._getCombinedNodes(twig, [node], DIR_PREV);
2925
- if (nextNodes.length) {
2926
- if (index === 0) {
2927
- return node;
841
+ const { combo, leaves } = twig;
842
+ const comboName = combo.name;
843
+ if (comboName === '+') {
844
+ const refNode = node.previousElementSibling;
845
+ if (refNode && this.matchLeaves(leaves, refNode, opt)) {
846
+ if (this._hasValidPathPrev(refNode, branch, index - 1, opt)) {
847
+ return true;
848
+ }
2928
849
  }
2929
- let matched;
2930
- for (const nextNode of nextNodes) {
2931
- matched = this._matchNodePrev(branch, nextNode, {
2932
- index: index - 1
2933
- });
2934
- if (matched) {
2935
- break;
850
+ } else if (comboName === '~') {
851
+ let refNode = node.previousElementSibling;
852
+ while (refNode) {
853
+ if (this.matchLeaves(leaves, refNode, opt)) {
854
+ if (this._hasValidPathPrev(refNode, branch, index - 1, opt)) {
855
+ return true;
856
+ }
857
+ }
858
+ refNode = refNode.previousElementSibling;
859
+ }
860
+ } else if (comboName === '>') {
861
+ const parentNode = node.parentNode;
862
+ if (parentNode && this.matchLeaves(leaves, parentNode, opt)) {
863
+ if (this._hasValidPathPrev(parentNode, branch, index - 1, opt)) {
864
+ return true;
2936
865
  }
2937
866
  }
2938
- if (matched) {
2939
- return node;
867
+ } else {
868
+ let refNode = node.parentNode;
869
+ while (refNode) {
870
+ if (this.matchLeaves(leaves, refNode, opt)) {
871
+ if (this._hasValidPathPrev(refNode, branch, index - 1, opt)) {
872
+ return true;
873
+ }
874
+ }
875
+ refNode = refNode.parentNode;
2940
876
  }
2941
877
  }
2942
- return null;
878
+ return false;
2943
879
  };
2944
880
 
2945
881
  /**
2946
- * Processes a complex selector branch to find all matching nodes.
882
+ * Processes complex branch for all matches.
2947
883
  * @private
2948
- * @param {Array} branch - The selector branch from the AST.
2949
- * @param {Array} entryNodes - The initial set of nodes to start from.
2950
- * @param {string} dir - The direction of traversal ('next' or 'prev').
2951
- * @returns {Set.<object>} A set of all matched nodes.
884
+ * @param {Array.<object>} branch - The selector branch.
885
+ * @param {Array.<object>} entryNodes - The entry nodes.
886
+ * @param {string} dir - The traversal direction.
887
+ * @returns {Set.<object>} Set of matched nodes.
2952
888
  */
2953
889
  _processComplexBranchAll = (branch, entryNodes, dir) => {
2954
890
  const matchedNodes = new Set();
@@ -2956,42 +892,29 @@ export class Finder {
2956
892
  const lastIndex = branchLen - 1;
2957
893
  if (dir === DIR_NEXT) {
2958
894
  const { combo: firstCombo } = branch[0];
2959
- for (const node of entryNodes) {
2960
- let combo = firstCombo;
2961
- let nextNodes = [node];
2962
- for (let j = 1; j < branchLen; j++) {
2963
- const { combo: nextCombo, leaves } = branch[j];
2964
- const twig = { combo, leaves };
2965
- const nodesArr = this._getCombinedNodes(twig, nextNodes, dir);
2966
- if (nodesArr.length) {
2967
- if (j === lastIndex) {
2968
- for (const nextNode of nodesArr) {
2969
- matchedNodes.add(nextNode);
2970
- }
2971
- }
2972
- combo = nextCombo;
2973
- nextNodes = nodesArr;
895
+ const dfs = (node, index, currentCombo) => {
896
+ const { combo: nextCombo, leaves } = branch[index];
897
+ const twig = { combo: currentCombo, leaves };
898
+ for (const nextNode of this.yieldCombinatorMatches(twig, node, {
899
+ dir
900
+ })) {
901
+ if (index === lastIndex) {
902
+ matchedNodes.add(nextNode);
2974
903
  } else {
2975
- break;
904
+ dfs(nextNode, index + 1, nextCombo);
2976
905
  }
2977
906
  }
907
+ };
908
+ for (const node of entryNodes) {
909
+ dfs(node, 1, firstCombo);
2978
910
  }
2979
- // DIR_PREV
2980
911
  } else {
2981
- for (const node of entryNodes) {
2982
- let nextNodes = [node];
2983
- for (let j = lastIndex - 1; j >= 0; j--) {
2984
- const twig = branch[j];
2985
- const nodesArr = this._getCombinedNodes(twig, nextNodes, dir);
2986
- if (nodesArr.length) {
2987
- // The entry node is the final match
2988
- if (j === 0) {
2989
- matchedNodes.add(node);
2990
- }
2991
- nextNodes = nodesArr;
2992
- } else {
2993
- break;
2994
- }
912
+ for (let i = 0, len = entryNodes.length; i < len; i++) {
913
+ const node = entryNodes[i];
914
+ if (
915
+ this._hasValidPathPrev(node, branch, lastIndex - 1, this.matchOpts)
916
+ ) {
917
+ matchedNodes.add(node);
2995
918
  }
2996
919
  }
2997
920
  }
@@ -2999,18 +922,17 @@ export class Finder {
2999
922
  };
3000
923
 
3001
924
  /**
3002
- * Processes a complex selector branch to find the first matching node.
925
+ * Processes complex branch for the first match.
3003
926
  * @private
3004
- * @param {Array} branch - The selector branch from the AST.
3005
- * @param {Array} entryNodes - The initial set of nodes to start from.
3006
- * @param {string} dir - The direction of traversal ('next' or 'prev').
3007
- * @param {string} targetType - The type of search (e.g., 'first').
3008
- * @returns {?object} The first matched node, or null.
927
+ * @param {Array.<object>} branch - The selector branch.
928
+ * @param {Array.<object>} entryNodes - The entry nodes.
929
+ * @param {string} dir - The traversal direction.
930
+ * @param {string} targetType - The target type.
931
+ * @returns {object|null} The matched node or null.
3009
932
  */
3010
933
  _processComplexBranchFirst = (branch, entryNodes, dir, targetType) => {
3011
934
  const branchLen = branch.length;
3012
935
  const lastIndex = branchLen - 1;
3013
- // DIR_NEXT logic for finding the first match.
3014
936
  if (dir === DIR_NEXT) {
3015
937
  const { combo: entryCombo } = branch[0];
3016
938
  for (const node of entryNodes) {
@@ -3019,11 +941,8 @@ export class Finder {
3019
941
  index: 1
3020
942
  });
3021
943
  if (matchedNode) {
3022
- if (this.#node.nodeType === ELEMENT_NODE) {
3023
- if (
3024
- matchedNode !== this.#node &&
3025
- this.#node.contains(matchedNode)
3026
- ) {
944
+ if (this.node.nodeType === ELEMENT_NODE) {
945
+ if (matchedNode !== this.node && this.node.contains(matchedNode)) {
3027
946
  return matchedNode;
3028
947
  }
3029
948
  } else {
@@ -3031,10 +950,9 @@ export class Finder {
3031
950
  }
3032
951
  }
3033
952
  }
3034
- // Fallback logic if no direct match found.
3035
953
  const { leaves: entryLeaves } = branch[0];
3036
954
  const [entryNode] = entryNodes;
3037
- if (this.#node.contains(entryNode)) {
955
+ if (this.node.contains(entryNode)) {
3038
956
  let [refNode] = this._findNodeWalker(entryLeaves, entryNode, {
3039
957
  targetType
3040
958
  });
@@ -3044,10 +962,10 @@ export class Finder {
3044
962
  index: 1
3045
963
  });
3046
964
  if (matchedNode) {
3047
- if (this.#node.nodeType === ELEMENT_NODE) {
965
+ if (this.node.nodeType === ELEMENT_NODE) {
3048
966
  if (
3049
- matchedNode !== this.#node &&
3050
- this.#node.contains(matchedNode)
967
+ matchedNode !== this.node &&
968
+ this.node.contains(matchedNode)
3051
969
  ) {
3052
970
  return matchedNode;
3053
971
  }
@@ -3061,28 +979,30 @@ export class Finder {
3061
979
  });
3062
980
  }
3063
981
  }
3064
- // DIR_PREV logic for finding the first match.
3065
982
  } else {
3066
- for (const node of entryNodes) {
3067
- const matchedNode = this._matchNodePrev(branch, node, {
3068
- index: lastIndex - 1
3069
- });
3070
- if (matchedNode) {
3071
- return matchedNode;
983
+ for (let i = 0, len = entryNodes.length; i < len; i++) {
984
+ const node = entryNodes[i];
985
+ if (
986
+ this._hasValidPathPrev(node, branch, lastIndex - 1, this.matchOpts)
987
+ ) {
988
+ return node;
3072
989
  }
3073
990
  }
3074
- // Fallback for TARGET_FIRST.
3075
991
  if (targetType === TARGET_FIRST) {
3076
992
  const { leaves: entryLeaves } = branch[lastIndex];
3077
- const [entryNode] = entryNodes;
993
+ const entryNode = entryNodes[0];
3078
994
  let [refNode] = this._findNodeWalker(entryLeaves, entryNode, {
3079
995
  targetType
3080
996
  });
3081
997
  while (refNode) {
3082
- const matchedNode = this._matchNodePrev(branch, refNode, {
3083
- index: lastIndex - 1
3084
- });
3085
- if (matchedNode) {
998
+ if (
999
+ this._hasValidPathPrev(
1000
+ refNode,
1001
+ branch,
1002
+ lastIndex - 1,
1003
+ this.matchOpts
1004
+ )
1005
+ ) {
3086
1006
  return refNode;
3087
1007
  }
3088
1008
  [refNode] = this._findNodeWalker(entryLeaves, refNode, {
@@ -3098,24 +1018,20 @@ export class Finder {
3098
1018
  /**
3099
1019
  * Finds matched nodes.
3100
1020
  * @param {string} targetType - The target type.
3101
- * @returns {Set.<object>} A collection of matched nodes.
1021
+ * @returns {Set.<object>|object} A collection of matched nodes.
3102
1022
  */
3103
1023
  find = targetType => {
3104
1024
  let collection;
3105
1025
  try {
3106
1026
  collection = this._collectNodes(targetType);
3107
1027
  } catch (e) {
3108
- if (this.#check) {
3109
- let pseudoElement;
3110
- if (this.#pseudoElement.length) {
3111
- pseudoElement = this.#pseudoElement.join('');
3112
- } else {
3113
- pseudoElement = null;
3114
- }
1028
+ if (this.check) {
3115
1029
  return {
3116
- pseudoElement,
1030
+ ast: this.#selectorAST,
3117
1031
  match: false,
3118
- ast: this.#selectorAST ?? null
1032
+ pseudoElement: this.pseudoElements.length
1033
+ ? this.pseudoElements.join('')
1034
+ : null
3119
1035
  };
3120
1036
  } else {
3121
1037
  throw e;
@@ -3132,17 +1048,18 @@ export class Finder {
3132
1048
  }
3133
1049
  const entryNodes = collectedNodes[i];
3134
1050
  const lastIndex = branch.length - 1;
3135
- // Handle simple selectors (no combinators).
3136
1051
  if (lastIndex === 0) {
3137
1052
  if (
3138
1053
  (targetType === TARGET_ALL || targetType === TARGET_FIRST) &&
3139
- this.#node.nodeType === ELEMENT_NODE
1054
+ this.node.nodeType === ELEMENT_NODE
3140
1055
  ) {
3141
1056
  for (const node of entryNodes) {
3142
- if (node !== this.#node && this.#node.contains(node)) {
3143
- nodes.add(node);
3144
- if (targetType === TARGET_FIRST) {
3145
- break;
1057
+ if (node !== this.node) {
1058
+ if (targetType === TARGET_ALL || this.node.contains(node)) {
1059
+ nodes.add(node);
1060
+ if (targetType === TARGET_FIRST) {
1061
+ break;
1062
+ }
3146
1063
  }
3147
1064
  }
3148
1065
  }
@@ -3160,51 +1077,39 @@ export class Finder {
3160
1077
  nodes.add(entryNodes[0]);
3161
1078
  }
3162
1079
  }
3163
- } else {
3164
- // Handle complex selectors.
3165
- if (targetType === TARGET_ALL) {
3166
- const newNodes = this._processComplexBranchAll(
3167
- branch,
3168
- entryNodes,
3169
- dir
3170
- );
3171
- if (nodes.size) {
3172
- for (const newNode of newNodes) {
3173
- nodes.add(newNode);
3174
- }
3175
- sort = true;
3176
- } else {
3177
- nodes = newNodes;
1080
+ } else if (targetType === TARGET_ALL) {
1081
+ const newNodes = this._processComplexBranchAll(branch, entryNodes, dir);
1082
+ if (nodes.size) {
1083
+ for (const newNode of newNodes) {
1084
+ nodes.add(newNode);
3178
1085
  }
1086
+ sort = true;
3179
1087
  } else {
3180
- const matchedNode = this._processComplexBranchFirst(
3181
- branch,
3182
- entryNodes,
3183
- dir,
3184
- targetType
3185
- );
3186
- if (matchedNode) {
3187
- nodes.add(matchedNode);
3188
- }
1088
+ nodes = newNodes;
3189
1089
  }
3190
- }
3191
- }
3192
- if (this.#check) {
3193
- const match = !!nodes.size;
3194
- let pseudoElement;
3195
- if (this.#pseudoElement.length) {
3196
- pseudoElement = this.#pseudoElement.join('');
3197
1090
  } else {
3198
- pseudoElement = null;
1091
+ const matchedNode = this._processComplexBranchFirst(
1092
+ branch,
1093
+ entryNodes,
1094
+ dir,
1095
+ targetType
1096
+ );
1097
+ if (matchedNode) {
1098
+ nodes.add(matchedNode);
1099
+ }
3199
1100
  }
1101
+ }
1102
+ if (this.check) {
3200
1103
  return {
3201
- match,
3202
- pseudoElement,
3203
- ast: this.#selectorAST
1104
+ ast: this.#selectorAST,
1105
+ match: nodes.size > 0,
1106
+ pseudoElement: this.pseudoElements.length
1107
+ ? this.pseudoElements.join('')
1108
+ : null
3204
1109
  };
3205
1110
  }
3206
1111
  if (targetType === TARGET_FIRST || targetType === TARGET_ALL) {
3207
- nodes.delete(this.#node);
1112
+ nodes.delete(this.node);
3208
1113
  }
3209
1114
  if ((sort || targetType === TARGET_FIRST) && nodes.size > 1) {
3210
1115
  return new Set(sortNodes(nodes));