@asamuzakjp/dom-selector 8.1.5 → 8.2.1

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,313 +13,180 @@ 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,
22
+ DIR_NEXT,
23
+ DIR_PREV,
42
24
  DOCUMENT_FRAGMENT_NODE,
43
25
  ELEMENT_NODE,
44
- FORM_PARTS,
45
- HEX,
46
26
  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
27
  PS_ELEMENT_SELECTOR,
55
- SHOW_ALL,
56
- SHOW_CONTAINER,
57
28
  SYNTAX_ERR,
58
29
  TARGET_ALL,
59
30
  TARGET_FIRST,
60
31
  TARGET_LINEAL,
61
32
  TARGET_SELF,
62
- TEXT_NODE,
63
33
  TYPE_SELECTOR
64
34
  } from './constant.js';
65
- const ANB_FIRST = { a: 0, b: 1 };
66
- const ANB_LAST = { a: 0, b: 1, reverse: true };
67
- const DIR_NEXT = 'next';
68
- 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
59
  */
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;
60
+ setup(selector, node, opt = {}) {
61
+ super.setup(selector, node, opt);
62
+ this.#ast = null;
63
+ this.#nodes = null;
229
64
  this.#scoped =
230
- this.#node !== this.#root && this.#node.nodeType === ELEMENT_NODE;
65
+ this.node !== this.root && this.node.nodeType === ELEMENT_NODE;
231
66
  this.#selector = selector;
232
- this.#pseudoElement = [];
233
- this.#walkers = null;
67
+ this.#selectorAST = null;
234
68
  this.#nodeWalker = null;
235
69
  this.#rootWalker = null;
236
- this.#verifyShadowHost = null;
237
- this.clearResults();
238
70
  return this;
239
- };
71
+ }
240
72
 
241
73
  /**
242
- * Clear cached results.
243
- * @param {boolean} all - Clear all results.
244
- * @returns {void}
74
+ * Finds matched nodes.
75
+ * @param {string} targetType - The target type.
76
+ * @returns {Set.<object>|object} A collection of matched nodes.
245
77
  */
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();
78
+ find = targetType => {
79
+ let collection;
80
+ try {
81
+ collection = this._collectNodes(targetType);
82
+ } catch (e) {
83
+ if (this.check) {
84
+ return {
85
+ ast: this.#selectorAST,
86
+ match: false,
87
+ pseudoElement: this.pseudoElements.length
88
+ ? this.pseudoElements.join('')
89
+ : null
90
+ };
91
+ } else {
92
+ throw e;
93
+ }
265
94
  }
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;
95
+ const [[...branches], collectedNodes] = collection;
96
+ const l = branches.length;
97
+ let sort = l > 1 && targetType === TARGET_ALL;
98
+ let nodes = new Set();
99
+ for (let i = 0; i < l; i++) {
100
+ const { branch, dir, find } = branches[i];
101
+ if (!branch.length || !find) {
102
+ continue;
103
+ }
104
+ const entryNodes = collectedNodes[i];
105
+ const lastIndex = branch.length - 1;
106
+ if (lastIndex === 0) {
107
+ if (
108
+ (targetType === TARGET_ALL || targetType === TARGET_FIRST) &&
109
+ this.node.nodeType === ELEMENT_NODE
110
+ ) {
111
+ for (const node of entryNodes) {
112
+ if (node !== this.node) {
113
+ if (targetType === TARGET_ALL || this.node.contains(node)) {
114
+ nodes.add(node);
115
+ if (targetType === TARGET_FIRST) {
116
+ break;
117
+ }
118
+ }
119
+ }
120
+ }
121
+ } else if (targetType === TARGET_ALL) {
122
+ if (nodes.size) {
123
+ for (const node of entryNodes) {
124
+ nodes.add(node);
125
+ }
126
+ sort = true;
127
+ } else {
128
+ nodes = new Set(entryNodes);
129
+ }
130
+ } else {
131
+ if (entryNodes.length) {
132
+ nodes.add(entryNodes[0]);
133
+ }
134
+ }
135
+ } else if (targetType === TARGET_ALL) {
136
+ const newNodes = this._processComplexBranchAll(branch, entryNodes, dir);
137
+ if (nodes.size) {
138
+ for (const newNode of newNodes) {
139
+ nodes.add(newNode);
140
+ }
141
+ sort = true;
142
+ } else {
143
+ nodes = newNodes;
144
+ }
145
+ } else {
146
+ const matchedNode = this._processComplexBranchFirst(
147
+ branch,
148
+ entryNodes,
149
+ dir,
150
+ targetType
151
+ );
152
+ if (matchedNode) {
153
+ nodes.add(matchedNode);
154
+ }
155
+ }
288
156
  }
157
+ if (this.check) {
158
+ return {
159
+ ast: this.#selectorAST,
160
+ match: nodes.size > 0,
161
+ pseudoElement: this.pseudoElements.length
162
+ ? this.pseudoElements.join('')
163
+ : null
164
+ };
165
+ }
166
+ if (targetType === TARGET_FIRST || targetType === TARGET_ALL) {
167
+ nodes.delete(this.node);
168
+ }
169
+ if ((sort || targetType === TARGET_FIRST) && nodes.size > 1) {
170
+ return new Set(sortNodes(nodes));
171
+ }
172
+ return nodes;
289
173
  };
290
174
 
291
175
  /**
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.
176
+ * Gets AST for selector.
177
+ * @param {string} selector - The selector text.
178
+ * @returns {object} The AST for the selector.
305
179
  */
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;
180
+ getAST = selector => {
181
+ return parseSelector(selector);
322
182
  };
323
183
 
324
184
  /**
325
185
  * Processes selector branches into the internal AST structure.
326
186
  * @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.
187
+ * @param {Array.<object>} branches - The selector branches to process.
188
+ * @param {string} selector - The CSS selector string.
189
+ * @returns {object} Object containing ast and descendant flags.
330
190
  */
