@markuplint/selector 3.0.0-alpha.2105
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/LICENSE +21 -0
- package/README.md +102 -0
- package/lib/compare-specificity.d.ts +2 -0
- package/lib/compare-specificity.js +25 -0
- package/lib/create-selector.d.ts +3 -0
- package/lib/create-selector.js +18 -0
- package/lib/debug.d.ts +3 -0
- package/lib/debug.js +18 -0
- package/lib/extended-selector/aria-pseudo-class.d.ts +3 -0
- package/lib/extended-selector/aria-pseudo-class.js +66 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +11 -0
- package/lib/invalid-selector-error.d.ts +3 -0
- package/lib/invalid-selector-error.js +10 -0
- package/lib/is-pure-html-element.d.ts +1 -0
- package/lib/is-pure-html-element.js +7 -0
- package/lib/is.d.ts +3 -0
- package/lib/is.js +15 -0
- package/lib/match-selector.d.ts +13 -0
- package/lib/match-selector.js +238 -0
- package/lib/regex-selector-matches.d.ts +3 -0
- package/lib/regex-selector-matches.js +25 -0
- package/lib/selector.d.ts +8 -0
- package/lib/selector.js +632 -0
- package/lib/types.d.ts +16 -0
- package/lib/types.js +2 -0
- package/lib/utils/is-pure-html-element.d.ts +1 -0
- package/lib/utils/is-pure-html-element.js +7 -0
- package/package.json +31 -0
- package/tsconfig.test.json +3 -0
- package/tsconfig.tsbuildinfo +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017-2019 Yusuke Hirao
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# @markuplint/selector
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@markuplint/selector)
|
|
4
|
+
[](https://travis-ci.org/markuplint/markuplint)
|
|
5
|
+
[](https://coveralls.io/github/markuplint/markuplint?branch=main)
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
$ npm install @markuplint/selector
|
|
11
|
+
|
|
12
|
+
$ yarn add @markuplint/selector
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## [W3C Selectors](https://www.w3.org/TR/selectors-4/) matcher
|
|
16
|
+
|
|
17
|
+
Supported selectors and operators:
|
|
18
|
+
|
|
19
|
+
| Selector Type | Code Example | Support |
|
|
20
|
+
| ------------------------------------------------ | ----------------------------------------------------------------------------------------- | ------- |
|
|
21
|
+
| Universal selector | `*` | ✅ |
|
|
22
|
+
| Type selector | `div` | ✅ |
|
|
23
|
+
| ID selector | `#id` | ✅ |
|
|
24
|
+
| Class selector | `.class` | ✅ |
|
|
25
|
+
| Attribute selector | `[data-attr]` | ✅ |
|
|
26
|
+
| Attribute selector, Exact match | `[data-attr=value]` `[data-attr=value i]` | ✅ |
|
|
27
|
+
| Attribute selector, Include whitespace separated | `[data-attr~=value]` `[data-attr~=value i]` | ✅ |
|
|
28
|
+
| Attribute selector, Subcode match | <code>[data-attr\|=value]</code> <code>[data-attr\|=value i]</code> | ✅ |
|
|
29
|
+
| Attribute selector, Partial match | `[data-attr*=value]` `[data-attr*=value i]` | ✅ |
|
|
30
|
+
| Attribute selector, Forward match | `[data-attr^=value]` `[data-attr^=value i]` | ✅ |
|
|
31
|
+
| Attribute selector, Backward match | `[data-attr$=value]` `[data-attr$=value i]` | ✅ |
|
|
32
|
+
| Negation pseudo-class | `:not(div)` | ✅ |
|
|
33
|
+
| Matches-Any pseudo-class | `:is(div)` | ✅ |
|
|
34
|
+
| Specificity-adjustment pseudo-class | `:where(div)` | ✅ |
|
|
35
|
+
| Relational pseudo-class | `:has(div)` `:has(> div)` `:has(+ div)` `:has(~ div)` | ✅ |
|
|
36
|
+
| Directionality pseudo-class | `:dir(ltr)` | ❌ |
|
|
37
|
+
| Language pseudo-class | `:lang(en)` | ❌ |
|
|
38
|
+
| Hyperlink pseudo-class | `:any-link` | ❌ |
|
|
39
|
+
| Link History pseudo-class | `:link` `:visited` | ❌ |
|
|
40
|
+
| Local link pseudo-class | `:local-link` | ❌ |
|
|
41
|
+
| Target pseudo-class | `:target` | ❌ |
|
|
42
|
+
| Target container pseudo-class | `:target-within` | ❌ |
|
|
43
|
+
| Reference element pseudo-class | `:scope` | ✅ |
|
|
44
|
+
| Current-element pseudo-class | `:current` `:current(div)` | ❌ |
|
|
45
|
+
| Past pseudo-class | `:past` | ❌ |
|
|
46
|
+
| Future pseudo-class | `:future` | ❌ |
|
|
47
|
+
| Interactive pseudo-class | `:active` `:hover` `:focus` `:focus-within` `:focus-visible` | ❌ |
|
|
48
|
+
| Enable and disable pseudo-class | `:enable` `:disable` | ❌ |
|
|
49
|
+
| Mutability pseudo-class | `:read-write` `:read-only` | ❌ |
|
|
50
|
+
| Placeholder-shown pseudo-class | `:placeholder-shown` | ❌ |
|
|
51
|
+
| Default-option pseudo-class | `:default` | ❌ |
|
|
52
|
+
| Selected-option pseudo-class | `:checked` | ❌ |
|
|
53
|
+
| Indeterminate value pseudo-class | `:indeterminate` | ❌ |
|
|
54
|
+
| Validity pseudo-class | `:valid` `:invalid` | ❌ |
|
|
55
|
+
| Range pseudo-class | `:in-range` `:out-of-range` | ❌ |
|
|
56
|
+
| Optionality pseudo-class | `:required` `:optional` | ❌ |
|
|
57
|
+
| Empty-Value pseudo-class | `:blank` | ❌ |
|
|
58
|
+
| User-interaction pseudo-class | `:user-invalid` | ❌ |
|
|
59
|
+
| Root pseudo-class | `:root` | ✅ |
|
|
60
|
+
| Empty pseudo-class | `:empty` | ❌ |
|
|
61
|
+
| Nth-child pseudo-class | `:nth-child(2)` `:nth-last-child(2)` `:first-child` `:last-child` `:only-child` | ❌ |
|
|
62
|
+
| Nth-child pseudo-class (`of El` Syntax) | `:nth-child(2 of div)` `:nth-last-child(2 of div)` | ❌ |
|
|
63
|
+
| Nth-of-type pseudo-class | `:nth-of-type(2)` `:nth-last-of-type(2)` `:first-of-type` `:last-of-type` `:only-of-type` | ❌ |
|
|
64
|
+
| Nth-col pseudo-class | `:nth-col(2)` `:nth-last-col(2)` | ❌ |
|
|
65
|
+
| Pseudo elements | `::before` `::after` | ❌ |
|
|
66
|
+
| Descendant combinator | `div span` | ✅ |
|
|
67
|
+
| Child combinator | `div > span` (`:has(> span)`) | ✅ |
|
|
68
|
+
| Next-sibling combinator | `div + span` (`:has(+ span)`) | ✅ |
|
|
69
|
+
| Subsequent-sibling combinator | `div ~ span` (`:has(~ span)`) | ✅ |
|
|
70
|
+
| Column combinator | <code>div \|\| span</code> | ❌ |
|
|
71
|
+
| Multiple selectors | `div, span` | ✅ |
|
|
72
|
+
|
|
73
|
+
## Extended Selector
|
|
74
|
+
|
|
75
|
+
The below is selectors that are extended by markuplint:
|
|
76
|
+
|
|
77
|
+
| Selector Type | Code Example |
|
|
78
|
+
| ----------------- | ----------------- |
|
|
79
|
+
| ARIA pseudo-class | `:aria(has name)` |
|
|
80
|
+
|
|
81
|
+
### ARIA pseudo-class
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
:aria(syntax)
|
|
85
|
+
:aria(syntax/version)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
| Syntax | Example | Description |
|
|
89
|
+
| -------------- | ------------------------------------------------------- | ------------------------------- |
|
|
90
|
+
| `has name` | `:aria(has name)`<br>`:aria(has name/1.1)` | It has accessible name |
|
|
91
|
+
| `has no name` | `:aria(has no name)`<br>`:aria(has no name/1.1)` | It does'nt have accessible name |
|
|
92
|
+
| `role is Role` | `:aria(role is banner)`<br>`:aria(role is generic/1.2)` | It matches the specfied role |
|
|
93
|
+
|
|
94
|
+
## Regex Selector
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"nodeName": "/^[a-z]+$/",
|
|
99
|
+
"attrName": "/^[a-z]+$/",
|
|
100
|
+
"attrValue": "/^[a-z]+$/"
|
|
101
|
+
}
|
|
102
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.compareSpecificity = void 0;
|
|
4
|
+
function compareSpecificity(a, b) {
|
|
5
|
+
if (a[0] < b[0]) {
|
|
6
|
+
return -1;
|
|
7
|
+
}
|
|
8
|
+
else if (a[0] > b[0]) {
|
|
9
|
+
return 1;
|
|
10
|
+
}
|
|
11
|
+
else if (a[1] < b[1]) {
|
|
12
|
+
return -1;
|
|
13
|
+
}
|
|
14
|
+
else if (a[1] > b[1]) {
|
|
15
|
+
return 1;
|
|
16
|
+
}
|
|
17
|
+
else if (a[2] < b[2]) {
|
|
18
|
+
return -1;
|
|
19
|
+
}
|
|
20
|
+
else if (a[2] > b[2]) {
|
|
21
|
+
return 1;
|
|
22
|
+
}
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
exports.compareSpecificity = compareSpecificity;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSelector = void 0;
|
|
4
|
+
const aria_pseudo_class_1 = require("./extended-selector/aria-pseudo-class");
|
|
5
|
+
const selector_1 = require("./selector");
|
|
6
|
+
const caches = new Map();
|
|
7
|
+
function createSelector(selector, specs) {
|
|
8
|
+
let instance = caches.get(selector);
|
|
9
|
+
if (instance) {
|
|
10
|
+
return instance;
|
|
11
|
+
}
|
|
12
|
+
instance = new selector_1.Selector(selector, {
|
|
13
|
+
aria: (0, aria_pseudo_class_1.ariaPseudoClass)(specs),
|
|
14
|
+
});
|
|
15
|
+
caches.set(selector, instance);
|
|
16
|
+
return instance;
|
|
17
|
+
}
|
|
18
|
+
exports.createSelector = createSelector;
|
package/lib/debug.d.ts
ADDED
package/lib/debug.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enableDebug = exports.log = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const debug_1 = tslib_1.__importDefault(require("debug"));
|
|
6
|
+
const CLI_NS = 'markuplint-cli';
|
|
7
|
+
exports.log = (0, debug_1.default)('selector');
|
|
8
|
+
function enableDebug() {
|
|
9
|
+
if (!exports.log.enabled) {
|
|
10
|
+
debug_1.default.enable(`${exports.log.namespace}*`);
|
|
11
|
+
(0, exports.log)(`Debug enable: ${exports.log.namespace}`);
|
|
12
|
+
if (!debug_1.default.enabled(CLI_NS)) {
|
|
13
|
+
debug_1.default.enable(`${exports.log.namespace}*,${CLI_NS}*`);
|
|
14
|
+
(0, exports.log)(`Debug enable: ${exports.log.namespace}, ${CLI_NS}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.enableDebug = enableDebug;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ariaPseudoClass = void 0;
|
|
4
|
+
const ml_spec_1 = require("@markuplint/ml-spec");
|
|
5
|
+
const roleIsRegxp = /^roleis/gi;
|
|
6
|
+
function ariaPseudoClass(specs) {
|
|
7
|
+
return (content) => (el) => {
|
|
8
|
+
var _a, _b;
|
|
9
|
+
const aria = ariaPseudoClassParser(content);
|
|
10
|
+
switch (aria.type) {
|
|
11
|
+
case 'hasName': {
|
|
12
|
+
const name = (0, ml_spec_1.getAccname)(el);
|
|
13
|
+
return {
|
|
14
|
+
specificity: [0, 1, 0],
|
|
15
|
+
matched: !!name,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
case 'hasNoName': {
|
|
19
|
+
const name = (0, ml_spec_1.getAccname)(el);
|
|
20
|
+
return {
|
|
21
|
+
specificity: [0, 1, 0],
|
|
22
|
+
matched: !name,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
case 'roleIs': {
|
|
26
|
+
const computed = (0, ml_spec_1.getComputedRole)(specs, el, (_a = aria.version) !== null && _a !== void 0 ? _a : '1.2');
|
|
27
|
+
return {
|
|
28
|
+
specificity: [0, 1, 0],
|
|
29
|
+
matched: ((_b = computed.role) === null || _b === void 0 ? void 0 : _b.name) === aria.role,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
exports.ariaPseudoClass = ariaPseudoClass;
|
|
36
|
+
function ariaPseudoClassParser(syntax) {
|
|
37
|
+
const [_query, _version] = syntax.split('|');
|
|
38
|
+
const query = _query.replace(/\s+/g, '').toLowerCase();
|
|
39
|
+
const version = _version === '1.1' ? '1.1' : '1.2';
|
|
40
|
+
switch (query) {
|
|
41
|
+
case 'hasname': {
|
|
42
|
+
return {
|
|
43
|
+
type: 'hasName',
|
|
44
|
+
version,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
case 'hasnoname': {
|
|
48
|
+
return {
|
|
49
|
+
type: 'hasNoName',
|
|
50
|
+
version,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (roleIsRegxp.test(query)) {
|
|
55
|
+
const role = query.replace(roleIsRegxp, '');
|
|
56
|
+
if (!role) {
|
|
57
|
+
throw new SyntaxError(`Unsupported syntax: ${syntax}`);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
type: 'roleIs',
|
|
61
|
+
role,
|
|
62
|
+
version,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
throw new SyntaxError(`Unsupported syntax: ${syntax}`);
|
|
66
|
+
}
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSelector = exports.matchSelector = exports.compareSpecificity = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
var compare_specificity_1 = require("./compare-specificity");
|
|
6
|
+
Object.defineProperty(exports, "compareSpecificity", { enumerable: true, get: function () { return compare_specificity_1.compareSpecificity; } });
|
|
7
|
+
var match_selector_1 = require("./match-selector");
|
|
8
|
+
Object.defineProperty(exports, "matchSelector", { enumerable: true, get: function () { return match_selector_1.matchSelector; } });
|
|
9
|
+
var create_selector_1 = require("./create-selector");
|
|
10
|
+
Object.defineProperty(exports, "createSelector", { enumerable: true, get: function () { return create_selector_1.createSelector; } });
|
|
11
|
+
tslib_1.__exportStar(require("./types"), exports);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InvalidSelectorError = void 0;
|
|
4
|
+
class InvalidSelectorError extends Error {
|
|
5
|
+
constructor() {
|
|
6
|
+
super(...arguments);
|
|
7
|
+
this.name = 'InvalidSelectorError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.InvalidSelectorError = InvalidSelectorError;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isPureHTMLElement(el: Element): boolean;
|
package/lib/is.d.ts
ADDED
package/lib/is.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isPureHTMLElement = exports.isNonDocumentTypeChildNode = exports.isElement = void 0;
|
|
4
|
+
function isElement(node) {
|
|
5
|
+
return node.nodeType === node.ELEMENT_NODE;
|
|
6
|
+
}
|
|
7
|
+
exports.isElement = isElement;
|
|
8
|
+
function isNonDocumentTypeChildNode(node) {
|
|
9
|
+
return 'previousElementSibling' in node && 'nextElementSibling' in node;
|
|
10
|
+
}
|
|
11
|
+
exports.isNonDocumentTypeChildNode = isNonDocumentTypeChildNode;
|
|
12
|
+
function isPureHTMLElement(el) {
|
|
13
|
+
return el.localName !== el.nodeName;
|
|
14
|
+
}
|
|
15
|
+
exports.isPureHTMLElement = isPureHTMLElement;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Specificity, RegexSelector } from './types';
|
|
2
|
+
export declare type SelectorMatches = SelectorMatched | SelectorUnmatched;
|
|
3
|
+
declare type SelectorMatched = {
|
|
4
|
+
matched: true;
|
|
5
|
+
selector: string;
|
|
6
|
+
specificity: Specificity;
|
|
7
|
+
data?: Record<string, string>;
|
|
8
|
+
};
|
|
9
|
+
declare type SelectorUnmatched = {
|
|
10
|
+
matched: false;
|
|
11
|
+
};
|
|
12
|
+
export declare function matchSelector(el: Node, selector: string | RegexSelector | undefined): SelectorMatches;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.matchSelector = void 0;
|
|
4
|
+
const is_1 = require("./is");
|
|
5
|
+
const regex_selector_matches_1 = require("./regex-selector-matches");
|
|
6
|
+
const selector_1 = require("./selector");
|
|
7
|
+
function matchSelector(el, selector) {
|
|
8
|
+
if (!selector) {
|
|
9
|
+
return {
|
|
10
|
+
matched: false,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
if (typeof selector === 'string') {
|
|
14
|
+
const sel = new selector_1.Selector(selector);
|
|
15
|
+
const specificity = sel.match(el);
|
|
16
|
+
if (specificity) {
|
|
17
|
+
return {
|
|
18
|
+
matched: true,
|
|
19
|
+
selector,
|
|
20
|
+
specificity,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
matched: false,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return regexSelect(el, selector);
|
|
28
|
+
}
|
|
29
|
+
exports.matchSelector = matchSelector;
|
|
30
|
+
function regexSelect(el, selector) {
|
|
31
|
+
let edge = new SelectorTarget(selector);
|
|
32
|
+
let edgeSelector = selector.combination;
|
|
33
|
+
while (edgeSelector) {
|
|
34
|
+
const child = new SelectorTarget(edgeSelector);
|
|
35
|
+
child.from(edge, edgeSelector.combinator);
|
|
36
|
+
edge = child;
|
|
37
|
+
edgeSelector = edgeSelector.combination;
|
|
38
|
+
}
|
|
39
|
+
return edge.match(el);
|
|
40
|
+
}
|
|
41
|
+
class SelectorTarget {
|
|
42
|
+
constructor(selector) {
|
|
43
|
+
this._combinatedFrom = null;
|
|
44
|
+
this._selector = selector;
|
|
45
|
+
}
|
|
46
|
+
from(target, combinator) {
|
|
47
|
+
this._combinatedFrom = { target, combinator };
|
|
48
|
+
}
|
|
49
|
+
match(el) {
|
|
50
|
+
const unitCheck = this._matchWithoutCombinateChecking(el);
|
|
51
|
+
if (!unitCheck.matched) {
|
|
52
|
+
return unitCheck;
|
|
53
|
+
}
|
|
54
|
+
if (!this._combinatedFrom) {
|
|
55
|
+
return unitCheck;
|
|
56
|
+
}
|
|
57
|
+
if (!(0, is_1.isNonDocumentTypeChildNode)(el)) {
|
|
58
|
+
return unitCheck;
|
|
59
|
+
}
|
|
60
|
+
const { target, combinator } = this._combinatedFrom;
|
|
61
|
+
switch (combinator) {
|
|
62
|
+
// Descendant combinator
|
|
63
|
+
case ' ': {
|
|
64
|
+
let ancestor = el.parentElement;
|
|
65
|
+
while (ancestor) {
|
|
66
|
+
const matches = target.match(ancestor);
|
|
67
|
+
if (matches.matched) {
|
|
68
|
+
return mergeMatches(matches, unitCheck, ' ');
|
|
69
|
+
}
|
|
70
|
+
ancestor = ancestor.parentElement;
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
matched: false,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// Child combinator
|
|
77
|
+
case '>': {
|
|
78
|
+
const parentNode = el.parentElement;
|
|
79
|
+
if (!parentNode) {
|
|
80
|
+
return { matched: false };
|
|
81
|
+
}
|
|
82
|
+
const matches = target.match(parentNode);
|
|
83
|
+
if (matches.matched) {
|
|
84
|
+
return mergeMatches(matches, unitCheck, ' > ');
|
|
85
|
+
}
|
|
86
|
+
return { matched: false };
|
|
87
|
+
}
|
|
88
|
+
// Next-sibling combinator
|
|
89
|
+
case '+': {
|
|
90
|
+
if (!el.previousElementSibling) {
|
|
91
|
+
return { matched: false };
|
|
92
|
+
}
|
|
93
|
+
const matches = target.match(el.previousElementSibling);
|
|
94
|
+
if (matches.matched) {
|
|
95
|
+
return mergeMatches(matches, unitCheck, ' + ');
|
|
96
|
+
}
|
|
97
|
+
return { matched: false };
|
|
98
|
+
}
|
|
99
|
+
// Subsequent-sibling combinator
|
|
100
|
+
case '~': {
|
|
101
|
+
let prev = el.previousElementSibling;
|
|
102
|
+
while (prev) {
|
|
103
|
+
const matches = target.match(prev);
|
|
104
|
+
if (matches.matched) {
|
|
105
|
+
return mergeMatches(matches, unitCheck, ' ~ ');
|
|
106
|
+
}
|
|
107
|
+
prev = prev.previousElementSibling;
|
|
108
|
+
}
|
|
109
|
+
return { matched: false };
|
|
110
|
+
}
|
|
111
|
+
// Prev-sibling combinator
|
|
112
|
+
case ':has(+)': {
|
|
113
|
+
if (!el.nextElementSibling) {
|
|
114
|
+
return { matched: false };
|
|
115
|
+
}
|
|
116
|
+
const matches = target.match(el.nextElementSibling);
|
|
117
|
+
if (matches.matched) {
|
|
118
|
+
return mergeMatches(matches, unitCheck, ':has(+ ', true);
|
|
119
|
+
}
|
|
120
|
+
return { matched: false };
|
|
121
|
+
}
|
|
122
|
+
// Subsequent-sibling (in front) combinator
|
|
123
|
+
case ':has(~)': {
|
|
124
|
+
let next = el.nextElementSibling;
|
|
125
|
+
while (next) {
|
|
126
|
+
const matches = target.match(next);
|
|
127
|
+
if (matches.matched) {
|
|
128
|
+
return mergeMatches(matches, unitCheck, ':has(~ ', true);
|
|
129
|
+
}
|
|
130
|
+
next = next.nextElementSibling;
|
|
131
|
+
}
|
|
132
|
+
return { matched: false };
|
|
133
|
+
}
|
|
134
|
+
default: {
|
|
135
|
+
throw new Error(`Unsupported ${this._combinatedFrom.combinator} combinator in selector`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
_matchWithoutCombinateChecking(el) {
|
|
140
|
+
return uncombinatedRegexSelect(el, this._selector);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function uncombinatedRegexSelect(el, selector) {
|
|
144
|
+
if (!(0, is_1.isElement)(el)) {
|
|
145
|
+
return {
|
|
146
|
+
matched: false,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
let matched = true;
|
|
150
|
+
let data = {};
|
|
151
|
+
let tagSelector = '';
|
|
152
|
+
const specificity = [0, 0, 0];
|
|
153
|
+
const specifiedAttr = new Map();
|
|
154
|
+
if (selector.nodeName) {
|
|
155
|
+
const matchedNodeName = (0, regex_selector_matches_1.regexSelectorMatches)(selector.nodeName, el.localName, (0, is_1.isPureHTMLElement)(el));
|
|
156
|
+
if (matchedNodeName) {
|
|
157
|
+
delete matchedNodeName.$0;
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
matched = false;
|
|
161
|
+
}
|
|
162
|
+
data = {
|
|
163
|
+
...data,
|
|
164
|
+
...matchedNodeName,
|
|
165
|
+
};
|
|
166
|
+
tagSelector = el.localName;
|
|
167
|
+
specificity[2] = 1;
|
|
168
|
+
}
|
|
169
|
+
if (selector.attrName) {
|
|
170
|
+
const selectorAttrName = selector.attrName;
|
|
171
|
+
const matchedAttrNameList = Array.from(el.attributes).map(attr => {
|
|
172
|
+
const attrName = attr.name;
|
|
173
|
+
const matchedAttrName = (0, regex_selector_matches_1.regexSelectorMatches)(selectorAttrName, attrName, (0, is_1.isPureHTMLElement)(el));
|
|
174
|
+
if (matchedAttrName) {
|
|
175
|
+
delete matchedAttrName.$0;
|
|
176
|
+
data = {
|
|
177
|
+
...data,
|
|
178
|
+
...matchedAttrName,
|
|
179
|
+
};
|
|
180
|
+
specifiedAttr.set(attrName, '');
|
|
181
|
+
}
|
|
182
|
+
return matchedAttrName;
|
|
183
|
+
});
|
|
184
|
+
if (!matchedAttrNameList.some(_ => !!_)) {
|
|
185
|
+
matched = false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (selector.attrValue) {
|
|
189
|
+
const selectorAttrValue = selector.attrValue;
|
|
190
|
+
const matchedAttrValueList = Array.from(el.attributes).map(attr => {
|
|
191
|
+
const attrName = attr.name;
|
|
192
|
+
const attrValue = attr.value;
|
|
193
|
+
const matchedAttrValue = (0, regex_selector_matches_1.regexSelectorMatches)(selectorAttrValue, attrValue, (0, is_1.isPureHTMLElement)(el));
|
|
194
|
+
if (matchedAttrValue) {
|
|
195
|
+
delete matchedAttrValue.$0;
|
|
196
|
+
data = {
|
|
197
|
+
...data,
|
|
198
|
+
...matchedAttrValue,
|
|
199
|
+
};
|
|
200
|
+
specifiedAttr.set(attrName, attrValue);
|
|
201
|
+
}
|
|
202
|
+
return matchedAttrValue;
|
|
203
|
+
});
|
|
204
|
+
if (!matchedAttrValueList.some(_ => !!_)) {
|
|
205
|
+
matched = false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const attrSelector = Array.from(specifiedAttr.entries())
|
|
209
|
+
.map(([name, value]) => {
|
|
210
|
+
return `[${name}${value ? `="${value}"` : ''}]`;
|
|
211
|
+
})
|
|
212
|
+
.join('');
|
|
213
|
+
specificity[1] += specifiedAttr.size;
|
|
214
|
+
if (matched) {
|
|
215
|
+
return {
|
|
216
|
+
matched,
|
|
217
|
+
selector: `${tagSelector}${attrSelector}`,
|
|
218
|
+
specificity,
|
|
219
|
+
data,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
return { matched };
|
|
223
|
+
}
|
|
224
|
+
function mergeMatches(a, b, sep, close = false) {
|
|
225
|
+
return {
|
|
226
|
+
matched: true,
|
|
227
|
+
selector: `${a.selector}${sep}${b.selector}${close ? ')' : ''}`,
|
|
228
|
+
specificity: [
|
|
229
|
+
a.specificity[0] + b.specificity[0],
|
|
230
|
+
a.specificity[1] + b.specificity[1],
|
|
231
|
+
a.specificity[2] + b.specificity[2],
|
|
232
|
+
],
|
|
233
|
+
data: {
|
|
234
|
+
...a.data,
|
|
235
|
+
...b.data,
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.regexSelectorMatches = void 0;
|
|
4
|
+
function regexSelectorMatches(reg, raw, ignoreCase) {
|
|
5
|
+
const res = {};
|
|
6
|
+
const pattern = toRegxp(reg);
|
|
7
|
+
const regex = new RegExp(pattern instanceof RegExp ? pattern : `^${pattern.trim()}$`, ignoreCase ? 'i' : undefined);
|
|
8
|
+
const matched = regex.exec(raw);
|
|
9
|
+
if (!matched) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
matched.forEach((val, i) => (res[`$${i}`] = val));
|
|
13
|
+
return {
|
|
14
|
+
...res,
|
|
15
|
+
...matched.groups,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
exports.regexSelectorMatches = regexSelectorMatches;
|
|
19
|
+
function toRegxp(pattern) {
|
|
20
|
+
const matched = pattern.match(/^\/(.+)\/([ig]*)$/i);
|
|
21
|
+
if (matched) {
|
|
22
|
+
return new RegExp(matched[1], matched[2]);
|
|
23
|
+
}
|
|
24
|
+
return pattern;
|
|
25
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SelectorResult, Specificity } from './types';
|
|
2
|
+
declare type ExtendedPseudoClass = Record<string, (content: string) => (el: Element) => SelectorResult>;
|
|
3
|
+
export declare class Selector {
|
|
4
|
+
#private;
|
|
5
|
+
constructor(selector: string, extended?: ExtendedPseudoClass);
|
|
6
|
+
match(el: Node, scope?: ParentNode | null): Specificity | false;
|
|
7
|
+
}
|
|
8
|
+
export {};
|