@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 +6 -5
- package/src/js/finder.js +33 -11
- package/src/js/matcher.js +30 -12
- package/types/js/finder.d.ts +5 -4
- package/types/js/matcher.d.ts +6 -6
- package/types/js/utility.d.ts +1 -1
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.
|
|
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": "^
|
|
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": "^
|
|
52
|
-
"wpt-runner": "^
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
457
|
-
if (item.has(attrValue)) {
|
|
475
|
+
if (` ${value.replace(/[\t\r\n\f]/g, ' ')} `.includes(target)) {
|
|
458
476
|
return true;
|
|
459
477
|
}
|
|
460
478
|
}
|
package/types/js/finder.d.ts
CHANGED
|
@@ -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;
|
package/types/js/matcher.d.ts
CHANGED
|
@@ -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;
|
package/types/js/utility.d.ts
CHANGED
|
@@ -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;
|