331
191
  _processSelectorBranches = (branches, selector) => {
332
192
  let descendant = false;
@@ -342,7 +202,7 @@ export class Finder {
342
202
  const isLast = j === itemsLen - 1;
343
203
  if (isInvalidCombinator(item.type, prevType, isLast)) {
344
204
  const msg = `Invalid selector ${selector}`;
345
- this.onError(generateException(msg, SYNTAX_ERR, this.#window));
205
+ this.onError(generateException(msg, SYNTAX_ERR, this.window));
346
206
  return { ast: [], descendant: false, invalidate: false };
347
207
  }
348
208
  if (item.type === COMBINATOR) {
@@ -357,1948 +217,102 @@ export class Finder {
357
217
  if (unescapedName !== item.name) {
358
218
  item.name = unescapedName;
359
219
  }
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
- };
2144
-
2145
- /**
2146
- * Finds descendant nodes.
2147
- * @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.
2152
- */
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);
2178
- }
2179
- }
2180
- return nodes;
2181
- }
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
- }
2193
- }
2194
- };
2195
-
2196
- /**
2197
- * Collects combinator matches into an array.
2198
- * @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.
2205
- */
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;
2222
- }
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;
2238
- }
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;
2255
- }
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);
220
+ if (/[|:]/.test(unescapedName)) {
221
+ item.namespace = true;
222
+ }
2268
223
  }
2269
- refNode = refNode.parentNode;
224
+ leaves.add(item);
2270
225
  }
2271
- if (ancestors.length) {
2272
- matched.push(...ancestors.reverse());
226
+ prevType = item.type;
227
+ if (isLast) {
228
+ branch.push({ combo: null, leaves: sortAST(leaves) });
229
+ leaves.clear();
2273
230
  }
2274
231
  }
2275
232
  }
233
+ ast.push({ branch, dir: null, filtered: false, find: false });
2276
234
  }
2277
- return matched;
235
+ return { ast, descendant };
2278
236
  };
2279
237
 
2280
238
  /**
2281
- * Matches a combinator.
239
+ * Corresponds AST and DOM nodes for the given selector.
2282
240
  * @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.
241
+ * @param {string} selector - The CSS selector string.
242
+ * @returns {Array} An array containing the AST and empty nodes array.
2287
243
  */
2288
- _matchCombinator = (twig, node, opt) =>
2289
- new Set(this._collectCombinatorMatches(twig, node, opt));
244
+ _correspond = selector => {
245
+ const nodes = [];
246
+ let descendant = false;
247
+ this.invalidate = false;
248
+ let ast;
249
+ if (this.documentCache.has(this.document)) {
250
+ const cachedItem = this.documentCache.get(this.document);
251
+ if (cachedItem && cachedItem.has(`${selector}`)) {
252
+ const item = cachedItem.get(`${selector}`);
253
+ ast = item.ast;
254
+ descendant = item.descendant;
255
+ this.invalidate = item.invalidate;
256
+ this.#selectorAST = item.selectorAST;
257
+ }
258
+ }
259
+ if (ast) {
260
+ const l = ast.length;
261
+ for (let i = 0; i < l; i++) {
262
+ ast[i].dir = null;
263
+ ast[i].filtered = false;
264
+ ast[i].find = false;
265
+ nodes[i] = [];
266
+ }
267
+ } else {
268
+ this.#selectorAST = parseSelector(selector);
269
+ const { branches, info } = walkAST(
270
+ this.#selectorAST,
271
+ true,
272
+ createHasValidator(this.window)
273
+ );
274
+ const {
275
+ hasHasPseudoFunc,
276
+ hasLogicalPseudoFunc,
277
+ hasNthChildOfSelector,
278
+ hasStatePseudoClass,
279
+ hasUnsupportedPseudoClass
280
+ } = info;
281
+ this.invalidate =
282
+ hasHasPseudoFunc ||
283
+ hasStatePseudoClass ||
284
+ hasUnsupportedPseudoClass ||
285
+ !!(hasLogicalPseudoFunc && hasNthChildOfSelector);
286
+ const processed = this._processSelectorBranches(branches, selector);
287
+ ast = processed.ast;
288
+ descendant = processed.descendant;
289
+ let cachedItem;
290
+ if (this.documentCache.has(this.document)) {
291
+ cachedItem = this.documentCache.get(this.document);
292
+ } else {
293
+ cachedItem = new Map();
294
+ }
295
+ cachedItem.set(`${selector}`, {
296
+ ast,
297
+ descendant,
298
+ invalidate: this.invalidate,
299
+ selectorAST: this.#selectorAST
300
+ });
301
+ this.documentCache.set(this.document, cachedItem);
302
+ for (let i = 0; i < ast.length; i++) {
303
+ nodes[i] = [];
304
+ }
305
+ }
306
+ return [ast, nodes];
307
+ };
2290
308
 
2291
309
  /**
2292
- * Traverses with a TreeWalker and collects nodes matching the leaves.
310
+ * Traverses and collects nodes matching leaves.
2293
311
  * @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.
312
+ * @param {object} walker - The TreeWalker instance.
313
+ * @param {Array.<object>} leaves - The AST leaves to match.
314
+ * @param {object} [opt] - Options for traversal.
315
+ * @returns {Array.<object>} An array of collected nodes.
2302
316
  */
