@asamuzakjp/dom-selector 7.0.4 → 7.0.6

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/package.json CHANGED
@@ -32,24 +32,25 @@
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/css-tree": "^2.3.11",
35
+ "@types/node": "^25.5.2",
35
36
  "benchmark": "^2.1.4",
36
37
  "c8": "^11.0.0",
37
38
  "chai": "^6.2.2",
38
39
  "commander": "^14.0.3",
39
40
  "eslint": "^9.39.4",
40
41
  "eslint-config-prettier": "^10.1.8",
41
- "eslint-plugin-jsdoc": "^62.8.0",
42
+ "eslint-plugin-jsdoc": "^62.9.0",
42
43
  "eslint-plugin-prettier": "^5.5.5",
43
44
  "eslint-plugin-regexp": "^3.1.0",
44
- "eslint-plugin-unicorn": "^63.0.0",
45
+ "eslint-plugin-unicorn": "^64.0.0",
45
46
  "globals": "^17.4.0",
46
47
  "jsdom": "^29.0.1",
47
48
  "mocha": "^11.7.5",
48
49
  "neostandard": "^0.13.0",
49
50
  "prettier": "^3.8.1",
50
51
  "sinon": "^21.0.3",
51
- "typescript": "^5.9.3",
52
- "wpt-runner": "^6.1.0"
52
+ "typescript": "^6.0.2",
53
+ "wpt-runner": "^7.0.0"
53
54
  },
54
55
  "overrides": {
55
56
  "c8": {
@@ -71,5 +72,5 @@
71
72
  "engines": {
72
73
  "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
73
74
  },
74
- "version": "7.0.4"
75
+ "version": "7.0.6"
75
76
  }
package/src/js/finder.js CHANGED
@@ -114,6 +114,7 @@ export class Finder {
114
114
  #documentURL;
115
115
  #event;
116
116
  #eventHandlers;
117
+ #filterLeavesCache;
117
118
  #focus;
118
119
  #invalidate;
119
120
  #invalidateResults;
@@ -160,6 +161,7 @@ export class Finder {
160
161
  handler: this._handleMouseEvent
161
162
  }
162
163
  ]);
164
+ this.#filterLeavesCache = new WeakMap();
163
165
  this._registerEventListeners();
164
166
  this.clearResults(true);
165
167
  }
@@ -233,6 +235,7 @@ export class Finder {
233
235
  this.#invalidateResults = new WeakMap();
234
236
  if (all) {
235
237
  this.#results = new WeakMap();
238
+ this.#filterLeavesCache = new WeakMap();
236
239
  }
237
240
  };
238
241
 
@@ -368,6 +371,7 @@ export class Finder {
368
371
  ast = item.ast;
369
372
  this.#descendant = item.descendant;
370
373
  this.#invalidate = item.invalidate;
374
+ this.#selectorAST = item.selectorAST;
371
375
  }
372
376
  }
