@asamuzakjp/dom-selector 0.11.1 → 0.12.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/package.json +1 -1
- package/src/js/matcher.js +153 -36
- package/types/js/matcher.d.ts +1 -0
package/package.json
CHANGED
package/src/js/matcher.js
CHANGED
|
@@ -13,6 +13,7 @@ const {
|
|
|
13
13
|
ATTRIBUTE_SELECTOR, CLASS_SELECTOR, COMBINATOR, IDENTIFIER, ID_SELECTOR,
|
|
14
14
|
NTH, PSEUDO_CLASS_SELECTOR, PSEUDO_ELEMENT_SELECTOR, TYPE_SELECTOR
|
|
15
15
|
} = require('./constant.js');
|
|
16
|
+
const DOCUMENT_POSITION_CONTAINS = 8;
|
|
16
17
|
const ELEMENT_NODE = 1;
|
|
17
18
|
const FILTER_ACCEPT = 1;
|
|
18
19
|
const FILTER_REJECT = 2;
|
|
@@ -29,6 +30,36 @@ const PSEUDO_NTH = /^nth-(?:last-)?(?:child|of-type)$/;
|
|
|
29
30
|
const REPLACE_CHAR = /[\0\uD800-\uDFFF]/g;
|
|
30
31
|
const WHITESPACE = /^[\n\r\f]/;
|
|
31
32
|
|
|
33
|
+
/**
|
|
34
|
+
* is content editable
|
|
35
|
+
* NOTE: not implemented in jsdom https://github.com/jsdom/jsdom/issues/1670
|
|
36
|
+
* @param {object} node - Element
|
|
37
|
+
* @returns {boolean} - result
|
|
38
|
+
*/
|
|
39
|
+
const isContentEditable = (node = {}) => {
|
|
40
|
+
let bool;
|
|
41
|
+
if (node.nodeType === ELEMENT_NODE) {
|
|
42
|
+
if (node.ownerDocument.designMode === 'on') {
|
|
43
|
+
bool = true;
|
|
44
|
+
} else if (node.hasAttribute('contenteditable')) {
|
|
45
|
+
const attr = node.getAttribute('contenteditable');
|
|
46
|
+
if (/^(?:plaintext-only|true)$/.test(attr) || attr === '') {
|
|
47
|
+
bool = true;
|
|
48
|
+
} else if (attr === 'inherit') {
|
|
49
|
+
let parent = node.parentNode;
|
|
50
|
+
while (parent) {
|
|
51
|
+
if (isContentEditable(parent)) {
|
|
52
|
+
bool = true;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
parent = parent.parentNode;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return !!bool;
|
|
61
|
+
};
|
|
62
|
+
|
|
32
63
|
/**
|
|
33
64
|
* unescape selector
|
|
34
65
|
* @param {string} selector - CSS selector
|
|
@@ -88,9 +119,9 @@ const collectNthChild = (anb = {}, node = {}) => {
|
|
|
88
119
|
const l = arr.length;
|
|
89
120
|
const items = [];
|
|
90
121
|
if (selector) {
|
|
91
|
-
const
|
|
92
|
-
if (
|
|
93
|
-
items.push(...
|
|
122
|
+
const ar = new Matcher(selector, ownerDocument).querySelectorAll();
|
|
123
|
+
if (ar.length) {
|
|
124
|
+
items.push(...ar);
|
|
94
125
|
}
|
|
95
126
|
}
|
|
96
127
|
// :first-child, :last-child, :nth-child(0 of S)
|
|
@@ -664,14 +695,12 @@ const matchPseudoClassSelector = (
|
|
|
664
695
|
switch (astName) {
|
|
665
696
|
case 'any-link':
|
|
666
697
|
case 'link':
|
|
667
|
-
|
|
668
|
-
if (node.hasAttribute('href')) {
|
|
698
|
+
if (/^a(?:rea)?$/.test(localName) && node.hasAttribute('href')) {
|
|
669
699
|
matched.push(node);
|
|
670
700
|
}
|
|
671
701
|
break;
|
|
672
702
|
case 'local-link':
|
|
673
|
-
|
|
674
|
-
if (node.hasAttribute('href')) {
|
|
703
|
+
if (/^a(?:rea)?$/.test(localName) && node.hasAttribute('href')) {
|
|
675
704
|
const attrURL = new URL(node.getAttribute('href'), docURL.href);
|
|
676
705
|
if (attrURL.origin === docURL.origin &&
|
|
677
706
|
attrURL.pathname === docURL.pathname) {
|
|
@@ -736,11 +765,24 @@ const matchPseudoClassSelector = (
|
|
|
736
765
|
}
|
|
737
766
|
break;
|
|
738
767
|
case 'disabled':
|
|
739
|
-
if (
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
768
|
+
if (HTML_FORM_INPUT.test(localName) ||
|
|
769
|
+
HTML_FORM_PARTS.test(localName) ||
|
|
770
|
+
isCustomElementName(localName)) {
|
|
771
|
+
if (node.hasAttribute('disabled')) {
|
|
772
|
+
matched.push(node);
|
|
773
|
+
} else {
|
|
774
|
+
let parent = node.parentNode;
|
|
775
|
+
while (parent) {
|
|
776
|
+
if (parent.localName === 'fieldset') {
|
|
777
|
+
break;
|
|
778
|
+
}
|
|
779
|
+
parent = parent.parentNode;
|
|
780
|
+
}
|
|
781
|
+
if (parent && parent.hasAttribute('disabled') &&
|
|
782
|
+
node.parentNode.localName !== 'legend') {
|
|
783
|
+
matched.push(node);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
744
786
|
}
|
|
745
787
|
break;
|
|
746
788
|
case 'enabled':
|
|
@@ -751,17 +793,75 @@ const matchPseudoClassSelector = (
|
|
|
751
793
|
matched.push(node);
|
|
752
794
|
}
|
|
753
795
|
break;
|
|
796
|
+
case 'read-only':
|
|
797
|
+
if (/^(?:input|textarea)$/.test(localName)) {
|
|
798
|
+
if (node.hasAttribute('readonly') ||
|
|
799
|
+
node.hasAttribute('disabled')) {
|
|
800
|
+
matched.push(node);
|
|
801
|
+
}
|
|
802
|
+
} else if (!isContentEditable(node)) {
|
|
803
|
+
matched.push(node);
|
|
804
|
+
}
|
|
805
|
+
break;
|
|
806
|
+
case 'read-write':
|
|
807
|
+
if (/^(?:input|textarea)$/.test(localName)) {
|
|
808
|
+
if (!(node.hasAttribute('readonly') ||
|
|
809
|
+
node.hasAttribute('disabled'))) {
|
|
810
|
+
matched.push(node);
|
|
811
|
+
}
|
|
812
|
+
} else if (isContentEditable(node)) {
|
|
813
|
+
matched.push(node);
|
|
814
|
+
}
|
|
815
|
+
break;
|
|
816
|
+
case 'placeholder-shown':
|
|
817
|
+
if (/^(?:input|textarea)$/.test(localName) &&
|
|
818
|
+
node.hasAttribute('placeholder') &&
|
|
819
|
+
node.getAttribute('placeholder').trim().length &&
|
|
820
|
+
node.value === '') {
|
|
821
|
+
matched.push(node);
|
|
822
|
+
}
|
|
823
|
+
break;
|
|
754
824
|
case 'checked':
|
|
755
|
-
if ((
|
|
825
|
+
if ((localName === 'input' && node.hasAttribute('type') &&
|
|
756
826
|
/^(?:checkbox|radio)$/.test(node.getAttribute('type')) &&
|
|
757
827
|
node.checked) ||
|
|
758
828
|
(localName === 'option' && node.selected)) {
|
|
759
829
|
matched.push(node);
|
|
760
830
|
}
|
|
761
831
|
break;
|
|
832
|
+
case 'indeterminate':
|
|
833
|
+
if ((localName === 'input' && node.type === 'checkbox' &&
|
|
834
|
+
node.indeterminate) ||
|
|
835
|
+
(localName === 'progress' && !node.hasAttribute('value'))) {
|
|
836
|
+
matched.push(node);
|
|
837
|
+
} else if (localName === 'input' && node.type === 'radio') {
|
|
838
|
+
const radioName = node.name;
|
|
839
|
+
let form = node;
|
|
840
|
+
while (form) {
|
|
841
|
+
if (form.localName === 'form') {
|
|
842
|
+
break;
|
|
843
|
+
}
|
|
844
|
+
form = form.parentNode;
|
|
845
|
+
}
|
|
846
|
+
if (form && radioName) {
|
|
847
|
+
const sel = `input[type="radio"][name="${radioName}"]`;
|
|
848
|
+
const arr = new Matcher(sel, form).querySelectorAll();
|
|
849
|
+
let checked;
|
|
850
|
+
for (const i of arr) {
|
|
851
|
+
checked = !!i.checked;
|
|
852
|
+
if (checked) {
|
|
853
|
+
break;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
if (!checked) {
|
|
857
|
+
matched.push(node);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
break;
|
|
762
862
|
case 'default':
|
|
763
863
|
// input[type="checkbox"], input[type="radio"]
|
|
764
|
-
if (
|
|
864
|
+
if (localName === 'input' && node.hasAttribute('type') &&
|
|
765
865
|
/^(?:checkbox|radio)$/.test(node.getAttribute('type'))) {
|
|
766
866
|
if (node.hasAttribute('checked')) {
|
|
767
867
|
matched.push(node);
|
|
@@ -806,12 +906,45 @@ const matchPseudoClassSelector = (
|
|
|
806
906
|
} else if ((localName === 'button' &&
|
|
807
907
|
(!node.hasAttribute('type') ||
|
|
808
908
|
node.getAttribute('type') === 'submit')) ||
|
|
809
|
-
(
|
|
909
|
+
(localName === 'input' && node.hasAttribute('type') &&
|
|
810
910
|
/^(?:image|submit)$/.test(node.getAttribute('type')))) {
|
|
811
911
|
throw new DOMException(`Unsupported pseudo-class ${astName}`,
|
|
812
912
|
'NotSupportedError');
|
|
813
913
|
}
|
|
814
914
|
break;
|
|
915
|
+
case 'valid':
|
|
916
|
+
if (HTML_FORM_INPUT.test(localName) ||
|
|
917
|
+
/^(?:f(?:ieldset|orm)|button|output)$/.test(localName)) {
|
|
918
|
+
if (node.checkValidity()) {
|
|
919
|
+
matched.push(node);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
break;
|
|
923
|
+
case 'invalid':
|
|
924
|
+
if (HTML_FORM_INPUT.test(localName) ||
|
|
925
|
+
/^(?:f(?:ieldset|orm)|button|output)$/.test(localName)) {
|
|
926
|
+
if (!node.checkValidity()) {
|
|
927
|
+
matched.push(node);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
break;
|
|
931
|
+
case 'in-range':
|
|
932
|
+
if (localName === 'input' &&
|
|
933
|
+
node.hasAttribute('min') && node.hasAttribute('max')) {
|
|
934
|
+
if (!(node.validity.rangeUnderflow ||
|
|
935
|
+
node.validity.rangeOverflow)) {
|
|
936
|
+
matched.push(node);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
break;
|
|
940
|
+
case 'out-of-range':
|
|
941
|
+
if (localName === 'input' &&
|
|
942
|
+
node.hasAttribute('min') && node.hasAttribute('max')) {
|
|
943
|
+
if (node.validity.rangeUnderflow || node.validity.rangeOverflow) {
|
|
944
|
+
matched.push(node);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
break;
|
|
815
948
|
case 'required':
|
|
816
949
|
if (HTML_FORM_INPUT.test(localName) && node.required) {
|
|
817
950
|
matched.push(node);
|
|
@@ -905,24 +1038,16 @@ const matchPseudoClassSelector = (
|
|
|
905
1038
|
case 'fullscreen':
|
|
906
1039
|
case 'future':
|
|
907
1040
|
case 'hover':
|
|
908
|
-
case 'indeterminate':
|
|
909
|
-
case 'invalid':
|
|
910
|
-
case 'in-range':
|
|
911
1041
|
case 'modal':
|
|
912
1042
|
case 'muted':
|
|
913
|
-
case 'out-of-range':
|
|
914
1043
|
case 'past':
|
|
915
1044
|
case 'paused':
|
|
916
1045
|
case 'picture-in-picture':
|
|
917
|
-
case 'placeholder-shown':
|
|
918
1046
|
case 'playing':
|
|
919
|
-
case 'read-only':
|
|
920
|
-
case 'read-write':
|
|
921
1047
|
case 'seeking':
|
|
922
1048
|
case 'stalled':
|
|
923
1049
|
case 'user-invalid':
|
|
924
1050
|
case 'user-valid':
|
|
925
|
-
case 'valid':
|
|
926
1051
|
case 'volume-locked':
|
|
927
1052
|
throw new DOMException(`Unsupported pseudo-class ${astName}`,
|
|
928
1053
|
'NotSupportedError');
|
|
@@ -1003,19 +1128,10 @@ class Matcher {
|
|
|
1003
1128
|
* @returns {boolean} - result
|
|
1004
1129
|
*/
|
|
1005
1130
|
_isAttached() {
|
|
1006
|
-
const root = this.#document
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
while (node) {
|
|
1011
|
-
if (node === root) {
|
|
1012
|
-
bool = true;
|
|
1013
|
-
break;
|
|
1014
|
-
}
|
|
1015
|
-
node = node.parentNode;
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
return !!bool;
|
|
1131
|
+
const root = this.#document.documentElement;
|
|
1132
|
+
const posBit =
|
|
1133
|
+
this.#node.compareDocumentPosition(root) & DOCUMENT_POSITION_CONTAINS;
|
|
1134
|
+
return !!posBit;
|
|
1019
1135
|
};
|
|
1020
1136
|
|
|
1021
1137
|
/**
|
|
@@ -1587,6 +1703,7 @@ module.exports = {
|
|
|
1587
1703
|
Matcher,
|
|
1588
1704
|
collectNthChild,
|
|
1589
1705
|
collectNthOfType,
|
|
1706
|
+
isContentEditable,
|
|
1590
1707
|
matchAnPlusB,
|
|
1591
1708
|
matchAttributeSelector,
|
|
1592
1709
|
matchClassSelector,
|
package/types/js/matcher.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ export function collectNthOfType(anb?: {
|
|
|
28
28
|
b: number;
|
|
29
29
|
reverse?: boolean;
|
|
30
30
|
}, node?: object): Array<object | undefined>;
|
|
31
|
+
export function isContentEditable(node?: object): boolean;
|
|
31
32
|
export function matchAnPlusB(nthName: string, ast?: object, node?: object): Array<object | undefined>;
|
|
32
33
|
export function matchAttributeSelector(ast?: object, node?: object): object | null;
|
|
33
34
|
export function matchClassSelector(ast?: object, node?: object): object | null;
|