2303
317
  _traverseAndCollectNodes = (walker, leaves, opt = {}) => {
2304
318
  const { boundaryNode, force, startNode, targetType } = opt;
@@ -2310,14 +324,10 @@ export class Finder {
2310
324
  // Adjust starting node.
2311
325
  if (currentNode.nodeType !== ELEMENT_NODE) {
2312
326
  currentNode = walker.nextNode();
2313
- } else if (currentNode === startNode && currentNode !== this.#root) {
327
+ } else if (currentNode === startNode && currentNode !== this.root) {
2314
328
  currentNode = walker.nextNode();
2315
329
  }
2316
- const matchOpt = {
2317
- warn: this.#warn
2318
- };
2319
330
  while (currentNode) {
2320
- // Stop when we reach the boundary.
2321
331
  if (boundaryNode) {
2322
332
  if (currentNode === boundaryNode) {
2323
333
  break;
@@ -2329,11 +339,10 @@ export class Finder {
2329
339
  }
2330
340
  }
2331
341
  if (
2332
- this._matchLeaves(leaves, currentNode, matchOpt) &&
2333
- currentNode !== this.#node
342
+ this.matchLeaves(leaves, currentNode, this.matchOpts) &&
343
+ currentNode !== this.node
2334
344
  ) {
2335
345
  collectedNodes.push(currentNode);
2336
- // Stop after the first match if not collecting all.
2337
346
  if (targetType !== TARGET_ALL) {
2338
347
  break;
2339
348
  }
@@ -2344,47 +353,44 @@ export class Finder {
2344
353
  };
2345
354
 
2346
355
  /**
2347
- * Finds matched node(s) preceding this.#node.
356
+ * Finds matching nodes preceding the current node.
2348
357
  * @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.
358
+ * @param {Array.<object>} leaves - The AST leaves to match.
359
+ * @param {object} node - The starting node.
360
+ * @param {object} [opt] - Options for finding.
361
+ * @returns {Array.<object>} An array of matched nodes.
2355
362
  */
2356
363
  _findPrecede = (leaves, node, opt = {}) => {
2357
364
  const { force, targetType } = opt;
2358
365
  if (!this.#rootWalker) {
2359
- this.#rootWalker = this._createTreeWalker(this.#root);
366
+ this.#rootWalker = this.createTreeWalker(this.root);
2360
367
  }
2361
368
  return this._traverseAndCollectNodes(this.#rootWalker, leaves, {
2362
369
  force,
2363
370
  targetType,
2364
- boundaryNode: this.#node,
371
+ boundaryNode: this.node,
2365
372
  startNode: node
2366
373
  });
2367
374
  };
2368
375
 
2369
376
  /**
2370
- * Finds matched node(s) in #nodeWalker.
377
+ * Finds matching nodes using TreeWalker.
2371
378
  * @private
2372
379
  * @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.
380
+ * @param {object} node - The starting node.
381
+ * @param {object} [opt] - Traversal options.
382
+ * @returns {Array.<object>} An array of matched nodes.
2377
383
  */
2378
384
  _findNodeWalker = (leaves, node, opt = {}) => {
2379
385
  const { precede, ...traversalOpts } = opt;
2380
386
  if (precede) {
2381
- const precedeNodes = this._findPrecede(leaves, this.#root, opt);
387
+ const precedeNodes = this._findPrecede(leaves, this.root, opt);
2382
388
  if (precedeNodes.length) {
2383
389
  return precedeNodes;
2384
390
  }
2385
391
  }
2386
392
  if (!this.#nodeWalker) {
2387
- this.#nodeWalker = this._createTreeWalker(this.#node);
393
+ this.#nodeWalker = this.createTreeWalker(this.node);
2388
394
  }
2389
395
  return this._traverseAndCollectNodes(this.#nodeWalker, leaves, {
2390
396
  ...traversalOpts,
@@ -2393,40 +399,38 @@ export class Finder {
2393
399
  };
2394
400
 
2395
401
  /**
2396
- * Matches the node itself.
402
+ * Matches the current node itself against leaves.
2397
403
  * @private
2398
- * @param {Array} leaves - The AST leaves.
2399
- * @returns {Array} An array containing [nodes, filtered, pseudoElement].
404
+ * @param {Array.<object>} leaves - The AST leaves.
405
+ * @returns {Array} Array with nodes, match boolean, and pseudo-elements.
2400
406
  */
2401
407
  _matchSelf = leaves => {
2402
- const matched = this._matchLeaves(leaves, this.#node, {
2403
- check: this.#check,
2404
- warn: this.#warn
408
+ const matched = this.matchLeaves(leaves, this.node, {
409
+ check: this.check,
410
+ warn: this.warn
2405
411
  });
2406
- const nodes = matched ? [this.#node] : [];
2407
- return [nodes, matched, this.#pseudoElement];
412
+ const nodes = matched ? [this.node] : [];
413
+ return [nodes, matched, this.pseudoElements];
2408
414
  };
2409
415
 
2410
416
  /**
2411
- * Finds lineal nodes (self and ancestors).
417
+ * Finds lineal matching nodes (self and ancestors).
2412
418
  * @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].
419
+ * @param {Array.<object>} leaves - The AST leaves.
420
+ * @param {object} [opt] - Options like complex flag.
421
+ * @returns {Array} Array containing nodes and filtered boolean.
2417
422
  */
2418
423
  _findLineal = (leaves, opt = {}) => {
2419
424
  const { complex } = opt;
2420
425
  const nodes = [];
2421
- const matchOpts = { warn: this.#warn };
2422
- const selfMatched = this._matchLeaves(leaves, this.#node, matchOpts);
426
+ const selfMatched = this.matchLeaves(leaves, this.node, this.matchOpts);
2423
427
  if (selfMatched) {
2424
- nodes.push(this.#node);
428
+ nodes.push(this.node);
2425
429
  }
2426
430
  if (!selfMatched || complex) {
2427
- let currentNode = this.#node.parentNode;
431
+ let currentNode = this.node.parentNode;
2428
432
  while (currentNode) {
2429
- if (this._matchLeaves(leaves, currentNode, matchOpts)) {
433
+ if (this.matchLeaves(leaves, currentNode, this.matchOpts)) {
2430
434
  nodes.push(currentNode);
2431
435
  }
2432
436
  currentNode = currentNode.parentNode;
@@ -2437,157 +441,202 @@ export class Finder {
2437
441
  };
2438
442
 
2439
443
  /**
2440
- * Finds entry nodes for pseudo-element selectors.
444
+ * Finds entry nodes for pseudo-elements.
2441
445
  * @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 }.
446
+ * @param {object} leaf - The AST leaf.
447
+ * @param {Array.<object>} filterLeaves - Leaves for filtering.
448
+ * @param {string} targetType - The target type.
449
+ * @returns {object} Object with nodes, filtered, and pending flags.
2446
450
  */
2447
451
  _findEntryNodesForPseudoElement = (leaf, filterLeaves, targetType) => {
2448
- let nodes = [];
2449
- let filtered = false;
2450
- if (targetType === TARGET_SELF && this.#check) {
452
+ if (targetType === TARGET_SELF && this.check) {
2451
453
  const css = generateCSS(leaf);
2452
- this.#pseudoElement.push(css);
454
+ this.pseudoElements.push(css);
2453
455
  if (filterLeaves.length) {
2454
- [nodes, filtered] = this._matchSelf(filterLeaves);
2455
- } else {
2456
- nodes.push(this.#node);
2457
- filtered = true;
456
+ const [nodes, filtered] = this._matchSelf(filterLeaves);
457
+ return { nodes, filtered, pending: false };
2458
458
  }
2459
- } else {
2460
- matchPseudoElementSelector(leaf.name, leaf.type, { warn: this.#warn });
459
+ return { nodes: [this.node], filtered: true, pending: false };
2461
460
  }
2462
- return { nodes, filtered, pending: false };
461
+ matchPseudoElementSelector(leaf.name, leaf.type, this.matchOpts);
462
+ return { nodes: [], filtered: false, pending: false };
2463
463
  };
2464
464
 
2465
465
  /**
2466
- * Finds entry nodes for ID selectors.
466
+ * Finds entry nodes using ID selector strategy.
2467
467
  * @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 }.
468
+ * @param {object} twig - The twig object containing leaves.
469
+ * @param {string} targetType - The target type.
470
+ * @param {object} [opt] - Strategy options.
471
+ * @returns {object} Result object with nodes and flags.
2474
472
  */
2475
473
  _findEntryNodesForId = (twig, targetType, opt = {}) => {
2476
474
  const { leaves } = twig;
2477
- const [leaf] = leaves;
2478
- const filterLeaves = this._getFilterLeaves(leaves);
475
+ const filterLeaves = this.getFilterLeaves(leaves);
2479
476
  const { complex, precede } = opt;
2480
- let nodes = [];
2481
- let filtered = false;
2482
477
  if (targetType === TARGET_SELF) {
2483
- [nodes, filtered] = this._matchSelf(leaves);
478
+ const [nodes, filtered] = this._matchSelf(leaves);
479
+ return { nodes, filtered, pending: false };
2484
480
  } else if (targetType === TARGET_LINEAL) {
2485
- [nodes, filtered] = this._findLineal(leaves, { complex });
481
+ const [nodes, filtered] = this._findLineal(leaves, { complex });
482
+ return { nodes, filtered, pending: false };
2486
483
  } else if (
2487
484
  targetType === TARGET_FIRST &&
2488
- this.#root.nodeType !== ELEMENT_NODE
485
+ this.root.nodeType !== ELEMENT_NODE
2489
486
  ) {
2490
- const node = this.#root.getElementById(leaf.name);
487
+ const [leaf] = leaves;
488
+ const node = this.root.getElementById(leaf.name);
489
+ const nodes = [];
2491
490
  if (node) {
2492
491
  if (filterLeaves.length) {
2493
- if (this._matchLeaves(filterLeaves, node, { warn: this.#warn })) {
492
+ if (this.matchLeaves(filterLeaves, node, this.matchOpts)) {
2494
493
  nodes.push(node);
2495
- filtered = true;
2496
494
  }
2497
495
  } else {
2498
496
  nodes.push(node);
2499
- filtered = true;
2500
497
  }
2501
498
  }
2502
- } else {
2503
- nodes = this._findNodeWalker(leaves, this.#node, { precede, targetType });
2504
- filtered = nodes.length > 0;
499
+ return { nodes, filtered: nodes.length > 0, pending: false };
2505
500
  }
2506
- return { nodes, filtered, pending: false };
501
+ const nodes = this._findNodeWalker(leaves, this.node, {
502
+ precede,
503
+ targetType
504
+ });
505
+ return { nodes, filtered: nodes.length > 0, pending: false };
2507
506
  };
2508
507
 
2509
508
  /**
2510
- * Finds entry nodes for class selectors.
509
+ * Finds entry nodes using class selector strategy.
2511
510
  * @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 }.
511
+ * @param {Array.<object>} leaves - The AST leaves.
512
+ * @param {string} targetType - The target type.
513
+ * @param {object} [opt] - Strategy options.
514
+ * @returns {object} Result object with nodes and flags.
2518
515
  */
2519
516
  _findEntryNodesForClass = (leaves, targetType, opt = {}) => {
2520
517
  const { complex, precede } = opt;
2521
- let nodes = [];
2522
- let filtered = false;
2523
518
  if (targetType === TARGET_SELF) {
2524
- [nodes, filtered] = this._matchSelf(leaves);
519
+ const [nodes, filtered] = this._matchSelf(leaves);
520
+ return { nodes, filtered, pending: false };
2525
521
  } 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;
522
+ const [nodes, filtered] = this._findLineal(leaves, { complex });
523
+ return { nodes, filtered, pending: false };
524
+ } else if (
525
+ targetType !== TARGET_FIRST &&
526
+ !precede &&
527
+ typeof this.node.getElementsByClassName === 'function'
528
+ ) {
529
+ this.matchLeaves(leaves, this.node, this.matchOpts);
530
+ const [leaf] = leaves;
531
+ const className = unescapeSelector(leaf.name);
532
+ const collection = this.node.getElementsByClassName(className);
533
+ const len = collection.length;
534
+ const filterLeaves = this.getFilterLeaves(leaves);
535
+ const hasFilter = filterLeaves.length > 0;
536
+ const nodeArray = [];
537
+ for (let i = 0; i < len; i++) {
538
+ const currentNode = collection[i];
539
+ if (
540
+ !hasFilter ||
541
+ this.matchLeaves(filterLeaves, currentNode, this.matchOpts)
542
+ ) {
543
+ nodeArray.push(currentNode);
544
+ }
545
+ }
546
+ return {
547
+ nodes: nodeArray,
548
+ filtered: nodeArray.length > 0,
549
+ pending: false
550
+ };
2530
551
  }
2531
- return { nodes, filtered, pending: false };
552
+ const nodes = this._findNodeWalker(leaves, this.node, {
553
+ precede,
554
+ targetType
555
+ });
556
+ return { nodes, filtered: nodes.length > 0, pending: false };
2532
557
  };
2533
558
 
2534
559
  /**
2535
- * Finds entry nodes for type selectors.
560
+ * Finds entry nodes using type selector strategy.
2536
561
  * @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 }.
562
+ * @param {Array.<object>} leaves - The AST leaves.
563
+ * @param {string} targetType - The target type.
564
+ * @param {object} [opt] - Strategy options.
565
+ * @returns {object} Result object with nodes and flags.
2543
566
  */
2544
567
  _findEntryNodesForType = (leaves, targetType, opt = {}) => {
2545
568
  const { complex, precede } = opt;
2546
- let nodes = [];
2547
- let filtered = false;
2548
569
  if (targetType === TARGET_SELF) {
2549
- [nodes, filtered] = this._matchSelf(leaves);
570
+ const [nodes, filtered] = this._matchSelf(leaves);
571
+ return { nodes, filtered, pending: false };
2550
572
  } 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;
573
+ const [nodes, filtered] = this._findLineal(leaves, { complex });
574
+ return { nodes, filtered, pending: false };
575
+ }
576
+ const [leaf] = leaves;
577
+ const tagName = unescapeSelector(leaf.name);
578
+ if (
579
+ targetType !== TARGET_FIRST &&
580
+ !precede &&
581
+ this.document.contentType === 'text/html' &&
582
+ typeof this.node.getElementsByTagName === 'function' &&
583
+ tagName.indexOf('|') === -1
584
+ ) {
585
+ this.matchLeaves(leaves, this.node, this.matchOpts);
586
+ const collection = this.node.getElementsByTagName(tagName);
587
+ const len = collection.length;
588
+ const filterLeaves = this.getFilterLeaves(leaves);
589
+ const hasFilter = filterLeaves.length > 0;
590
+ const nodeArray = [];
591
+ for (let i = 0; i < len; i++) {
592
+ const currentNode = collection[i];
593
+ if (
594
+ !hasFilter ||
595
+ this.matchLeaves(filterLeaves, currentNode, this.matchOpts)
596
+ ) {
597
+ nodeArray.push(currentNode);
598
+ }
599
+ }
600
+ return {
601
+ nodes: nodeArray,
602
+ filtered: nodeArray.length > 0,
603
+ pending: false
604
+ };
2555
605
  }
2556
- return { nodes, filtered, pending: false };
606
+ const nodes = this._findNodeWalker(leaves, this.node, {
607
+ precede,
608
+ targetType
609
+ });
610
+ return { nodes, filtered: nodes.length > 0, pending: false };
2557
611
  };
2558
612
 
2559
613
  /**
2560
- * Finds entry nodes for other selector types (default case).
614
+ * Finds entry nodes for other selector types.
2561
615
  * @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 }.
616
+ * @param {object} twig - The twig object containing leaves.
617
+ * @param {string} targetType - The target type.
618
+ * @param {object} [opt] - Strategy options.
619
+ * @returns {object} Result object with nodes and flags.
2568
620
  */
2569
621
  _findEntryNodesForOther = (twig, targetType, opt = {}) => {
2570
622
  const { leaves } = twig;
2571
623
  const [leaf] = leaves;
2572
- const filterLeaves = this._getFilterLeaves(leaves);
624
+ const filterLeaves = this.getFilterLeaves(leaves);
2573
625
  const { complex, precede } = opt;
2574
- let nodes = [];
2575
- let filtered = false;
2576
- let pending = false;
2577
626
  if (targetType !== TARGET_LINEAL && /host(?:-context)?/.test(leaf.name)) {
2578
627
  let shadowRoot = null;
2579
628
  if (
2580
- this.#shadow &&
2581
- this.#node.nodeType === DOCUMENT_FRAGMENT_NODE &&
2582
- this._evaluateShadowHost(leaf, this.#node)
629
+ this.shadow &&
630
+ this.node.nodeType === DOCUMENT_FRAGMENT_NODE &&
631
+ this.evaluateShadowHost(leaf, this.node)
2583
632
  ) {
2584
- shadowRoot = this.#node;
633
+ shadowRoot = this.node;
2585
634
  } else if (
2586
635
  filterLeaves.length &&
2587
- this.#node.nodeType === ELEMENT_NODE &&
2588
- this._evaluateShadowHost(leaf, this.#node.shadowRoot)
636
+ this.node.nodeType === ELEMENT_NODE &&
637
+ this.evaluateShadowHost(leaf, this.node.shadowRoot)
2589
638
  ) {
2590
- shadowRoot = this.#node.shadowRoot;
639
+ shadowRoot = this.node.shadowRoot;
2591
640
  }
2592
641
  if (shadowRoot) {
2593
642
  let bool = true;
@@ -2597,11 +646,11 @@ export class Finder {
2597
646
  switch (filterLeaf.name) {
2598
647
  case 'host':
2599
648
  case 'host-context': {
2600
- bool = this._evaluateShadowHost(filterLeaf, shadowRoot);
649
+ bool = this.evaluateShadowHost(filterLeaf, shadowRoot);
2601
650
  break;
2602
651
  }
2603
652
  case 'has': {
2604
- bool = this._matchPseudoClassSelector(filterLeaf, shadowRoot, {});
653
+ bool = this.matchPseudoClassSelector(filterLeaf, shadowRoot, {});
2605
654
  break;
2606
655
  }
2607
656
  default: {
@@ -2612,43 +661,45 @@ export class Finder {
2612
661
  break;
2613
662
  }
2614
663
  }
664
+ const nodes = [];
2615
665
  if (bool) {
2616
666
  nodes.push(shadowRoot);
2617
- filtered = true;
2618
667
  }
668
+ return { nodes, filtered: nodes.length > 0, pending: false };
2619
669
  }
2620
670
  } else if (targetType === TARGET_SELF) {
2621
- [nodes, filtered] = this._matchSelf(leaves);
671
+ const [nodes, filtered] = this._matchSelf(leaves);
672
+ return { nodes, filtered, pending: false };
2622
673
  } else if (targetType === TARGET_LINEAL) {
2623
- [nodes, filtered] = this._findLineal(leaves, { complex });
674
+ const [nodes, filtered] = this._findLineal(leaves, { complex });
675
+ return { nodes, filtered, pending: false };
2624
676
  } else if (targetType === TARGET_FIRST) {
2625
- nodes = this._findNodeWalker(leaves, this.#node, { precede, targetType });
2626
- filtered = nodes.length > 0;
2627
- } else {
2628
- pending = true;
677
+ const nodes = this._findNodeWalker(leaves, this.node, {
678
+ precede,
679
+ targetType
680
+ });
681
+ return { nodes, filtered: nodes.length > 0, pending: false };
2629
682
  }
2630
- return { nodes, filtered, pending };
683
+ return { nodes: [], filtered: false, pending: true };
2631
684
  };
2632
685
 
2633
686
  /**
2634
- * Finds entry nodes.
687
+ * Finds entry nodes based on the selector type.
2635
688
  * @private
2636
- * @param {object} twig - The twig object.
689
+ * @param {object} twig - The twig object containing leaves.
2637
690
  * @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.
691
+ * @param {object} [opt] - Strategy options.
692
+ * @returns {object} Result object with nodes and flags.
2642
693
  */
2643
694
  _findEntryNodes = (twig, targetType, opt = {}) => {
2644
695
  const { leaves } = twig;
2645
696
  const [leaf] = leaves;
2646
- const filterLeaves = this._getFilterLeaves(leaves);
697
+ const filterLeaves = this.getFilterLeaves(leaves);
2647
698
  const { complex = false, dir = DIR_PREV } = opt;
2648
699
  const precede =
2649
700
  dir === DIR_NEXT &&
2650
- this.#node.nodeType === ELEMENT_NODE &&
2651
- this.#node !== this.#root;
701
+ this.node.nodeType === ELEMENT_NODE &&
702
+ this.node !== this.root;
2652
703
  let result;
2653
704
  switch (leaf.type) {
2654
705
  case PS_ELEMENT_SELECTOR: {
@@ -2696,11 +747,11 @@ export class Finder {
2696
747
  };
2697
748
 
2698
749
  /**
2699
- * Determines the direction and starting twig for a selector branch.
750
+ * Determines the traversal direction and starting twig.
2700
751
  * @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.
752
+ * @param {Array.<object>} branch - The selector branch.
753
+ * @param {string} targetType - The target type.
754
+ * @returns {object} Object containing dir and twig properties.
2704
755
  */
2705
756
  _determineTraversalStrategy = (branch, targetType) => {
2706
757
  const branchLen = branch.length;
@@ -2709,7 +760,6 @@ export class Finder {
2709
760
  if (branchLen === 1) {
2710
761
  return { dir: DIR_PREV, twig: firstTwig };
2711
762
  }
2712
- // Complex selector (branchLen > 1).
2713
763
  const {
2714
764
  leaves: [{ name: firstName, type: firstType }]
2715
765
  } = firstTwig;
@@ -2748,37 +798,34 @@ export class Finder {
2748
798
  return { dir: DIR_PREV, twig: lastTwig };
2749
799
  }
2750
800
  }
2751
- // Default strategy for complex selectors.
2752
801
  return { dir: DIR_NEXT, twig: firstTwig };
2753
802
  };
2754
803
 
2755
804
  /**
2756
- * Processes pending items not resolved with a direct strategy.
805
+ * Processes pending items to find matches.
2757
806
  * @private
2758
- * @param {Set.<Map>} pendingItems - The set of pending items.
807
+ * @param {Set.<Map>} pendingItems - Set of pending items to process.
808
+ * @returns {void}
2759
809
  */
2760
810
  _processPendingItems = pendingItems => {
2761
811
  if (!pendingItems.size) {
2762
812
  return;
2763
813
  }
2764
814
  if (!this.#rootWalker) {
2765
- this.#rootWalker = this._createTreeWalker(this.#root);
815
+ this.#rootWalker = this.createTreeWalker(this.root);
2766
816
  }
817
+ const node = this.#scoped ? this.node : this.root;
2767
818
  const walker = this.#rootWalker;
2768
- let node = this.#root;
2769
- if (this.#scoped) {
2770
- node = this.#node;
2771
- }
2772
819
  let nextNode = traverseNode(node, walker);
2773
820
  while (nextNode) {
2774
821
  const isWithinScope =
2775
- this.#node.nodeType !== ELEMENT_NODE ||
2776
- nextNode === this.#node ||
2777
- this.#node.contains(nextNode);
822
+ this.node.nodeType !== ELEMENT_NODE ||
823
+ nextNode === this.node ||
824
+ this.node.contains(nextNode);
2778
825
  if (isWithinScope) {
2779
826
  for (const pendingItem of pendingItems) {
2780
827
  const { leaves } = pendingItem.get('twig');
2781
- if (this._matchLeaves(leaves, nextNode, { warn: this.#warn })) {
828
+ if (this.matchLeaves(leaves, nextNode, this.matchOpts)) {
2782
829
  const index = pendingItem.get('index');
2783
830
  this.#ast[index].filtered = true;
2784
831
  this.#ast[index].find = true;
@@ -2793,10 +840,10 @@ export class Finder {
2793
840
  };
2794
841
 
2795
842
  /**
2796
- * Collects nodes.
843
+ * Collects all matching nodes into AST nodes array.
2797
844
  * @private
2798
845
  * @param {string} targetType - The target type.
2799
- * @returns {Array.<Array.<object>>} An array containing the AST and nodes.
846
+ * @returns {Array} Array containing the AST and nodes arrays.
2800
847
  */
2801
848
  _collectNodes = targetType => {
2802
849
  [this.#ast, this.#nodes] = this._correspond(this.#selector);
@@ -2855,35 +902,12 @@ export class Finder {
2855
902
  };
2856
903
 
2857
904
  /**
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.
905
+ * Matches a node in the next direction.
2880
906
  * @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.
907
+ * @param {Array.<object>} branch - The selector branch.
908
+ * @param {Array.<object>|Set.<object>} nodes - The starting nodes.
909
+ * @param {object} [opt] - Options containing combo and index.
910
+ * @returns {object|null} The matched node or null.
2887
911
  */
2888
912
  _matchNodeNext = (branch, nodes, opt = {}) => {
2889
913
  const { combo, index } = opt;
@@ -2892,63 +916,86 @@ export class Finder {
2892
916
  combo,
2893
917
  leaves
2894
918
  };
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];
919
+ for (const node of nodes) {
920
+ for (const nextNode of this.yieldCombinatorMatches(twig, node, {
921
+ dir: DIR_NEXT
922
+ })) {
923
+ if (index === branch.length - 1) {
924
+ return nextNode;
925
+ }
926
+ const result = this._matchNodeNext(branch, new Set([nextNode]), {
927
+ combo: nextCombo,
928
+ index: index + 1
929
+ });
930
+ if (result) {
931
+ return result;
2900
932
  }
2901
- const [nextNode] = sortNodes(nextNodes);
2902
- return nextNode;
2903
933
  }
2904
- return this._matchNodeNext(branch, nextNodes, {
2905
- combo: nextCombo,
2906
- index: index + 1
2907
- });
2908
934
  }
2909
935
  return null;
2910
936
  };
2911
937
 
2912
938
  /**
2913
- * Matches a node in the 'previous' direction.
939
+ * Recursively checks for a valid backward path.
2914
940
  * @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.
941
+ * @param {object} node - The starting node.
942
+ * @param {Array.<object>} branch - The selector branch.
943
+ * @param {number} index - The current branch index.
944
+ * @param {object} opt - The match options.
945
+ * @returns {boolean} True if a valid path exists, otherwise false.
2920
946
  */
2921
- _matchNodePrev = (branch, node, opt = {}) => {
2922
- const { index } = opt;
947
+ _hasValidPathPrev = (node, branch, index, opt) => {
948
+ if (index < 0) {
949
+ return true;
950
+ }
2923
951
  const twig = branch[index];
2924
- const nextNodes = this._getCombinedNodes(twig, [node], DIR_PREV);
2925
- if (nextNodes.length) {
2926
- if (index === 0) {
2927
- return node;
952
+ const { combo, leaves } = twig;
953
+ const comboName = combo.name;
954
+ if (comboName === '+') {
955
+ const refNode = node.previousElementSibling;
956
+ if (refNode && this.matchLeaves(leaves, refNode, opt)) {
957
+ if (this._hasValidPathPrev(refNode, branch, index - 1, opt)) {
958
+ return true;
959
+ }
2928
960
  }
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;
961
+ } else if (comboName === '~') {
962
+ let refNode = node.previousElementSibling;
963
+ while (refNode) {
964
+ if (this.matchLeaves(leaves, refNode, opt)) {
965
+ if (this._hasValidPathPrev(refNode, branch, index - 1, opt)) {
966
+ return true;
967
+ }
968
+ }
969
+ refNode = refNode.previousElementSibling;
970
+ }
971
+ } else if (comboName === '>') {
972
+ const parentNode = node.parentNode;
973
+ if (parentNode && this.matchLeaves(leaves, parentNode, opt)) {
974
+ if (this._hasValidPathPrev(parentNode, branch, index - 1, opt)) {
975
+ return true;
2936
976
  }
2937
977
  }
2938
- if (matched) {
2939
- return node;
978
+ } else {
979
+ let refNode = node.parentNode;
980
+ while (refNode) {
981
+ if (this.matchLeaves(leaves, refNode, opt)) {
982
+ if (this._hasValidPathPrev(refNode, branch, index - 1, opt)) {
983
+ return true;
984
+ }
985
+ }
986
+ refNode = refNode.parentNode;
2940
987
  }
2941
988
  }
2942
- return null;
989
+ return false;
2943
990
  };
2944
991
 
2945
992
  /**
2946
- * Processes a complex selector branch to find all matching nodes.
993
+ * Processes complex branch for all matches.
2947
994
  * @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.
995
+ * @param {Array.<object>} branch - The selector branch.
996
+ * @param {Array.<object>} entryNodes - The entry nodes.
997
+ * @param {string} dir - The traversal direction.
998
+ * @returns {Set.<object>} Set of matched nodes.
2952
999
  */
2953
1000
  _processComplexBranchAll = (branch, entryNodes, dir) => {
2954
1001
  const matchedNodes = new Set();
@@ -2956,42 +1003,29 @@ export class Finder {
2956
1003
  const lastIndex = branchLen - 1;
2957
1004
  if (dir === DIR_NEXT) {
2958
1005
  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;
1006
+ const dfs = (node, index, currentCombo) => {
1007
+ const { combo: nextCombo, leaves } = branch[index];
1008
+ const twig = { combo: currentCombo, leaves };
1009
+ for (const nextNode of this.yieldCombinatorMatches(twig, node, {
1010
+ dir
1011
+ })) {
1012
+ if (index === lastIndex) {
1013
+ matchedNodes.add(nextNode);
2974
1014
  } else {
2975
- break;
1015
+ dfs(nextNode, index + 1, nextCombo);
2976
1016
  }
2977
1017
  }
1018
+ };
1019
+ for (const node of entryNodes) {
1020
+ dfs(node, 1, firstCombo);
2978
1021
  }
2979
- // DIR_PREV
2980
1022
  } 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
- }
1023
+ for (let i = 0, len = entryNodes.length; i < len; i++) {
1024
+ const node = entryNodes[i];
1025
+ if (
1026
+ this._hasValidPathPrev(node, branch, lastIndex - 1, this.matchOpts)
1027
+ ) {
1028
+ matchedNodes.add(node);
2995
1029
  }
2996
1030
  }
2997
1031
  }
@@ -2999,18 +1033,17 @@ export class Finder {
2999
1033
  };
3000
1034
 
3001
1035
  /**
3002
- * Processes a complex selector branch to find the first matching node.
1036
+ * Processes complex branch for the first match.
3003
1037
  * @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.
1038
+ * @param {Array.<object>} branch - The selector branch.
1039
+ * @param {Array.<object>} entryNodes - The entry nodes.
1040
+ * @param {string} dir - The traversal direction.
1041
+ * @param {string} targetType - The target type.
1042
+ * @returns {object|null} The matched node or null.
3009
1043
  */
3010
1044
  _processComplexBranchFirst = (branch, entryNodes, dir, targetType) => {
3011
1045
  const branchLen = branch.length;
3012
1046
  const lastIndex = branchLen - 1;
3013
- // DIR_NEXT logic for finding the first match.
3014
1047
  if (dir === DIR_NEXT) {
3015
1048
  const { combo: entryCombo } = branch[0];
3016
1049
  for (const node of entryNodes) {
@@ -3019,11 +1052,8 @@ export class Finder {
3019
1052
  index: 1
3020
1053
  });
3021
1054
  if (matchedNode) {
3022
- if (this.#node.nodeType === ELEMENT_NODE) {
3023
- if (
3024
- matchedNode !== this.#node &&
3025
- this.#node.contains(matchedNode)
3026
- ) {
1055
+ if (this.node.nodeType === ELEMENT_NODE) {
1056
+ if (matchedNode !== this.node && this.node.contains(matchedNode)) {
3027
1057
  return matchedNode;
3028
1058
  }
3029
1059
  } else {
@@ -3031,10 +1061,9 @@ export class Finder {
3031
1061
  }
3032
1062
  }
3033
1063
  }
3034
- // Fallback logic if no direct match found.
3035
1064
  const { leaves: entryLeaves } = branch[0];
3036
1065
  const [entryNode] = entryNodes;
3037
- if (this.#node.contains(entryNode)) {
1066
+ if (this.node.contains(entryNode)) {
3038
1067
  let [refNode] = this._findNodeWalker(entryLeaves, entryNode, {
3039
1068
  targetType
3040
1069
  });
@@ -3044,10 +1073,10 @@ export class Finder {
3044
1073
  index: 1
3045
1074
  });
3046
1075
  if (matchedNode) {
3047
- if (this.#node.nodeType === ELEMENT_NODE) {
1076
+ if (this.node.nodeType === ELEMENT_NODE) {
3048
1077
  if (
3049
- matchedNode !== this.#node &&
3050
- this.#node.contains(matchedNode)
1078
+ matchedNode !== this.node &&
1079
+ this.node.contains(matchedNode)
3051
1080
  ) {
3052
1081
  return matchedNode;
3053
1082
  }
@@ -3061,28 +1090,30 @@ export class Finder {
3061
1090
  });
3062
1091
  }
3063
1092
  }
3064
- // DIR_PREV logic for finding the first match.
3065
1093
  } 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;
1094
+ for (let i = 0, len = entryNodes.length; i < len; i++) {
1095
+ const node = entryNodes[i];
1096
+ if (
1097
+ this._hasValidPathPrev(node, branch, lastIndex - 1, this.matchOpts)
1098
+ ) {
1099
+ return node;
3072
1100
  }
3073
1101
  }
3074
- // Fallback for TARGET_FIRST.
3075
1102
  if (targetType === TARGET_FIRST) {
3076
1103
  const { leaves: entryLeaves } = branch[lastIndex];
3077
- const [entryNode] = entryNodes;
1104
+ const entryNode = entryNodes[0];
3078
1105
  let [refNode] = this._findNodeWalker(entryLeaves, entryNode, {
3079
1106
  targetType
3080
1107
  });
3081
1108
  while (refNode) {
3082
- const matchedNode = this._matchNodePrev(branch, refNode, {
3083
- index: lastIndex - 1
3084
- });
3085
- if (matchedNode) {
1109
+ if (
1110
+ this._hasValidPathPrev(
1111
+ refNode,
1112
+ branch,
1113
+ lastIndex - 1,
1114
+ this.matchOpts
1115
+ )
1116
+ ) {
3086
1117
  return refNode;
3087
1118
  }
3088
1119
  [refNode] = this._findNodeWalker(entryLeaves, refNode, {
@@ -3094,130 +1125,4 @@ export class Finder {
3094
1125
  }
3095
1126
  return null;
3096
1127
  };
3097
-
3098
- /**
3099
- * Finds matched nodes.
3100
- * @param {string} targetType - The target type.
3101
- * @returns {Set.<object>} A collection of matched nodes.
3102
- */
3103
- find = targetType => {
3104
- let collection;
3105
- try {
3106
- collection = this._collectNodes(targetType);
3107
- } 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
- }
3115
- return {
3116
- pseudoElement,
3117
- match: false,
3118
- ast: this.#selectorAST ?? null
3119
- };
3120
- } else {
3121
- throw e;
3122
- }
3123
- }
3124
- const [[...branches], collectedNodes] = collection;
3125
- const l = branches.length;
3126
- let sort = l > 1 && targetType === TARGET_ALL;
3127
- let nodes = new Set();
3128
- for (let i = 0; i < l; i++) {
3129
- const { branch, dir, find } = branches[i];
3130
- if (!branch.length || !find) {
3131
- continue;
3132
- }
3133
- const entryNodes = collectedNodes[i];
3134
- const lastIndex = branch.length - 1;
3135
- // Handle simple selectors (no combinators).
3136
- if (lastIndex === 0) {
3137
- if (
3138
- (targetType === TARGET_ALL || targetType === TARGET_FIRST) &&
3139
- this.#node.nodeType === ELEMENT_NODE
3140
- ) {
3141
- 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;
3146
- }
3147
- }
3148
- }
3149
- } else if (targetType === TARGET_ALL) {
3150
- if (nodes.size) {
3151
- for (const node of entryNodes) {
3152
- nodes.add(node);
3153
- }
3154
- sort = true;
3155
- } else {
3156
- nodes = new Set(entryNodes);
3157
- }
3158
- } else {
3159
- if (entryNodes.length) {
3160
- nodes.add(entryNodes[0]);
3161
- }
3162
- }
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;
3178
- }
3179
- } else {
3180
- const matchedNode = this._processComplexBranchFirst(
3181
- branch,
3182
- entryNodes,
3183
- dir,
3184
- targetType
3185
- );
3186
- if (matchedNode) {
3187
- nodes.add(matchedNode);
3188
- }
3189
- }
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
- } else {
3198
- pseudoElement = null;
3199
- }
3200
- return {
3201
- match,
3202
- pseudoElement,
3203
- ast: this.#selectorAST
3204
- };
3205
- }
3206
- if (targetType === TARGET_FIRST || targetType === TARGET_ALL) {
3207
- nodes.delete(this.#node);
3208
- }
3209
- if ((sort || targetType === TARGET_FIRST) && nodes.size > 1) {
3210
- return new Set(sortNodes(nodes));
3211
- }
3212
- return nodes;
3213
- };
3214
-
3215
- /**
3216
- * Gets AST for selector.
3217
- * @param {string} selector - The selector text.
3218
- * @returns {object} The AST for the selector.
3219
- */
3220
- getAST = selector => {
3221
- return parseSelector(selector);
3222
- };
3223
1128
  }