@asamuzakjp/dom-selector 7.0.5 → 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
@@ -72,5 +72,5 @@
72
72
  "engines": {
73
73
  "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
74
74
  },
75
- "version": "7.0.5"
75
+ "version": "7.0.6"
76
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
 
@@ -462,8 +465,7 @@ export class Finder {
462
465
  */
463
466
  _getFilteredChildren = (parentNode, selectorBranches, opt) => {
464
467
  const children = [];
465
- const walker = this._createTreeWalker(parentNode, { force: true });
466
- let childNode = walker.firstChild();
468
+ let childNode = parentNode.firstElementChild;
467
469
  while (childNode) {
468
470
  if (selectorBranches) {
469
471
  let isMatch = false;
@@ -485,7 +487,7 @@ export class Finder {
485
487
  } else {
486
488
  children.push(childNode);
487
489
  }
488
- childNode = walker.nextSibling();
490
+ childNode = childNode.nextElementSibling;
489
491
  }
490
492
  return children;
491
493
  };
@@ -555,8 +557,7 @@ export class Finder {
555
557
  return new Set();
556
558
  }
557
559
  const typedSiblings = [];
558
- const walker = this._createTreeWalker(parentNode, { force: true });
559
- let sibling = walker.firstChild();
560
+ let sibling = parentNode.firstElementChild;
560
561
  while (sibling) {
561
562
  if (
562
563
  sibling.localName === node.localName &&
@@ -565,7 +566,7 @@ export class Finder {
565
566
  ) {
566
567
  typedSiblings.push(sibling);
567
568
  }
568
- sibling = walker.nextSibling();
569
+ sibling = sibling.nextElementSibling;
569
570
  }
570
571
  const matchedNodes = filterNodesByAnB(typedSiblings, anb);
571
572
  return new Set(matchedNodes);
@@ -2009,6 +2010,21 @@ export class Finder {
2009
2010
  return bool;
2010
2011
  };
2011
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
+
2012
2028
  /**
2013
2029
  * Traverses all descendant nodes and collects matches.
2014
2030
  * @private
@@ -2040,7 +2056,8 @@ export class Finder {
2040
2056
  * @returns {Set.<object>} A collection of matched nodes.
2041
2057
  */
2042
2058
  _findDescendantNodes = (leaves, baseNode, opt) => {
2043
- const [leaf, ...filterLeaves] = leaves;
2059
+ const [leaf] = leaves;
2060
+ const filterLeaves = this._getFilterLeaves(leaves);
2044
2061
  const { type: leafType } = leaf;
2045
2062
  switch (leafType) {
2046
2063
  case ID_SELECTOR: {
@@ -2362,7 +2379,8 @@ export class Finder {
2362
2379
  */
2363
2380
  _findEntryNodesForId = (twig, targetType, opt = {}) => {
2364
2381
  const { leaves } = twig;
2365
- const [leaf, ...filterLeaves] = leaves;
2382
+ const [leaf] = leaves;
2383
+ const filterLeaves = this._getFilterLeaves(leaves);
2366
2384
  const { complex, precede } = opt;
2367
2385
  let nodes = [];
2368
2386
  let filtered = false;
@@ -2455,7 +2473,8 @@ export class Finder {
2455
2473
  */
2456
2474
  _findEntryNodesForOther = (twig, targetType, opt = {}) => {
2457
2475
  const { leaves } = twig;
2458
- const [leaf, ...filterLeaves] = leaves;
2476
+ const [leaf] = leaves;
2477
+ const filterLeaves = this._getFilterLeaves(leaves);
2459
2478
  const { complex, precede } = opt;
2460
2479
  let nodes = [];
2461
2480
  let filtered = false;
@@ -2531,7 +2550,8 @@ export class Finder {
2531
2550
  */
2532
2551
  _findEntryNodes = (twig, targetType, opt = {}) => {
2533
2552
  const { leaves } = twig;
2534
- const [leaf, ...filterLeaves] = leaves;
2553
+ const [leaf] = leaves;
2554
+ const filterLeaves = this._getFilterLeaves(leaves);
2535
2555
  const { complex = false, dir = DIR_PREV } = opt;
2536
2556
  const precede =
2537
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
  }
@@ -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;