373
377
  if (ast) {
@@ -403,7 +407,8 @@ export class Finder {
403
407
  cachedItem.set(`${selector}`, {
404
408
  ast,
405
409
  descendant: this.#descendant,
406
- invalidate: this.#invalidate
410
+ invalidate: this.#invalidate,
411
+ selectorAST: this.#selectorAST
407
412
  });
408
413
  this.#documentCache.set(this.#document, cachedItem);
409
414
  // Initialize nodes array for each branch.
@@ -460,8 +465,7 @@ export class Finder {
460
465
  */
461
466
  _getFilteredChildren = (parentNode, selectorBranches, opt) => {
462
467
  const children = [];
463
- const walker = this._createTreeWalker(parentNode, { force: true });
464
- let childNode = walker.firstChild();
468
+ let childNode = parentNode.firstElementChild;
465
469
  while (childNode) {
466
470
  if (selectorBranches) {
467
471
  let isMatch = false;
@@ -483,7 +487,7 @@ export class Finder {
483
487
  } else {
484
488
  children.push(childNode);
485
489
  }
486
- childNode = walker.nextSibling();
490
+ childNode = childNode.nextElementSibling;
487
491
  }
488
492
  return children;
489
493
  };
@@ -553,8 +557,7 @@ export class Finder {
553
557
  return new Set();
554
558
  }
555
559
  const typedSiblings = [];
556
- const walker = this._createTreeWalker(parentNode, { force: true });
557
- let sibling = walker.firstChild();
560
+ let sibling = parentNode.firstElementChild;
558
561
  while (sibling) {
559
562
  if (
560
563
  sibling.localName === node.localName &&
@@ -563,7 +566,7 @@ export class Finder {
563
566
  ) {
564
567
  typedSiblings.push(sibling);
565
568
  }
566
- sibling = walker.nextSibling();
569
+ sibling = sibling.nextElementSibling;
567
570
  }
568
571
  const matchedNodes = filterNodesByAnB(typedSiblings, anb);
569
572
  return new Set(matchedNodes);
@@ -2007,6 +2010,21 @@ export class Finder {
2007
2010
  return bool;
2008
2011
  };
2009
2012
 
2013
+ /**
2014
+ * Returns a cached slice of the leaves array (excluding the first item).
2015
+ * @private
2016
+ * @param {Array.<object>} leaves - The original AST leaves array.
2017
+ * @returns {Array.<object>} The filtered leaves.
2018
+ */
2019
+ _getFilterLeaves = leaves => {
2020
+ if (this.#filterLeavesCache.has(leaves)) {
2021
+ return this.#filterLeavesCache.get(leaves);
2022
+ }
2023
+ const filterLeaves = leaves.slice(1);
2024
+ this.#filterLeavesCache.set(leaves, filterLeaves);
2025
+ return filterLeaves;
2026
+ };
2027
+
2010
2028
  /**
2011
2029
  * Traverses all descendant nodes and collects matches.
2012
2030
  * @private
@@ -2038,7 +2056,8 @@ export class Finder {
2038
2056
  * @returns {Set.<object>} A collection of matched nodes.
2039
2057
  */
2040
2058
  _findDescendantNodes = (leaves, baseNode, opt) => {
2041
- const [leaf, ...filterLeaves] = leaves;
2059
+ const [leaf] = leaves;
2060
+ const filterLeaves = this._getFilterLeaves(leaves);
2042
2061
  const { type: leafType } = leaf;
2043
2062
  switch (leafType) {
2044
2063
  case ID_SELECTOR: {
@@ -2360,7 +2379,8 @@ export class Finder {
2360
2379
  */
2361
2380
  _findEntryNodesForId = (twig, targetType, opt = {}) => {
2362
2381
  const { leaves } = twig;
2363
- const [leaf, ...filterLeaves] = leaves;
2382
+ const [leaf] = leaves;
2383
+ const filterLeaves = this._getFilterLeaves(leaves);
2364
2384
  const { complex, precede } = opt;
2365
2385
  let nodes = [];
2366
2386
  let filtered = false;
@@ -2453,7 +2473,8 @@ export class Finder {
2453
2473
  */
2454
2474
  _findEntryNodesForOther = (twig, targetType, opt = {}) => {
2455
2475
  const { leaves } = twig;
2456
- const [leaf, ...filterLeaves] = leaves;
2476
+ const [leaf] = leaves;
2477
+ const filterLeaves = this._getFilterLeaves(leaves);
2457
2478
  const { complex, precede } = opt;
2458
2479
  let nodes = [];
2459
2480
  let filtered = false;
@@ -2529,7 +2550,8 @@ export class Finder {
2529
2550
  */
2530
2551
  _findEntryNodes = (twig, targetType, opt = {}) => {
2531
2552
  const { leaves } = twig;
2532
- const [leaf, ...filterLeaves] = leaves;
2553
+ const [leaf] = leaves;
2554
+ const filterLeaves = this._getFilterLeaves(leaves);
2533
2555
  const { complex = false, dir = DIR_PREV } = opt;
2534
2556
  const precede =
2535
2557
  dir === DIR_NEXT &&
package/src/js/matcher.js CHANGED
@@ -136,6 +136,22 @@ export const matchDirectionPseudoClass = (ast, node) => {
136
136
  * @returns {boolean} - True if the language matches, otherwise false.
137
137
  */
138
138
  export const matchLanguagePseudoClass = (ast, node) => {
139
+ // Get the effective language attribute for the current node.
140
+ const elementLang = getLanguageAttribute(node);
141
+ // If the element has no language, it cannot match a specific pattern.
142
+ if (elementLang === null) {
143
+ return false;
144
+ }
145
+ // Use cached regex.
146
+ if (ast._langRegex !== undefined) {
147
+ if (ast._langPattern === '*') {
148
+ return elementLang !== '';
149
+ }
150
+ if (ast._langRegex === null) {
151
+ return false;
152
+ }
153
+ return ast._langRegex.test(elementLang);
154
+ }
139
155
  const { name, type, value } = ast;
140
156
  let langPattern;
141
157
  // Determine the language pattern from the AST.
@@ -144,35 +160,31 @@ export const matchLanguagePseudoClass = (ast, node) => {
144
160
  } else if (type === IDENT && name) {
145
161
  langPattern = unescapeSelector(name);
146
162
  }
163
+ // Cache lang pattern.
164
+ ast._langPattern = langPattern;
147
165
  // If no valid language pattern is provided, it cannot match.
148
166
  if (typeof langPattern !== 'string') {
149
- return false;
150
- }
151
- // Get the effective language attribute for the current node.
152
- const elementLang = getLanguageAttribute(node);
153
- // If the element has no language, it cannot match a specific pattern.
154
- if (elementLang === null) {
167
+ ast._langRegex = null;
155
168
  return false;
156
169
  }
157
170
  // Handle the universal selector '*' for :lang.
158
171
  if (langPattern === '*') {
159
- // It matches any language unless attribute is not empty.
172
+ ast._langRegex = null;
160
173
  return elementLang !== '';
161
174
  }
162
175
  // Validate the provided language pattern structure.
163
176
  if (!REG_LANG_VALID.test(langPattern)) {
177
+ ast._langRegex = null;
164
178
  return false;
165
179
  }
166
180
  // Build a regex for extended language range matching.
167
181
  let matcherRegex;
168
182
  if (langPattern.indexOf('-') > -1) {
169
- // Handle complex patterns with wildcards and sub-tags (e.g., '*-US').
170
183
  const [langMain, langSub, ...langRest] = langPattern.split('-');
171
184
  const extendedMain =
172
185
  langMain === '*' ? `${ALPHA_NUM}${LANG_PART}` : `${langMain}${LANG_PART}`;
173
186
  const extendedSub = `-${langSub}${LANG_PART}`;
174
187
  let extendedRest = '';
175
- // Use a standard for loop for performance as per the rules.
176
188
  for (let i = 0; i < langRest.length; i++) {
177
189
  extendedRest += `-${langRest[i]}${LANG_PART}`;
178
190
  }
@@ -181,9 +193,9 @@ export const matchLanguagePseudoClass = (ast, node) => {
181
193
  'i'
182
194
  );
183
195
  } else {
184
- // Handle simple language patterns (e.g., 'en').
185
196
  matcherRegex = new RegExp(`^${langPattern}${LANG_PART}$`, 'i');
186
197
  }
198
+ ast._langRegex = matcherRegex;
187
199
  // Test the element's language against the constructed regex.
188
200
  return matcherRegex.test(elementLang);
189
201
  };
@@ -452,9 +464,15 @@ export const matchAttributeSelector = (ast, node, opt = {}) => {
452
464
  }
453
465
  case '~=': {
454
466
  if (attrValue && typeof attrValue === 'string') {
467
+ if (/\s/.test(attrValue)) {
468
+ return false;
469
+ }
470
+ if (ast._tildeTarget === undefined) {
471
+ ast._tildeTarget = ` ${attrValue} `;
472
+ }
473
+ const target = ast._tildeTarget;
455
474
  for (const value of attrValues) {
456
- const item = new Set(value.split(/\s+/));
457
- if (item.has(attrValue)) {
475
+ if (` ${value.replace(/[\t\r\n\f]/g, ' ')} `.includes(target)) {
458
476
  return true;
459
477
  }
460
478
  }
@@ -1,12 +1,12 @@
1
1
  export class Finder {
2
2
  constructor(window: object);
3
3
  onError: (e: Error, opt?: {
4
- noexcept?: boolean;
4
+ noexcept?: boolean | undefined;
5
5
  }) => void;
6
6
  setup: (selector: string, node: object, opt?: {
7
- check?: boolean;
8
- noexcept?: boolean;
9
- warn?: boolean;
7
+ check?: boolean | undefined;
8
+ noexcept?: boolean | undefined;
9
+ warn?: boolean | undefined;
10
10
  }) => object;
11
11
  clearResults: (all?: boolean) => void;
12
12
  private _handleFocusEvent;
@@ -32,6 +32,7 @@ export class Finder {
32
32
  private _matchSelectorForShadowRoot;
33
33
  private _matchSelector;
34
34
  private _matchLeaves;
35
+ private _getFilterLeaves;
35
36
  private _traverseAllDescendants;
36
37
  private _findDescendantNodes;
37
38
  private _collectCombinatorMatches;
@@ -1,16 +1,16 @@
1
1
  export function matchPseudoElementSelector(astName: string, astType: string, opt?: {
2
- forgive?: boolean;
3
- warn?: boolean;
2
+ forgive?: boolean | undefined;
3
+ warn?: boolean | undefined;
4
4
  }): void;
5
5
  export function matchDirectionPseudoClass(ast: object, node: object): boolean;
6
6
  export function matchLanguagePseudoClass(ast: object, node: object): boolean;
7
7
  export function matchDisabledPseudoClass(astName: string, node: object): boolean;
8
8
  export function matchReadOnlyPseudoClass(astName: string, node: object): boolean;
9
9
  export function matchAttributeSelector(ast: object, node: object, opt?: {
10
- check?: boolean;
11
- forgive?: boolean;
10
+ check?: boolean | undefined;
11
+ forgive?: boolean | undefined;
12
12
  }): boolean;
13
13
  export function matchTypeSelector(ast: object, node: object, opt?: {
14
- check?: boolean;
15
- forgive?: boolean;
14
+ check?: boolean | undefined;
15
+ forgive?: boolean | undefined;
16
16
  }): boolean;
@@ -6,7 +6,7 @@ export function findLogicalWithNestedHas(leaf: object): object | null;
6
6
  export function filterNodesByAnB(nodes: Array<object>, anb: {
7
7
  a: number;
8
8
  b: number;
9
- reverse?: boolean;
9
+ reverse?: boolean | undefined;
10
10
  }): Array<object>;
11
11
  export function resolveContent(node: object): Array<object | boolean>;
12
12
  export function traverseNode(node: object, walker: object, force?: boolean): object | null;