@asamuzakjp/dom-selector 0.21.1 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/package.json +1 -1
- package/src/index.js +6 -6
- package/src/js/dom-util.js +0 -4
- package/src/js/matcher.js +391 -243
- package/src/js/parser.js +2 -2
- package/types/index.d.ts +2 -2
- package/types/js/matcher.d.ts +5 -5
package/README.md
CHANGED
|
@@ -55,21 +55,21 @@ closest - same functionality as [Element.closest()][65]
|
|
|
55
55
|
Returns **[object][60]?** matched node
|
|
56
56
|
|
|
57
57
|
|
|
58
|
-
### querySelector(selector,
|
|
58
|
+
### querySelector(selector, node, opt)
|
|
59
59
|
|
|
60
60
|
querySelector - same functionality as [Document.querySelector()][66], [DocumentFragment.querySelector()][67], [Element.querySelector()][68]
|
|
61
61
|
|
|
62
62
|
#### Parameters
|
|
63
63
|
|
|
64
64
|
- `selector` **[string][59]** CSS selector
|
|
65
|
-
- `
|
|
65
|
+
- `node` **[object][60]** Document, DocumentFragment or Element node
|
|
66
66
|
- `opt` **[object][60]?** options
|
|
67
67
|
- `opt.warn` **[boolean][61]?** console warn e.g. unsupported pseudo-class
|
|
68
68
|
|
|
69
69
|
Returns **[object][60]?** matched node
|
|
70
70
|
|
|
71
71
|
|
|
72
|
-
### querySelectorAll(selector,
|
|
72
|
+
### querySelectorAll(selector, node, opt)
|
|
73
73
|
|
|
74
74
|
querySelectorAll - same functionality as [Document.querySelectorAll()][69], [DocumentFragment.querySelectorAll()][70], [Element.querySelectorAll()][71]
|
|
75
75
|
**NOTE**: returns Array, not NodeList
|
|
@@ -77,7 +77,7 @@ querySelectorAll - same functionality as [Document.querySelectorAll()][69], [Doc
|
|
|
77
77
|
#### Parameters
|
|
78
78
|
|
|
79
79
|
- `selector` **[string][59]** CSS selector
|
|
80
|
-
- `
|
|
80
|
+
- `node` **[object][60]** Document, DocumentFragment or Element node
|
|
81
81
|
- `opt` **[object][60]?** options
|
|
82
82
|
- `opt.sort` **[boolean][61]?** sort matched nodes
|
|
83
83
|
- `opt.warn` **[boolean][61]?** console warn e.g. unsupported pseudo-class
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -33,23 +33,23 @@ export const closest = (selector, node, opt) =>
|
|
|
33
33
|
/**
|
|
34
34
|
* querySelector
|
|
35
35
|
* @param {string} selector - CSS selector
|
|
36
|
-
* @param {object}
|
|
36
|
+
* @param {object} node - Document, DocumentFragment or Element node
|
|
37
37
|
* @param {object} [opt] - options
|
|
38
38
|
* @param {boolean} [opt.warn] - console warn e.g. unsupported pseudo-class
|
|
39
39
|
* @returns {?object} - matched node
|
|
40
40
|
*/
|
|
41
|
-
export const querySelector = (selector,
|
|
42
|
-
new Matcher(selector,
|
|
41
|
+
export const querySelector = (selector, node, opt) =>
|
|
42
|
+
new Matcher(selector, node, opt).querySelector();
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* querySelectorAll
|
|
46
46
|
* NOTE: returns Array, not NodeList
|
|
47
47
|
* @param {string} selector - CSS selector
|
|
48
|
-
* @param {object}
|
|
48
|
+
* @param {object} node - Document, DocumentFragment or Element node
|
|
49
49
|
* @param {object} [opt] - options
|
|
50
50
|
* @param {boolean} [opt.sort] - sort matched nodes
|
|
51
51
|
* @param {boolean} [opt.warn] - console warn e.g. unsupported pseudo-class
|
|
52
52
|
* @returns {Array.<object|undefined>} - array of matched nodes
|
|
53
53
|
*/
|
|
54
|
-
export const querySelectorAll = (selector,
|
|
55
|
-
new Matcher(selector,
|
|
54
|
+
export const querySelectorAll = (selector, node, opt) =>
|
|
55
|
+
new Matcher(selector, node, opt).querySelectorAll();
|
package/src/js/dom-util.js
CHANGED
|
@@ -98,10 +98,6 @@ export const selectorToNodeProps = (selector, node) => {
|
|
|
98
98
|
if (selector && typeof selector === 'string') {
|
|
99
99
|
if (/\|/.test(selector)) {
|
|
100
100
|
[prefix, tagName] = selector.split('|');
|
|
101
|
-
if (prefix && prefix !== '*' &&
|
|
102
|
-
node && !isNamespaceDeclared(prefix, node)) {
|
|
103
|
-
throw new DOMException(`invalid selector ${selector}`, SYNTAX_ERR);
|
|
104
|
-
}
|
|
105
101
|
} else {
|
|
106
102
|
prefix = '*';
|
|
107
103
|
tagName = selector;
|
package/src/js/matcher.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
/* import */
|
|
6
6
|
import isCustomElementName from 'is-potential-custom-element-name';
|
|
7
7
|
import {
|
|
8
|
-
isContentEditable, isSameOrDescendant,
|
|
8
|
+
isContentEditable, isNamespaceDeclared, isSameOrDescendant,
|
|
9
|
+
selectorToNodeProps
|
|
9
10
|
} from './dom-util.js';
|
|
10
11
|
import {
|
|
11
12
|
generateCSS, parseSelector, unescapeSelector, walkAST
|
|
@@ -26,11 +27,16 @@ const TARGET_SELF = 'self';
|
|
|
26
27
|
|
|
27
28
|
/* regexp */
|
|
28
29
|
const DIR_VALUE = /^(?:auto|ltr|rtl)$/;
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
|
|
30
|
+
const FORM_INPUT = /^(?:input|textarea)$/;
|
|
31
|
+
const FORM_PARTS =
|
|
32
|
+
/^(?:(?:fieldse|inpu|selec)t|button|opt(?:group|ion)|textarea)$/;
|
|
33
|
+
const FORM_VALIDITY = /^(?:(?:(?:in|out)pu|selec)t|button|form|textarea)$/;
|
|
34
|
+
const HTML_ANCHOR = /^a(?:rea)?$/;
|
|
32
35
|
const HTML_INTERACT = /^d(?:etails|ialog)$/;
|
|
36
|
+
const INPUT_CHECK = /^(?:checkbox|radio)$/;
|
|
33
37
|
const INPUT_RANGE = /(?:(?:rang|tim)e|date(?:time-local)?|month|number|week)$/;
|
|
38
|
+
const INPUT_RESET = /^(?:button|reset)$/;
|
|
39
|
+
const INPUT_SUBMIT = /^(?:image|submit)$/;
|
|
34
40
|
const INPUT_TEXT = /^(?:(?:emai|te|ur)l|password|search|text)$/;
|
|
35
41
|
const PSEUDO_FUNC = /^(?:(?:ha|i)s|not|where)$/;
|
|
36
42
|
const PSEUDO_NTH = /^nth-(?:last-)?(?:child|of-type)$/;
|
|
@@ -145,7 +151,7 @@ export class Matcher {
|
|
|
145
151
|
break;
|
|
146
152
|
}
|
|
147
153
|
default: {
|
|
148
|
-
throw new TypeError(`Unexpected node
|
|
154
|
+
throw new TypeError(`Unexpected node ${node.nodeName}`);
|
|
149
155
|
}
|
|
150
156
|
}
|
|
151
157
|
return {
|
|
@@ -209,7 +215,7 @@ export class Matcher {
|
|
|
209
215
|
if (item.type === COMBINATOR) {
|
|
210
216
|
const [nextItem] = items;
|
|
211
217
|
if (nextItem.type === COMBINATOR) {
|
|
212
|
-
const msg = `
|
|
218
|
+
const msg = `Invalid combinator ${item.name}${nextItem.name}`;
|
|
213
219
|
throw new DOMException(msg, SYNTAX_ERR);
|
|
214
220
|
}
|
|
215
221
|
branch.push({
|
|
@@ -245,42 +251,6 @@ export class Matcher {
|
|
|
245
251
|
];
|
|
246
252
|
}
|
|
247
253
|
|
|
248
|
-
/**
|
|
249
|
-
* throw DOMExeption on pseudo element selector
|
|
250
|
-
* @param {object} astName - AST name
|
|
251
|
-
* @throws {DOMException}
|
|
252
|
-
* @returns {void}
|
|
253
|
-
*/
|
|
254
|
-
_throwOnPseudoElementSelector(astName) {
|
|
255
|
-
let msg;
|
|
256
|
-
let type;
|
|
257
|
-
switch (astName) {
|
|
258
|
-
case 'after':
|
|
259
|
-
case 'backdrop':
|
|
260
|
-
case 'before':
|
|
261
|
-
case 'cue':
|
|
262
|
-
case 'cue-region':
|
|
263
|
-
case 'first-letter':
|
|
264
|
-
case 'first-line':
|
|
265
|
-
case 'file-selector-button':
|
|
266
|
-
case 'marker':
|
|
267
|
-
case 'part':
|
|
268
|
-
case 'placeholder':
|
|
269
|
-
case 'selection':
|
|
270
|
-
case 'slotted':
|
|
271
|
-
case 'target-text': {
|
|
272
|
-
msg = `Unsupported pseudo-element ::${astName}`;
|
|
273
|
-
type = NOT_SUPPORTED_ERR;
|
|
274
|
-
break;
|
|
275
|
-
}
|
|
276
|
-
default: {
|
|
277
|
-
msg = `Unknown pseudo-element ::${astName}`;
|
|
278
|
-
type = SYNTAX_ERR;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
throw new DOMException(msg, type);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
254
|
/**
|
|
285
255
|
* collect nth child
|
|
286
256
|
* @param {object} anb - An+B options
|
|
@@ -294,84 +264,109 @@ export class Matcher {
|
|
|
294
264
|
_collectNthChild(anb, node) {
|
|
295
265
|
const { a, b, reverse, selector } = anb;
|
|
296
266
|
const { parentNode } = node;
|
|
297
|
-
const
|
|
267
|
+
const matched = new Set();
|
|
268
|
+
let selectorBranches;
|
|
298
269
|
if (selector) {
|
|
299
|
-
let branches;
|
|
300
270
|
if (this.#cache.has(selector)) {
|
|
301
|
-
|
|
271
|
+
selectorBranches = this.#cache.get(selector);
|
|
302
272
|
} else {
|
|
303
|
-
|
|
304
|
-
this.#cache.set(selector,
|
|
273
|
+
selectorBranches = walkAST(selector);
|
|
274
|
+
this.#cache.set(selector, selectorBranches);
|
|
305
275
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
276
|
+
}
|
|
277
|
+
if (parentNode) {
|
|
278
|
+
const arr = [...parentNode.children];
|
|
279
|
+
const l = arr.length;
|
|
280
|
+
if (l) {
|
|
281
|
+
const selectorNodes = new Set();
|
|
282
|
+
if (selectorBranches) {
|
|
283
|
+
const branchesLen = selectorBranches.length;
|
|
284
|
+
for (const refNode of arr) {
|
|
285
|
+
let bool;
|
|
286
|
+
for (let i = 0; i < branchesLen; i++) {
|
|
287
|
+
const leaves = selectorBranches[i];
|
|
288
|
+
bool = this._matchLeaves(leaves, refNode);
|
|
289
|
+
if (!bool) {
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (bool) {
|
|
294
|
+
selectorNodes.add(refNode);
|
|
295
|
+
}
|
|
315
296
|
}
|
|
316
297
|
}
|
|
317
|
-
if (
|
|
318
|
-
|
|
298
|
+
if (reverse) {
|
|
299
|
+
arr.reverse();
|
|
319
300
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
const current = arr[i];
|
|
334
|
-
if (selectorNodes.has(current)) {
|
|
301
|
+
// :first-child, :last-child, :nth-child(0 of S)
|
|
302
|
+
if (a === 0) {
|
|
303
|
+
if (b > 0 && b <= l) {
|
|
304
|
+
if (selectorNodes.size) {
|
|
305
|
+
for (let i = 0; i < l; i++) {
|
|
306
|
+
const current = arr[i];
|
|
307
|
+
if (selectorNodes.has(current)) {
|
|
308
|
+
matched.add(current);
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
} else if (!selector) {
|
|
313
|
+
const current = arr[b - 1];
|
|
335
314
|
matched.add(current);
|
|
336
|
-
break;
|
|
337
315
|
}
|
|
338
316
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
317
|
+
// :nth-child()
|
|
318
|
+
} else {
|
|
319
|
+
let n = 0;
|
|
320
|
+
let nth = b - 1;
|
|
321
|
+
if (a > 0) {
|
|
322
|
+
while (nth < 0) {
|
|
323
|
+
nth += (++n * a);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (nth >= 0 && nth < l) {
|
|
327
|
+
let j = a > 0 ? 0 : b - 1;
|
|
328
|
+
for (let i = 0; i < l && nth >= 0 && nth < l; i++) {
|
|
329
|
+
const current = arr[i];
|
|
330
|
+
if (selectorNodes.size) {
|
|
331
|
+
if (selectorNodes.has(current)) {
|
|
332
|
+
if (j === nth) {
|
|
333
|
+
matched.add(current);
|
|
334
|
+
nth += a;
|
|
335
|
+
}
|
|
336
|
+
if (a > 0) {
|
|
337
|
+
j++;
|
|
338
|
+
} else {
|
|
339
|
+
j--;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
} else if (i === nth) {
|
|
343
|
+
if (!selector) {
|
|
344
|
+
matched.add(current);
|
|
345
|
+
}
|
|
361
346
|
nth += a;
|
|
362
347
|
}
|
|
363
|
-
if (a > 0) {
|
|
364
|
-
j++;
|
|
365
|
-
} else {
|
|
366
|
-
j--;
|
|
367
|
-
}
|
|
368
348
|
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
} else {
|
|
353
|
+
const { root } = this.#root;
|
|
354
|
+
if (root.nodeType === ELEMENT_NODE && node === root && (a + b) === 1) {
|
|
355
|
+
if (selectorBranches) {
|
|
356
|
+
const branchesLen = selectorBranches.length;
|
|
357
|
+
let bool;
|
|
358
|
+
for (let i = 0; i < branchesLen; i++) {
|
|
359
|
+
const leaves = selectorBranches[i];
|
|
360
|
+
bool = this._matchLeaves(leaves, node);
|
|
361
|
+
if (bool) {
|
|
362
|
+
break;
|
|
372
363
|
}
|
|
373
|
-
nth += a;
|
|
374
364
|
}
|
|
365
|
+
if (bool) {
|
|
366
|
+
matched.add(node);
|
|
367
|
+
}
|
|
368
|
+
} else {
|
|
369
|
+
matched.add(node);
|
|
375
370
|
}
|
|
376
371
|
}
|
|
377
372
|
}
|
|
@@ -390,56 +385,65 @@ export class Matcher {
|
|
|
390
385
|
_collectNthOfType(anb, node) {
|
|
391
386
|
const { a, b, reverse } = anb;
|
|
392
387
|
const { localName, parentNode, prefix } = node;
|
|
393
|
-
const arr = [...parentNode.children];
|
|
394
|
-
if (reverse) {
|
|
395
|
-
arr.reverse();
|
|
396
|
-
}
|
|
397
|
-
const l = arr.length;
|
|
398
388
|
const matched = new Set();
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
389
|
+
if (parentNode) {
|
|
390
|
+
const arr = [...parentNode.children];
|
|
391
|
+
const l = arr.length;
|
|
392
|
+
if (l) {
|
|
393
|
+
if (reverse) {
|
|
394
|
+
arr.reverse();
|
|
395
|
+
}
|
|
396
|
+
// :first-of-type, :last-of-type
|
|
397
|
+
if (a === 0) {
|
|
398
|
+
if (b > 0 && b <= l) {
|
|
399
|
+
let j = 0;
|
|
400
|
+
for (let i = 0; i < l; i++) {
|
|
401
|
+
const current = arr[i];
|
|
402
|
+
const { localName: itemLocalName, prefix: itemPrefix } = current;
|
|
403
|
+
if (itemLocalName === localName && itemPrefix === prefix) {
|
|
404
|
+
if (j === b - 1) {
|
|
405
|
+
matched.add(current);
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
j++;
|
|
409
|
+
}
|
|
410
410
|
}
|
|
411
|
-
j++;
|
|
412
411
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
if (a > 0) {
|
|
419
|
-
while (nth < 0) {
|
|
420
|
-
nth += a;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
if (nth >= 0 && nth < l) {
|
|
424
|
-
let j = a > 0 ? 0 : b - 1;
|
|
425
|
-
for (let i = 0; i < l; i++) {
|
|
426
|
-
const current = arr[i];
|
|
427
|
-
const { localName: itemLocalName, prefix: itemPrefix } = current;
|
|
428
|
-
if (itemLocalName === localName && itemPrefix === prefix) {
|
|
429
|
-
if (j === nth) {
|
|
430
|
-
matched.add(current);
|
|
412
|
+
// :nth-of-type()
|
|
413
|
+
} else {
|
|
414
|
+
let nth = b - 1;
|
|
415
|
+
if (a > 0) {
|
|
416
|
+
while (nth < 0) {
|
|
431
417
|
nth += a;
|
|
432
418
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
419
|
+
}
|
|
420
|
+
if (nth >= 0 && nth < l) {
|
|
421
|
+
let j = a > 0 ? 0 : b - 1;
|
|
422
|
+
for (let i = 0; i < l; i++) {
|
|
423
|
+
const current = arr[i];
|
|
424
|
+
const { localName: itemLocalName, prefix: itemPrefix } = current;
|
|
425
|
+
if (itemLocalName === localName && itemPrefix === prefix) {
|
|
426
|
+
if (j === nth) {
|
|
427
|
+
matched.add(current);
|
|
428
|
+
nth += a;
|
|
429
|
+
}
|
|
430
|
+
if (nth < 0 || nth >= l) {
|
|
431
|
+
break;
|
|
432
|
+
} else if (a > 0) {
|
|
433
|
+
j++;
|
|
434
|
+
} else {
|
|
435
|
+
j--;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
439
438
|
}
|
|
440
439
|
}
|
|
441
440
|
}
|
|
442
441
|
}
|
|
442
|
+
} else {
|
|
443
|
+
const { root } = this.#root;
|
|
444
|
+
if (root.nodeType === ELEMENT_NODE && node === root && (a + b) === 1) {
|
|
445
|
+
matched.add(node);
|
|
446
|
+
}
|
|
443
447
|
}
|
|
444
448
|
return matched;
|
|
445
449
|
}
|
|
@@ -489,7 +493,7 @@ export class Matcher {
|
|
|
489
493
|
}
|
|
490
494
|
}
|
|
491
495
|
let matched = new Set();
|
|
492
|
-
if (anbMap.has('a') && anbMap.has('b')
|
|
496
|
+
if (anbMap.has('a') && anbMap.has('b')) {
|
|
493
497
|
if (/^nth-(?:last-)?child$/.test(nthName)) {
|
|
494
498
|
if (selector) {
|
|
495
499
|
anbMap.set('selector', selector);
|
|
@@ -510,6 +514,50 @@ export class Matcher {
|
|
|
510
514
|
return matched;
|
|
511
515
|
}
|
|
512
516
|
|
|
517
|
+
/**
|
|
518
|
+
* match pseudo element selector
|
|
519
|
+
* @param {string} astName - AST name
|
|
520
|
+
* @param {object} opt - options
|
|
521
|
+
* @throws {DOMException}
|
|
522
|
+
* @returns {void}
|
|
523
|
+
*/
|
|
524
|
+
_matchPseudoElementSelector(astName, opt = {}) {
|
|
525
|
+
const { forgive } = opt;
|
|
526
|
+
switch (astName) {
|
|
527
|
+
case 'after':
|
|
528
|
+
case 'backdrop':
|
|
529
|
+
case 'before':
|
|
530
|
+
case 'cue':
|
|
531
|
+
case 'cue-region':
|
|
532
|
+
case 'first-letter':
|
|
533
|
+
case 'first-line':
|
|
534
|
+
case 'file-selector-button':
|
|
535
|
+
case 'marker':
|
|
536
|
+
case 'part':
|
|
537
|
+
case 'placeholder':
|
|
538
|
+
case 'selection':
|
|
539
|
+
case 'slotted':
|
|
540
|
+
case 'target-text': {
|
|
541
|
+
if (this.#warn) {
|
|
542
|
+
throw new DOMException(`Unsupported pseudo-element ::${astName}`,
|
|
543
|
+
NOT_SUPPORTED_ERR);
|
|
544
|
+
}
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
default: {
|
|
548
|
+
if (astName.startsWith('-webkit-')) {
|
|
549
|
+
if (this.#warn) {
|
|
550
|
+
throw new DOMException(`Unsupported pseudo-element ::${astName}`,
|
|
551
|
+
NOT_SUPPORTED_ERR);
|
|
552
|
+
}
|
|
553
|
+
} else if (!forgive) {
|
|
554
|
+
throw new DOMException(`Unknown pseudo-element ::${astName}`,
|
|
555
|
+
SYNTAX_ERR);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
513
561
|
/**
|
|
514
562
|
* match directionality pseudo-class - :dir()
|
|
515
563
|
* @see https://html.spec.whatwg.org/multipage/dom.html#the-dir-attribute
|
|
@@ -534,14 +582,17 @@ export class Matcher {
|
|
|
534
582
|
} else if (nodeDir === 'auto' &&
|
|
535
583
|
(localName === 'textarea' ||
|
|
536
584
|
(localName === 'input' &&
|
|
537
|
-
(!inputType ||
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
585
|
+
(!inputType || INPUT_TEXT.test(inputType))))) {
|
|
586
|
+
if (this.#warn) {
|
|
587
|
+
throw new DOMException('Unsupported pseudo-class :dir()',
|
|
588
|
+
NOT_SUPPORTED_ERR);
|
|
589
|
+
}
|
|
541
590
|
// FIXME:
|
|
542
591
|
} else if (nodeDir === 'auto' || (localName === 'bdi' && !nodeDir)) {
|
|
543
|
-
|
|
544
|
-
|
|
592
|
+
if (this.#warn) {
|
|
593
|
+
throw new DOMException('Unsupported pseudo-class :dir()',
|
|
594
|
+
NOT_SUPPORTED_ERR);
|
|
595
|
+
}
|
|
545
596
|
} else if (!nodeDir) {
|
|
546
597
|
let parent = node.parentNode;
|
|
547
598
|
while (parent) {
|
|
@@ -663,7 +714,8 @@ export class Matcher {
|
|
|
663
714
|
if (nodes.size) {
|
|
664
715
|
if (leaves.length) {
|
|
665
716
|
for (const nextNode of nodes) {
|
|
666
|
-
bool =
|
|
717
|
+
bool =
|
|
718
|
+
this._matchHasPseudoFunc(Object.assign([], leaves), nextNode);
|
|
667
719
|
if (bool) {
|
|
668
720
|
break;
|
|
669
721
|
}
|
|
@@ -678,20 +730,22 @@ export class Matcher {
|
|
|
678
730
|
|
|
679
731
|
/**
|
|
680
732
|
* match logical pseudo-class functions - :has(), :is(), :not(), :where()
|
|
681
|
-
* @param {object}
|
|
733
|
+
* @param {object} astData - AST data
|
|
682
734
|
* @param {object} node - Element node
|
|
683
735
|
* @returns {?object} - matched node
|
|
684
736
|
*/
|
|
685
|
-
_matchLogicalPseudoFunc(
|
|
686
|
-
const {
|
|
737
|
+
_matchLogicalPseudoFunc(astData, node) {
|
|
738
|
+
const {
|
|
739
|
+
astName = '', branches = [], selector = '', twigBranches = []
|
|
740
|
+
} = astData;
|
|
687
741
|
let res;
|
|
688
|
-
const branchLen = branches.length;
|
|
689
742
|
if (astName === 'has') {
|
|
690
743
|
if (selector.includes(':has(')) {
|
|
691
744
|
res = null;
|
|
692
745
|
} else {
|
|
693
746
|
let bool;
|
|
694
|
-
|
|
747
|
+
const l = branches.length;
|
|
748
|
+
for (let i = 0; i < l; i++) {
|
|
695
749
|
const leaves = branches[i];
|
|
696
750
|
bool = this._matchHasPseudoFunc(Object.assign([], leaves), node);
|
|
697
751
|
if (bool) {
|
|
@@ -702,15 +756,45 @@ export class Matcher {
|
|
|
702
756
|
res = node;
|
|
703
757
|
}
|
|
704
758
|
}
|
|
705
|
-
// NOTE: according to MDN, :not() can not contain :not()
|
|
706
|
-
// but spec says nothing about that?
|
|
707
|
-
} else if (astName === 'not' && selector.includes(':not(')) {
|
|
708
|
-
res = null;
|
|
709
759
|
} else {
|
|
760
|
+
const forgive = /^(?:is|where)$/.test(astName);
|
|
710
761
|
let bool;
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
762
|
+
const l = twigBranches.length;
|
|
763
|
+
for (let i = 0; i < l; i++) {
|
|
764
|
+
const branch = twigBranches[i];
|
|
765
|
+
const lastIndex = branch.length - 1;
|
|
766
|
+
const { leaves } = branch[lastIndex];
|
|
767
|
+
bool = this._matchLeaves(leaves, node, {
|
|
768
|
+
forgive
|
|
769
|
+
});
|
|
770
|
+
if (bool && lastIndex > 0) {
|
|
771
|
+
let nextNodes = new Set([node]);
|
|
772
|
+
for (let j = lastIndex - 1; j >= 0; j--) {
|
|
773
|
+
const twig = branch[j];
|
|
774
|
+
const arr = [];
|
|
775
|
+
for (const nextNode of nextNodes) {
|
|
776
|
+
const m = this._matchCombinator(twig, nextNode, {
|
|
777
|
+
forgive,
|
|
778
|
+
find: 'prev'
|
|
779
|
+
});
|
|
780
|
+
if (m.size) {
|
|
781
|
+
arr.push(...m);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
const matchedNodes = new Set(arr);
|
|
785
|
+
if (matchedNodes.size) {
|
|
786
|
+
if (j === 0) {
|
|
787
|
+
bool = true;
|
|
788
|
+
break;
|
|
789
|
+
} else {
|
|
790
|
+
nextNodes = matchedNodes;
|
|
791
|
+
}
|
|
792
|
+
} else {
|
|
793
|
+
bool = false;
|
|
794
|
+
break;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
714
798
|
if (bool) {
|
|
715
799
|
break;
|
|
716
800
|
}
|
|
@@ -731,38 +815,64 @@ export class Matcher {
|
|
|
731
815
|
* @see https://html.spec.whatwg.org/#pseudo-classes
|
|
732
816
|
* @param {object} ast - AST
|
|
733
817
|
* @param {object} node - Element node
|
|
818
|
+
* @param {object} opt - options
|
|
734
819
|
* @returns {object} - collection of matched nodes
|
|
735
820
|
*/
|
|
736
|
-
_matchPseudoClassSelector(ast, node) {
|
|
821
|
+
_matchPseudoClassSelector(ast, node, opt = {}) {
|
|
737
822
|
const { children: astChildren } = ast;
|
|
738
823
|
const { localName, parentNode } = node;
|
|
824
|
+
const { forgive } = opt;
|
|
739
825
|
const astName = unescapeSelector(ast.name);
|
|
740
826
|
let matched = new Set();
|
|
741
827
|
// :has(), :is(), :not(), :where()
|
|
742
828
|
if (PSEUDO_FUNC.test(astName)) {
|
|
743
|
-
let
|
|
829
|
+
let astData;
|
|
744
830
|
if (this.#cache.has(ast)) {
|
|
745
|
-
|
|
831
|
+
astData = this.#cache.get(ast);
|
|
746
832
|
} else {
|
|
747
833
|
const branches = walkAST(ast);
|
|
748
|
-
const
|
|
749
|
-
const
|
|
750
|
-
for (
|
|
751
|
-
const leaves = branches[i];
|
|
834
|
+
const selectors = [];
|
|
835
|
+
const twigBranches = [];
|
|
836
|
+
for (const [...leaves] of branches) {
|
|
752
837
|
for (const leaf of leaves) {
|
|
753
838
|
const css = generateCSS(leaf);
|
|
754
|
-
|
|
839
|
+
selectors.push(css);
|
|
840
|
+
}
|
|
841
|
+
const branch = [];
|
|
842
|
+
const leavesSet = new Set();
|
|
843
|
+
let item = leaves.shift();
|
|
844
|
+
while (item) {
|
|
845
|
+
if (item.type === COMBINATOR) {
|
|
846
|
+
branch.push({
|
|
847
|
+
combo: item,
|
|
848
|
+
leaves: [...leavesSet]
|
|
849
|
+
});
|
|
850
|
+
leavesSet.clear();
|
|
851
|
+
} else if (item) {
|
|
852
|
+
leavesSet.add(item);
|
|
853
|
+
}
|
|
854
|
+
if (leaves.length) {
|
|
855
|
+
item = leaves.shift();
|
|
856
|
+
} else {
|
|
857
|
+
branch.push({
|
|
858
|
+
combo: null,
|
|
859
|
+
leaves: [...leavesSet]
|
|
860
|
+
});
|
|
861
|
+
leavesSet.clear();
|
|
862
|
+
break;
|
|
863
|
+
}
|
|
755
864
|
}
|
|
865
|
+
twigBranches.push(branch);
|
|
756
866
|
}
|
|
757
|
-
|
|
758
|
-
opt = {
|
|
867
|
+
astData = {
|
|
759
868
|
astName,
|
|
760
869
|
branches,
|
|
761
|
-
|
|
870
|
+
twigBranches,
|
|
871
|
+
selector: selectors.join(',')
|
|
762
872
|
};
|
|
763
|
-
this.#cache.set(ast,
|
|
873
|
+
this.#cache.set(ast, astData);
|
|
764
874
|
}
|
|
765
|
-
const res = this._matchLogicalPseudoFunc(
|
|
875
|
+
const res = this._matchLogicalPseudoFunc(astData, node);
|
|
766
876
|
if (res) {
|
|
767
877
|
matched.add(res);
|
|
768
878
|
}
|
|
@@ -790,28 +900,35 @@ export class Matcher {
|
|
|
790
900
|
switch (astName) {
|
|
791
901
|
case 'current':
|
|
792
902
|
case 'nth-col':
|
|
793
|
-
case 'nth-last-col':
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
903
|
+
case 'nth-last-col': {
|
|
904
|
+
if (this.#warn) {
|
|
905
|
+
throw new DOMException(`Unsupported pseudo-class :${astName}()`,
|
|
906
|
+
NOT_SUPPORTED_ERR);
|
|
907
|
+
}
|
|
908
|
+
break;
|
|
909
|
+
}
|
|
910
|
+
default: {
|
|
911
|
+
if (!forgive) {
|
|
912
|
+
throw new DOMException(`Unknown pseudo-class :${astName}()`,
|
|
913
|
+
SYNTAX_ERR);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
799
916
|
}
|
|
800
917
|
}
|
|
801
918
|
} else {
|
|
802
|
-
const { document } = this.#root;
|
|
803
|
-
const
|
|
919
|
+
const { document, root } = this.#root;
|
|
920
|
+
const { documentElement } = document;
|
|
804
921
|
const docURL = new URL(document.URL);
|
|
805
922
|
switch (astName) {
|
|
806
923
|
case 'any-link':
|
|
807
924
|
case 'link': {
|
|
808
|
-
if (
|
|
925
|
+
if (HTML_ANCHOR.test(localName) && node.hasAttribute('href')) {
|
|
809
926
|
matched.add(node);
|
|
810
927
|
}
|
|
811
928
|
break;
|
|
812
929
|
}
|
|
813
930
|
case 'local-link': {
|
|
814
|
-
if (
|
|
931
|
+
if (HTML_ANCHOR.test(localName) && node.hasAttribute('href')) {
|
|
815
932
|
const attrURL = new URL(node.getAttribute('href'), docURL.href);
|
|
816
933
|
if (attrURL.origin === docURL.origin &&
|
|
817
934
|
attrURL.pathname === docURL.pathname) {
|
|
@@ -850,7 +967,7 @@ export class Matcher {
|
|
|
850
967
|
if (node === this.#node) {
|
|
851
968
|
matched.add(node);
|
|
852
969
|
}
|
|
853
|
-
} else if (node ===
|
|
970
|
+
} else if (node === documentElement) {
|
|
854
971
|
matched.add(node);
|
|
855
972
|
}
|
|
856
973
|
break;
|
|
@@ -885,9 +1002,7 @@ export class Matcher {
|
|
|
885
1002
|
break;
|
|
886
1003
|
}
|
|
887
1004
|
case 'disabled': {
|
|
888
|
-
if (
|
|
889
|
-
HTML_FORM_PARTS.test(localName) ||
|
|
890
|
-
isCustomElementName(localName)) {
|
|
1005
|
+
if (FORM_PARTS.test(localName) || isCustomElementName(localName)) {
|
|
891
1006
|
if (node.disabled || node.hasAttribute('disabled')) {
|
|
892
1007
|
matched.add(node);
|
|
893
1008
|
} else {
|
|
@@ -907,16 +1022,14 @@ export class Matcher {
|
|
|
907
1022
|
break;
|
|
908
1023
|
}
|
|
909
1024
|
case 'enabled': {
|
|
910
|
-
if ((
|
|
911
|
-
HTML_FORM_PARTS.test(localName) ||
|
|
912
|
-
isCustomElementName(localName)) &&
|
|
1025
|
+
if ((FORM_PARTS.test(localName) || isCustomElementName(localName)) &&
|
|
913
1026
|
!(node.disabled && node.hasAttribute('disabled'))) {
|
|
914
1027
|
matched.add(node);
|
|
915
1028
|
}
|
|
916
1029
|
break;
|
|
917
1030
|
}
|
|
918
1031
|
case 'read-only': {
|
|
919
|
-
if (
|
|
1032
|
+
if (FORM_INPUT.test(localName)) {
|
|
920
1033
|
let targetNode;
|
|
921
1034
|
if (localName === 'input') {
|
|
922
1035
|
if (node.hasAttribute('type')) {
|
|
@@ -930,7 +1043,7 @@ export class Matcher {
|
|
|
930
1043
|
} else {
|
|
931
1044
|
targetNode = node;
|
|
932
1045
|
}
|
|
933
|
-
} else
|
|
1046
|
+
} else {
|
|
934
1047
|
targetNode = node;
|
|
935
1048
|
}
|
|
936
1049
|
if (targetNode) {
|
|
@@ -945,7 +1058,7 @@ export class Matcher {
|
|
|
945
1058
|
break;
|
|
946
1059
|
}
|
|
947
1060
|
case 'read-write': {
|
|
948
|
-
if (
|
|
1061
|
+
if (FORM_INPUT.test(localName)) {
|
|
949
1062
|
let targetNode;
|
|
950
1063
|
if (localName === 'input') {
|
|
951
1064
|
if (node.hasAttribute('type')) {
|
|
@@ -959,7 +1072,7 @@ export class Matcher {
|
|
|
959
1072
|
} else {
|
|
960
1073
|
targetNode = node;
|
|
961
1074
|
}
|
|
962
|
-
} else
|
|
1075
|
+
} else {
|
|
963
1076
|
targetNode = node;
|
|
964
1077
|
}
|
|
965
1078
|
if (targetNode) {
|
|
@@ -996,7 +1109,7 @@ export class Matcher {
|
|
|
996
1109
|
}
|
|
997
1110
|
case 'checked': {
|
|
998
1111
|
if ((localName === 'input' && node.hasAttribute('type') &&
|
|
999
|
-
|
|
1112
|
+
INPUT_CHECK.test(node.getAttribute('type')) &&
|
|
1000
1113
|
node.checked) ||
|
|
1001
1114
|
(localName === 'option' && node.selected)) {
|
|
1002
1115
|
matched.add(node);
|
|
@@ -1019,7 +1132,7 @@ export class Matcher {
|
|
|
1019
1132
|
parent = parent.parentNode;
|
|
1020
1133
|
}
|
|
1021
1134
|
if (!parent) {
|
|
1022
|
-
parent =
|
|
1135
|
+
parent = documentElement;
|
|
1023
1136
|
}
|
|
1024
1137
|
const nodes = [...parent.getElementsByTagName('input')];
|
|
1025
1138
|
let checked;
|
|
@@ -1047,9 +1160,9 @@ export class Matcher {
|
|
|
1047
1160
|
// button[type="submit"], input[type="submit"], input[type="image"]
|
|
1048
1161
|
if ((localName === 'button' &&
|
|
1049
1162
|
!(node.hasAttribute('type') &&
|
|
1050
|
-
|
|
1163
|
+
INPUT_RESET.test(node.getAttribute('type')))) ||
|
|
1051
1164
|
(localName === 'input' && node.hasAttribute('type') &&
|
|
1052
|
-
|
|
1165
|
+
INPUT_SUBMIT.test(node.getAttribute('type')))) {
|
|
1053
1166
|
let form = node.parentNode;
|
|
1054
1167
|
while (form) {
|
|
1055
1168
|
if (form.localName === 'form') {
|
|
@@ -1065,10 +1178,10 @@ export class Matcher {
|
|
|
1065
1178
|
let m;
|
|
1066
1179
|
if (nodeName === 'button') {
|
|
1067
1180
|
m = !(nextNode.hasAttribute('type') &&
|
|
1068
|
-
|
|
1181
|
+
INPUT_RESET.test(nextNode.getAttribute('type')));
|
|
1069
1182
|
} else if (nodeName === 'input') {
|
|
1070
1183
|
m = nextNode.hasAttribute('type') &&
|
|
1071
|
-
|
|
1184
|
+
INPUT_SUBMIT.test(nextNode.getAttribute('type'));
|
|
1072
1185
|
}
|
|
1073
1186
|
if (m) {
|
|
1074
1187
|
if (nextNode === node) {
|
|
@@ -1081,7 +1194,7 @@ export class Matcher {
|
|
|
1081
1194
|
}
|
|
1082
1195
|
// input[type="checkbox"], input[type="radio"]
|
|
1083
1196
|
} else if (localName === 'input' && node.hasAttribute('type') &&
|
|
1084
|
-
|
|
1197
|
+
INPUT_CHECK.test(node.getAttribute('type')) &&
|
|
1085
1198
|
node.hasAttribute('checked')) {
|
|
1086
1199
|
matched.add(node);
|
|
1087
1200
|
// option
|
|
@@ -1099,8 +1212,10 @@ export class Matcher {
|
|
|
1099
1212
|
}
|
|
1100
1213
|
// FIXME:
|
|
1101
1214
|
if (isMultiple) {
|
|
1102
|
-
|
|
1103
|
-
|
|
1215
|
+
if (this.#warn) {
|
|
1216
|
+
throw new DOMException(`Unsupported pseudo-class :${astName}`,
|
|
1217
|
+
NOT_SUPPORTED_ERR);
|
|
1218
|
+
}
|
|
1104
1219
|
} else {
|
|
1105
1220
|
const firstOpt = parentNode.firstElementChild;
|
|
1106
1221
|
const defaultOpt = new Set();
|
|
@@ -1123,12 +1238,11 @@ export class Matcher {
|
|
|
1123
1238
|
break;
|
|
1124
1239
|
}
|
|
1125
1240
|
case 'valid': {
|
|
1126
|
-
if (
|
|
1241
|
+
if (FORM_VALIDITY.test(localName)) {
|
|
1127
1242
|
if (node.checkValidity()) {
|
|
1128
1243
|
matched.add(node);
|
|
1129
1244
|
}
|
|
1130
1245
|
} else if (/^fieldset$/.test(localName)) {
|
|
1131
|
-
const { document } = this.#root;
|
|
1132
1246
|
const iterator = document.createNodeIterator(node, SHOW_ELEMENT);
|
|
1133
1247
|
let refNode = iterator.nextNode();
|
|
1134
1248
|
if (refNode === node) {
|
|
@@ -1136,7 +1250,7 @@ export class Matcher {
|
|
|
1136
1250
|
}
|
|
1137
1251
|
let bool;
|
|
1138
1252
|
while (refNode) {
|
|
1139
|
-
if (
|
|
1253
|
+
if (FORM_VALIDITY.test(refNode.localName)) {
|
|
1140
1254
|
bool = refNode.checkValidity();
|
|
1141
1255
|
if (!bool) {
|
|
1142
1256
|
break;
|
|
@@ -1151,12 +1265,11 @@ export class Matcher {
|
|
|
1151
1265
|
break;
|
|
1152
1266
|
}
|
|
1153
1267
|
case 'invalid': {
|
|
1154
|
-
if (
|
|
1268
|
+
if (FORM_VALIDITY.test(localName)) {
|
|
1155
1269
|
if (!node.checkValidity()) {
|
|
1156
1270
|
matched.add(node);
|
|
1157
1271
|
}
|
|
1158
1272
|
} else if (/^fieldset$/.test(localName)) {
|
|
1159
|
-
const { document } = this.#root;
|
|
1160
1273
|
const iterator = document.createNodeIterator(node, SHOW_ELEMENT);
|
|
1161
1274
|
let refNode = iterator.nextNode();
|
|
1162
1275
|
if (refNode === node) {
|
|
@@ -1164,7 +1277,7 @@ export class Matcher {
|
|
|
1164
1277
|
}
|
|
1165
1278
|
let bool;
|
|
1166
1279
|
while (refNode) {
|
|
1167
|
-
if (
|
|
1280
|
+
if (FORM_VALIDITY.test(refNode.localName)) {
|
|
1168
1281
|
bool = refNode.checkValidity();
|
|
1169
1282
|
if (!bool) {
|
|
1170
1283
|
break;
|
|
@@ -1256,7 +1369,7 @@ export class Matcher {
|
|
|
1256
1369
|
break;
|
|
1257
1370
|
}
|
|
1258
1371
|
case 'root': {
|
|
1259
|
-
if (node ===
|
|
1372
|
+
if (node === documentElement) {
|
|
1260
1373
|
matched.add(node);
|
|
1261
1374
|
}
|
|
1262
1375
|
break;
|
|
@@ -1281,20 +1394,24 @@ export class Matcher {
|
|
|
1281
1394
|
break;
|
|
1282
1395
|
}
|
|
1283
1396
|
case 'first-child': {
|
|
1284
|
-
if (parentNode && node === parentNode.firstElementChild)
|
|
1397
|
+
if ((parentNode && node === parentNode.firstElementChild) ||
|
|
1398
|
+
(root.nodeType === ELEMENT_NODE && node === root)) {
|
|
1285
1399
|
matched.add(node);
|
|
1286
1400
|
}
|
|
1287
1401
|
break;
|
|
1288
1402
|
}
|
|
1289
1403
|
case 'last-child': {
|
|
1290
|
-
if (parentNode && node === parentNode.lastElementChild)
|
|
1404
|
+
if ((parentNode && node === parentNode.lastElementChild) ||
|
|
1405
|
+
(root.nodeType === ELEMENT_NODE && node === root)) {
|
|
1291
1406
|
matched.add(node);
|
|
1292
1407
|
}
|
|
1293
1408
|
break;
|
|
1294
1409
|
}
|
|
1295
1410
|
case 'only-child': {
|
|
1296
|
-
if (parentNode &&
|
|
1297
|
-
|
|
1411
|
+
if ((parentNode &&
|
|
1412
|
+
node === parentNode.firstElementChild &&
|
|
1413
|
+
node === parentNode.lastElementChild) ||
|
|
1414
|
+
(root.nodeType === ELEMENT_NODE && node === root)) {
|
|
1298
1415
|
matched.add(node);
|
|
1299
1416
|
}
|
|
1300
1417
|
break;
|
|
@@ -1308,6 +1425,8 @@ export class Matcher {
|
|
|
1308
1425
|
if (node1) {
|
|
1309
1426
|
matched.add(node1);
|
|
1310
1427
|
}
|
|
1428
|
+
} else if (root.nodeType === ELEMENT_NODE && node === root) {
|
|
1429
|
+
matched.add(node);
|
|
1311
1430
|
}
|
|
1312
1431
|
break;
|
|
1313
1432
|
}
|
|
@@ -1321,6 +1440,8 @@ export class Matcher {
|
|
|
1321
1440
|
if (node1) {
|
|
1322
1441
|
matched.add(node1);
|
|
1323
1442
|
}
|
|
1443
|
+
} else if (root.nodeType === ELEMENT_NODE && node === root) {
|
|
1444
|
+
matched.add(node);
|
|
1324
1445
|
}
|
|
1325
1446
|
break;
|
|
1326
1447
|
}
|
|
@@ -1340,6 +1461,8 @@ export class Matcher {
|
|
|
1340
1461
|
matched.add(node);
|
|
1341
1462
|
}
|
|
1342
1463
|
}
|
|
1464
|
+
} else if (root.nodeType === ELEMENT_NODE && node === root) {
|
|
1465
|
+
matched.add(node);
|
|
1343
1466
|
}
|
|
1344
1467
|
break;
|
|
1345
1468
|
}
|
|
@@ -1348,8 +1471,11 @@ export class Matcher {
|
|
|
1348
1471
|
case 'before':
|
|
1349
1472
|
case 'first-letter':
|
|
1350
1473
|
case 'first-line': {
|
|
1351
|
-
|
|
1352
|
-
|
|
1474
|
+
if (this.#warn) {
|
|
1475
|
+
throw new DOMException(`Unsupported pseudo-element ::${astName}`,
|
|
1476
|
+
NOT_SUPPORTED_ERR);
|
|
1477
|
+
}
|
|
1478
|
+
break;
|
|
1353
1479
|
}
|
|
1354
1480
|
case 'active':
|
|
1355
1481
|
case 'autofill':
|
|
@@ -1372,12 +1498,22 @@ export class Matcher {
|
|
|
1372
1498
|
case 'user-valid':
|
|
1373
1499
|
case 'volume-locked':
|
|
1374
1500
|
case '-webkit-autofill': {
|
|
1375
|
-
|
|
1376
|
-
|
|
1501
|
+
if (this.#warn) {
|
|
1502
|
+
throw new DOMException(`Unsupported pseudo-class :${astName}`,
|
|
1503
|
+
NOT_SUPPORTED_ERR);
|
|
1504
|
+
}
|
|
1505
|
+
break;
|
|
1377
1506
|
}
|
|
1378
1507
|
default: {
|
|
1379
|
-
|
|
1380
|
-
|
|
1508
|
+
if (astName.startsWith('-webkit-')) {
|
|
1509
|
+
if (this.#warn) {
|
|
1510
|
+
throw new DOMException(`Unsupported pseudo-class :${astName}`,
|
|
1511
|
+
NOT_SUPPORTED_ERR);
|
|
1512
|
+
}
|
|
1513
|
+
} else if (!forgive) {
|
|
1514
|
+
throw new DOMException(`Unknown pseudo-class :${astName}`,
|
|
1515
|
+
SYNTAX_ERR);
|
|
1516
|
+
}
|
|
1381
1517
|
}
|
|
1382
1518
|
}
|
|
1383
1519
|
}
|
|
@@ -1395,7 +1531,7 @@ export class Matcher {
|
|
|
1395
1531
|
flags: astFlags, matcher: astMatcher, name: astName, value: astValue
|
|
1396
1532
|
} = ast;
|
|
1397
1533
|
if (typeof astFlags === 'string' && !/^[is]$/i.test(astFlags)) {
|
|
1398
|
-
throw new DOMException('
|
|
1534
|
+
throw new DOMException('Invalid attribute selector', SYNTAX_ERR);
|
|
1399
1535
|
}
|
|
1400
1536
|
const { attributes } = node;
|
|
1401
1537
|
let res;
|
|
@@ -1450,7 +1586,8 @@ export class Matcher {
|
|
|
1450
1586
|
if (/:/.test(itemName)) {
|
|
1451
1587
|
const [itemNamePrefix, itemNameLocalName] = itemName.split(':');
|
|
1452
1588
|
if (astAttrPrefix === itemNamePrefix &&
|
|
1453
|
-
astAttrLocalName === itemNameLocalName
|
|
1589
|
+
astAttrLocalName === itemNameLocalName &&
|
|
1590
|
+
isNamespaceDeclared(astAttrPrefix, node)) {
|
|
1454
1591
|
attrValues.add(itemValue);
|
|
1455
1592
|
}
|
|
1456
1593
|
}
|
|
@@ -1649,7 +1786,8 @@ export class Matcher {
|
|
|
1649
1786
|
if (astNodeName === '*' || astNodeName === nodeName) {
|
|
1650
1787
|
res = node;
|
|
1651
1788
|
}
|
|
1652
|
-
} else if (astPrefix === nodePrefix
|
|
1789
|
+
} else if (astPrefix === nodePrefix &&
|
|
1790
|
+
isNamespaceDeclared(astPrefix, node)) {
|
|
1653
1791
|
if (astNodeName === '*' || astNodeName === nodeName) {
|
|
1654
1792
|
res = node;
|
|
1655
1793
|
}
|
|
@@ -1661,9 +1799,10 @@ export class Matcher {
|
|
|
1661
1799
|
* match selector
|
|
1662
1800
|
* @param {object} ast - AST
|
|
1663
1801
|
* @param {object} node - Document, DocumentFragment, Element node
|
|
1802
|
+
* @param {object} opt - options
|
|
1664
1803
|
* @returns {object} - collection of matched nodes
|
|
1665
1804
|
*/
|
|
1666
|
-
_matchSelector(ast, node) {
|
|
1805
|
+
_matchSelector(ast, node, opt) {
|
|
1667
1806
|
const { type } = ast;
|
|
1668
1807
|
let matched = new Set();
|
|
1669
1808
|
if (node.nodeType === ELEMENT_NODE) {
|
|
@@ -1690,7 +1829,7 @@ export class Matcher {
|
|
|
1690
1829
|
break;
|
|
1691
1830
|
}
|
|
1692
1831
|
case PSEUDO_CLASS_SELECTOR: {
|
|
1693
|
-
const nodes = this._matchPseudoClassSelector(ast, node);
|
|
1832
|
+
const nodes = this._matchPseudoClassSelector(ast, node, opt);
|
|
1694
1833
|
if (nodes.size) {
|
|
1695
1834
|
matched = nodes;
|
|
1696
1835
|
}
|
|
@@ -1698,7 +1837,7 @@ export class Matcher {
|
|
|
1698
1837
|
}
|
|
1699
1838
|
case PSEUDO_ELEMENT_SELECTOR: {
|
|
1700
1839
|
const astName = unescapeSelector(ast.name);
|
|
1701
|
-
this.
|
|
1840
|
+
this._matchPseudoElementSelector(astName, opt);
|
|
1702
1841
|
break;
|
|
1703
1842
|
}
|
|
1704
1843
|
case TYPE_SELECTOR:
|
|
@@ -1717,12 +1856,13 @@ export class Matcher {
|
|
|
1717
1856
|
* match leaves
|
|
1718
1857
|
* @param {Array.<object>} leaves - AST leaves
|
|
1719
1858
|
* @param {object} node - node
|
|
1859
|
+
* @param {object} opt - options
|
|
1720
1860
|
* @returns {boolean} - result
|
|
1721
1861
|
*/
|
|
1722
|
-
_matchLeaves(leaves, node) {
|
|
1862
|
+
_matchLeaves(leaves, node, opt) {
|
|
1723
1863
|
let bool;
|
|
1724
1864
|
for (const leaf of leaves) {
|
|
1725
|
-
bool = this._matchSelector(leaf, node).has(node);
|
|
1865
|
+
bool = this._matchSelector(leaf, node, opt).has(node);
|
|
1726
1866
|
if (!bool) {
|
|
1727
1867
|
break;
|
|
1728
1868
|
}
|
|
@@ -1807,7 +1947,7 @@ export class Matcher {
|
|
|
1807
1947
|
break;
|
|
1808
1948
|
}
|
|
1809
1949
|
case PSEUDO_ELEMENT_SELECTOR: {
|
|
1810
|
-
this.
|
|
1950
|
+
this._matchPseudoElementSelector(leafName);
|
|
1811
1951
|
break;
|
|
1812
1952
|
}
|
|
1813
1953
|
default: {
|
|
@@ -1831,7 +1971,7 @@ export class Matcher {
|
|
|
1831
1971
|
_matchCombinator(twig, node, opt = {}) {
|
|
1832
1972
|
const { combo, leaves } = twig;
|
|
1833
1973
|
const { name: comboName } = combo;
|
|
1834
|
-
const { find } = opt;
|
|
1974
|
+
const { find, forgive } = opt;
|
|
1835
1975
|
let matched = new Set();
|
|
1836
1976
|
if (find === 'next') {
|
|
1837
1977
|
switch (comboName) {
|
|
@@ -1893,7 +2033,9 @@ export class Matcher {
|
|
|
1893
2033
|
case '+': {
|
|
1894
2034
|
const refNode = node.previousElementSibling;
|
|
1895
2035
|
if (refNode) {
|
|
1896
|
-
const bool = this._matchLeaves(leaves, refNode
|
|
2036
|
+
const bool = this._matchLeaves(leaves, refNode, {
|
|
2037
|
+
forgive
|
|
2038
|
+
});
|
|
1897
2039
|
if (bool) {
|
|
1898
2040
|
matched.add(refNode);
|
|
1899
2041
|
}
|
|
@@ -1904,7 +2046,9 @@ export class Matcher {
|
|
|
1904
2046
|
const arr = [];
|
|
1905
2047
|
let refNode = node.previousElementSibling;
|
|
1906
2048
|
while (refNode) {
|
|
1907
|
-
const bool = this._matchLeaves(leaves, refNode
|
|
2049
|
+
const bool = this._matchLeaves(leaves, refNode, {
|
|
2050
|
+
forgive
|
|
2051
|
+
});
|
|
1908
2052
|
if (bool) {
|
|
1909
2053
|
arr.push(refNode);
|
|
1910
2054
|
}
|
|
@@ -1918,7 +2062,9 @@ export class Matcher {
|
|
|
1918
2062
|
case '>': {
|
|
1919
2063
|
const refNode = node.parentNode;
|
|
1920
2064
|
if (refNode) {
|
|
1921
|
-
const bool = this._matchLeaves(leaves, refNode
|
|
2065
|
+
const bool = this._matchLeaves(leaves, refNode, {
|
|
2066
|
+
forgive
|
|
2067
|
+
});
|
|
1922
2068
|
if (bool) {
|
|
1923
2069
|
matched.add(refNode);
|
|
1924
2070
|
}
|
|
@@ -1930,7 +2076,9 @@ export class Matcher {
|
|
|
1930
2076
|
const arr = [];
|
|
1931
2077
|
let refNode = node.parentNode;
|
|
1932
2078
|
while (refNode) {
|
|
1933
|
-
const bool = this._matchLeaves(leaves, refNode
|
|
2079
|
+
const bool = this._matchLeaves(leaves, refNode, {
|
|
2080
|
+
forgive
|
|
2081
|
+
});
|
|
1934
2082
|
if (bool) {
|
|
1935
2083
|
arr.push(refNode);
|
|
1936
2084
|
}
|
|
@@ -2105,7 +2253,7 @@ export class Matcher {
|
|
|
2105
2253
|
break;
|
|
2106
2254
|
}
|
|
2107
2255
|
case PSEUDO_ELEMENT_SELECTOR: {
|
|
2108
|
-
this.
|
|
2256
|
+
this._matchPseudoElementSelector(leafName);
|
|
2109
2257
|
break;
|
|
2110
2258
|
}
|
|
2111
2259
|
default: {
|
|
@@ -2372,7 +2520,7 @@ export class Matcher {
|
|
|
2372
2520
|
*/
|
|
2373
2521
|
matches() {
|
|
2374
2522
|
if (this.#node.nodeType !== ELEMENT_NODE) {
|
|
2375
|
-
throw new TypeError(`Unexpected node
|
|
2523
|
+
throw new TypeError(`Unexpected node ${this.#node.nodeName}`);
|
|
2376
2524
|
}
|
|
2377
2525
|
let res;
|
|
2378
2526
|
try {
|
|
@@ -2390,7 +2538,7 @@ export class Matcher {
|
|
|
2390
2538
|
*/
|
|
2391
2539
|
closest() {
|
|
2392
2540
|
if (this.#node.nodeType !== ELEMENT_NODE) {
|
|
2393
|
-
throw new TypeError(`Unexpected node
|
|
2541
|
+
throw new TypeError(`Unexpected node ${this.#node.nodeName}`);
|
|
2394
2542
|
}
|
|
2395
2543
|
let res;
|
|
2396
2544
|
try {
|
package/src/js/parser.js
CHANGED
|
@@ -102,7 +102,7 @@ export const preprocess = (...args) => {
|
|
|
102
102
|
selector = Object.prototype.toString.call(selector)
|
|
103
103
|
.slice(TYPE_FROM, TYPE_TO).toLowerCase();
|
|
104
104
|
} else {
|
|
105
|
-
throw new DOMException(`
|
|
105
|
+
throw new DOMException(`Invalid selector ${selector}`, SYNTAX_ERR);
|
|
106
106
|
}
|
|
107
107
|
return selector;
|
|
108
108
|
};
|
|
@@ -116,7 +116,7 @@ export const parseSelector = selector => {
|
|
|
116
116
|
selector = preprocess(selector);
|
|
117
117
|
// invalid selectors
|
|
118
118
|
if (/^$|^\s*>|,\s*$/.test(selector)) {
|
|
119
|
-
throw new DOMException(`
|
|
119
|
+
throw new DOMException(`Invalid selector ${selector}`, SYNTAX_ERR);
|
|
120
120
|
}
|
|
121
121
|
let res;
|
|
122
122
|
try {
|
package/types/index.d.ts
CHANGED
|
@@ -4,10 +4,10 @@ export function matches(selector: string, node: object, opt?: {
|
|
|
4
4
|
export function closest(selector: string, node: object, opt?: {
|
|
5
5
|
warn?: boolean;
|
|
6
6
|
}): object | null;
|
|
7
|
-
export function querySelector(selector: string,
|
|
7
|
+
export function querySelector(selector: string, node: object, opt?: {
|
|
8
8
|
warn?: boolean;
|
|
9
9
|
}): object | null;
|
|
10
|
-
export function querySelectorAll(selector: string,
|
|
10
|
+
export function querySelectorAll(selector: string, node: object, opt?: {
|
|
11
11
|
sort?: boolean;
|
|
12
12
|
warn?: boolean;
|
|
13
13
|
}): Array<object | undefined>;
|
package/types/js/matcher.d.ts
CHANGED
|
@@ -7,7 +7,6 @@ export class Matcher {
|
|
|
7
7
|
_getRoot(node?: object): object;
|
|
8
8
|
_sortLeaves(leaves: Array<object>): Array<object>;
|
|
9
9
|
_prepare(selector?: string): Array<Array<object | undefined>>;
|
|
10
|
-
_throwOnPseudoElementSelector(astName: object): void;
|
|
11
10
|
_collectNthChild(anb: {
|
|
12
11
|
a: number;
|
|
13
12
|
b: number;
|
|
@@ -20,17 +19,18 @@ export class Matcher {
|
|
|
20
19
|
reverse?: boolean;
|
|
21
20
|
}, node: object): object;
|
|
22
21
|
_matchAnPlusB(ast: object, node: object, nthName: string): object;
|
|
22
|
+
_matchPseudoElementSelector(astName: string, opt?: object): void;
|
|
23
23
|
_matchDirectionPseudoClass(ast: object, node: object): object | null;
|
|
24
24
|
_matchLanguagePseudoClass(ast: object, node: object): object | null;
|
|
25
25
|
_matchHasPseudoFunc(leaves: Array<object>, node: object): boolean;
|
|
26
|
-
_matchLogicalPseudoFunc(
|
|
27
|
-
_matchPseudoClassSelector(ast: object, node: object): object;
|
|
26
|
+
_matchLogicalPseudoFunc(astData: object, node: object): object | null;
|
|
27
|
+
_matchPseudoClassSelector(ast: object, node: object, opt?: object): object;
|
|
28
28
|
_matchAttributeSelector(ast: object, node: object): object | null;
|
|
29
29
|
_matchClassSelector(ast: object, node: object): object | null;
|
|
30
30
|
_matchIDSelector(ast: object, node: object): object | null;
|
|
31
31
|
_matchTypeSelector(ast: object, node: object): object | null;
|
|
32
|
-
_matchSelector(ast: object, node: object): object;
|
|
33
|
-
_matchLeaves(leaves: Array<object>, node: object): boolean;
|
|
32
|
+
_matchSelector(ast: object, node: object, opt: object): object;
|
|
33
|
+
_matchLeaves(leaves: Array<object>, node: object, opt: object): boolean;
|
|
34
34
|
_findDescendantNodes(leaves: Array<object>, baseNode: object): object;
|
|
35
35
|
_matchCombinator(twig: object, node: object, opt?: {
|
|
36
36
|
find?: string;
|