@asamuzakjp/dom-selector 8.0.1 → 8.0.2

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/src/js/utility.js CHANGED
@@ -4,36 +4,19 @@
4
4
 
5
5
  /* import */
6
6
  import bidiFactory from 'bidi-js';
7
- import * as cssTree from 'css-tree';
8
7
  import isCustomElementName from 'is-potential-custom-element-name';
9
8
 
10
9
  /* constants */
11
10
  import {
12
- ATRULE,
13
- COMBO,
14
- COMPOUND_I,
15
- DESCEND,
16
11
  DOCUMENT_FRAGMENT_NODE,
17
12
  DOCUMENT_NODE,
18
13
  DOCUMENT_POSITION_CONTAINS,
19
14
  DOCUMENT_POSITION_PRECEDING,
20
15
  ELEMENT_NODE,
21
- HAS_COMPOUND,
22
16
  INPUT_BUTTON,
23
17
  INPUT_EDIT,
24
18
  INPUT_LTR,
25
19
  INPUT_TEXT,
26
- KEYS_LOGICAL,
27
- LOGIC_COMPLEX,
28
- LOGIC_COMPOUND,
29
- N_TH,
30
- PSEUDO_CLASS,
31
- RULE,
32
- SCOPE,
33
- SELECTOR_LIST,
34
- TAG_TYPE,
35
- TARGET_ALL,
36
- TARGET_FIRST,
37
20
  TEXT_NODE,
38
21
  TYPE_FROM,
39
22
  TYPE_TO
@@ -58,105 +41,9 @@ const KEYS_NODE_FOCUSABLE_SVG = new Set([
58
41
  'symbol',
59
42
  'title'
60
43
  ]);
61
- const REG_ATTR_SIMPLE = /^\[[A-Z\d-]{1,255}(?:="?[A-Z\d\s-]{1,255}"?)?\]$/i;
62
- const REG_TAG_SIMPLE = new RegExp(`^(?:${TAG_TYPE})$`);
63
- const REG_EXCLUDE_BASIC =
64
- /[|\\]|::|[^\u0021-\u007F\s]|\[\s*[\w$*=^|~-]+(?:(?:"[\w$*=^|~\s'-]+"|'[\w$*=^|~\s"-]+')?(?:\s+[\w$*=^|~-]+)+|"[^"\]]{1,255}|'[^'\]]{1,255})\s*\]|:(?:is|where)\(\s*\)/;
65
- const REG_COMPLEX = new RegExp(`${COMPOUND_I}${COMBO}${COMPOUND_I}`, 'i');
66
- const REG_DESCEND = new RegExp(`${COMPOUND_I}${DESCEND}${COMPOUND_I}`, 'i');
67
- const REG_LOGIC_COMPLEX = new RegExp(
68
- `:(?!${PSEUDO_CLASS}|${N_TH}|${LOGIC_COMPLEX})`
69
- );
70
- const REG_LOGIC_COMPOUND = new RegExp(
71
- `:(?!${PSEUDO_CLASS}|${N_TH}|${LOGIC_COMPOUND})`
72
- );
73
- const REG_LOGIC_HAS_COMPOUND = new RegExp(
74
- `:(?!${PSEUDO_CLASS}|${N_TH}|${LOGIC_COMPOUND}|${HAS_COMPOUND})`
75
- );
76
- const REG_END_WITH_HAS = new RegExp(`:${HAS_COMPOUND}$`);
77
- const REG_WO_LOGICAL = new RegExp(`:(?!${PSEUDO_CLASS}|${N_TH})`);
78
44
  const REG_IS_HTML = /^(?:application\/xhtml\+x|text\/ht)ml$/;
79
45
  const REG_IS_XML =
80
46
  /^(?:application\/(?:[\w\-.]+\+)?|image\/[\w\-.]+\+|text\/)xml$/;
81
- const REG_COMBO = new RegExp(COMBO);
82
- const REG_ID = /#(\D[^#.*]+)/g;
83
- const REG_CLASS = /\.(\D[^#.*]+)/g;
84
- const REG_TAG = /^([^#.]+)/;
85
-
86
- /**
87
- * Manages state for extracting nested selectors from a CSS AST.
88
- */
89
- class SelectorExtractor {
90
- constructor() {
91
- this.selectors = [];
92
- this.isScoped = false;
93
- }
94
-
95
- /**
96
- * Walker enter function.
97
- * @param {object} node - The AST node.
98
- */
99
- enter(node) {
100
- switch (node.type) {
101
- case ATRULE: {
102
- if (node.name === 'scope') {
103
- this.isScoped = true;
104
- }
105
- break;
106
- }
107
- case SCOPE: {
108
- const { children, type } = node.root;
109
- const arr = [];
110
- if (type === SELECTOR_LIST) {
111
- for (const child of children) {
112
- const selector = cssTree.generate(child);
113
- arr.push(selector);
114
- }
115
- this.selectors.push(arr);
116
- }
117
- break;
118
- }
119
- case RULE: {
120
- const { children, type } = node.prelude;
121
- const arr = [];
122
- if (type === SELECTOR_LIST) {
123
- let hasAmp = false;
124
- for (const child of children) {
125
- const selector = cssTree.generate(child);
126
- if (this.isScoped && !hasAmp) {
127
- hasAmp = /\x26/.test(selector);
128
- }
129
- arr.push(selector);
130
- }
131
- if (this.isScoped) {
132
- if (hasAmp) {
133
- this.selectors.push(arr);
134
- /* FIXME:
135
- } else {
136
- this.selectors = arr;
137
- this.isScoped = false;
138
- */
139
- }
140
- } else {
141
- this.selectors.push(arr);
142
- }
143
- }
144
- }
145
- }
146
- }
147
-
148
- /**
149
- * Walker leave function.
150
- * @param {object} node - The AST node.
151
- */
152
- leave(node) {
153
- if (node.type === ATRULE) {
154
- if (node.name === 'scope') {
155
- this.isScoped = false;
156
- }
157
- }
158
- }
159
- }
160
47
 
161
48
  /**
162
49
  * Get type of an object.
@@ -199,27 +86,6 @@ export const generateException = (msg, name, globalObject = globalThis) => {
199
86
  return new globalObject.DOMException(msg, name);
200
87
  };
201
88
 
202
- /**
203
- * Find a nested :has() pseudo-class.
204
- * @param {object} leaf - The AST leaf to check.
205
- * @returns {?object} The leaf if it's :has, otherwise null.
206
- */
207
- export const findNestedHas = leaf => {
208
- return leaf.name === 'has';
209
- };
210
-
211
- /**
212
- * Find a logical pseudo-class that contains a nested :has().
213
- * @param {object} leaf - The AST leaf to check.
214
- * @returns {?object} The leaf if it matches, otherwise null.
215
- */
216
- export const findLogicalWithNestedHas = leaf => {
217
- if (KEYS_LOGICAL.has(leaf.name) && cssTree.find(leaf, findNestedHas)) {
218
- return leaf;
219
- }
220
- return null;
221
- };
222
-
223
89
  /**
224
90
  * Filter a list of nodes based on An+B logic
225
91
  * @param {Array.<object>} nodes - array of nodes to filter
@@ -928,203 +794,3 @@ export const sortNodes = (nodes = []) => {
928
794
  }
929
795
  return arr;
930
796
  };
931
-
932
- /**
933
- * Concat an array of nested selectors into an equivalent single selector.
934
- * @param {Array.<Array.<string>>} selectors - [parents, children, ...].
935
- * @returns {string} - The concatenated selector.
936
- */
937
- export const concatNestedSelectors = selectors => {
938
- if (!Array.isArray(selectors)) {
939
- throw new TypeError(`Unexpected type ${getType(selectors)}`);
940
- }
941
- let selector = '';
942
- if (selectors.length) {
943
- const revSelectors = selectors.toReversed();
944
- let child = verifyArray(revSelectors.shift(), 'String');
945
- if (child.length === 1) {
946
- [child] = child;
947
- }
948
- while (revSelectors.length) {
949
- const parentArr = verifyArray(revSelectors.shift(), 'String');
950
- if (!parentArr.length) {
951
- continue;
952
- }
953
- let parent;
954
- if (parentArr.length === 1) {
955
- [parent] = parentArr;
956
- if (!/^[>~+]/.test(parent) && /[\s>~+]/.test(parent)) {
957
- parent = `:is(${parent})`;
958
- }
959
- } else {
960
- parent = `:is(${parentArr.join(', ')})`;
961
- }
962
- if (selector.includes('\x26')) {
963
- selector = selector.replace(/\x26/g, parent);
964
- }
965
- if (Array.isArray(child)) {
966
- const items = [];
967
- for (let item of child) {
968
- if (item.includes('\x26')) {
969
- if (/^[>~+]/.test(item)) {
970
- item = `${parent} ${item.replace(/\x26/g, parent)} ${selector}`;
971
- } else {
972
- item = `${item.replace(/\x26/g, parent)} ${selector}`;
973
- }
974
- } else {
975
- item = `${parent} ${item} ${selector}`;
976
- }
977
- items.push(item.trim());
978
- }
979
- selector = items.join(', ');
980
- } else if (revSelectors.length) {
981
- selector = `${child} ${selector}`;
982
- } else {
983
- if (child.includes('\x26')) {
984
- if (/^[>~+]/.test(child)) {
985
- selector = `${parent} ${child.replace(/\x26/g, parent)} ${selector}`;
986
- } else {
987
- selector = `${child.replace(/\x26/g, parent)} ${selector}`;
988
- }
989
- } else {
990
- selector = `${parent} ${child} ${selector}`;
991
- }
992
- }
993
- selector = selector.trim();
994
- if (revSelectors.length) {
995
- child = parentArr.length > 1 ? parentArr : parent;
996
- } else {
997
- break;
998
- }
999
- }
1000
- selector = selector.replace(/\x26/g, ':scope').trim();
1001
- }
1002
- return selector;
1003
- };
1004
-
1005
- /**
1006
- * Extract nested selectors from CSSRule.cssText.
1007
- * @param {string} css - CSSRule.cssText.
1008
- * @returns {Array.<Array.<string>>} - Array of nested selectors.
1009
- */
1010
- export const extractNestedSelectors = css => {
1011
- const ast = cssTree.parse(css, {
1012
- context: 'rule'
1013
- });
1014
- const extractor = new SelectorExtractor();
1015
- cssTree.walk(ast, {
1016
- enter: extractor.enter.bind(extractor),
1017
- leave: extractor.leave.bind(extractor)
1018
- });
1019
- return extractor.selectors;
1020
- };
1021
-
1022
- /**
1023
- * Extracts the rightmost subject keys (id, class, tag) from a selector.
1024
- * @param {string} selector - The CSS selector string to parse.
1025
- * @param {boolean} caseSensitive - True if the tag should be case-sensitive.
1026
- * @returns {Array<{id: string|null, className: string|null, tag: string|null}>} The list of extracted keys for each selector group.
1027
- */
1028
- export const extractSubjectsRegExp = (selector, caseSensitive) => {
1029
- const subjects = [];
1030
- const groups = selector.split(',');
1031
- for (let i = 0; i < groups.length; i++) {
1032
- const group = groups[i].trim();
1033
- if (!group) {
1034
- continue;
1035
- }
1036
- const compounds = group.split(REG_COMBO);
1037
- const rightmost = compounds[compounds.length - 1];
1038
- let idKey = null;
1039
- let classKey = null;
1040
- let tagKey = null;
1041
- if (rightmost) {
1042
- const idMatch = rightmost.match(REG_ID);
1043
- if (idMatch) {
1044
- idKey = idMatch[idMatch.length - 1].slice(1);
1045
- }
1046
- const classMatch = rightmost.match(REG_CLASS);
1047
- if (classMatch) {
1048
- classKey = classMatch[classMatch.length - 1].slice(1);
1049
- }
1050
- const tagMatch = rightmost.match(REG_TAG);
1051
- if (tagMatch) {
1052
- const tag = tagMatch[1];
1053
- if (tag !== '*') {
1054
- tagKey = caseSensitive ? tag : tag.toLowerCase();
1055
- }
1056
- }
1057
- }
1058
- subjects.push({ id: idKey, className: classKey, tag: tagKey });
1059
- }
1060
- return subjects;
1061
- };
1062
-
1063
- /**
1064
- * Filter a selector for use with nwsapi.
1065
- * @param {string} selector - The selector string.
1066
- * @param {string} target - The target type.
1067
- * @returns {boolean} - True if the selector is valid for nwsapi.
1068
- */
1069
- export const filterSelector = (selector, target) => {
1070
- const isQuerySelectorAll = target === TARGET_ALL;
1071
- // Basic validation and fast-fail for null/undefined/non-string values.
1072
- if (
1073
- !selector ||
1074
- typeof selector !== 'string' ||
1075
- /null|undefined/.test(selector)
1076
- ) {
1077
- return false;
1078
- }
1079
- // Exclude various complex or unsupported selectors early.
1080
- // i.e. non-ASCII, escaped selectors, namespaced selectors, pseudo-elements.
1081
- if (selector.includes('/') || REG_EXCLUDE_BASIC.test(selector)) {
1082
- return false;
1083
- }
1084
- // Validate attribute selector integrity.
1085
- if (selector.includes('[')) {
1086
- const index = selector.lastIndexOf('[');
1087
- if (selector.indexOf(']', index) === -1) {
1088
- return false;
1089
- }
1090
- }
1091
- // Target-specific early exits.
1092
- if (target === TARGET_FIRST) {
1093
- return REG_ATTR_SIMPLE.test(selector);
1094
- }
1095
- if (target === TARGET_ALL && REG_TAG_SIMPLE.test(selector)) {
1096
- return false;
1097
- }
1098
- // Logic for pseudo-classes.
1099
- if (selector.includes(':')) {
1100
- // Exclude descendant combinators in logical selectors for querySelectorAll.
1101
- if (isQuerySelectorAll && REG_DESCEND.test(selector)) {
1102
- return false;
1103
- }
1104
- // Determine if the selector has complex logical structures.
1105
- const isComplex = isQuerySelectorAll ? false : REG_COMPLEX.test(selector);
1106
- // Handle :has() specifically.
1107
- if (selector.includes(':has(')) {
1108
- if (isQuerySelectorAll) {
1109
- return false;
1110
- }
1111
- if (!isComplex || REG_LOGIC_HAS_COMPOUND.test(selector)) {
1112
- return false;
1113
- }
1114
- return REG_END_WITH_HAS.test(selector);
1115
- }
1116
- // Handle :is() and :not().
1117
- if (/(?:is|not)\(/.test(selector)) {
1118
- if (isComplex) {
1119
- return !REG_LOGIC_COMPLEX.test(selector);
1120
- } else {
1121
- return !REG_LOGIC_COMPOUND.test(selector);
1122
- }
1123
- }
1124
- // Default check for other pseudo-classes against known list.
1125
- if (REG_WO_LOGICAL.test(selector)) {
1126
- return false;
1127
- }
1128
- }
1129
- return true;
1130
- };
package/types/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export class DOMSelector {
6
6
  className: string | null;
7
7
  tag: string | null;
8
8
  }>;
9
+ supports: (selector: string) => boolean;
9
10
  check: (selector: string, node: Element, opt?: object) => CheckResult;
10
11
  matches: (selector: string, node: Element, opt?: object) => boolean;
11
12
  closest: (selector: string, node: Element, opt?: object) => Element | null;
@@ -75,3 +75,6 @@ export const INPUT_TEXT: readonly string[];
75
75
  export const INPUT_EDIT: readonly string[];
76
76
  export const INPUT_LTR: readonly string[];
77
77
  export const KEYS_LOGICAL: Set<string>;
78
+ export const KEYS_PS_CLASS_SUPPORTED: Set<string>;
79
+ export const KEYS_PS_CLASS_UNSUPPORTED: Set<string>;
80
+ export const KEYS_PS_ELEMENT_UNSUPPORTED: Set<string>;
@@ -1,7 +1,7 @@
1
1
  export function unescapeSelector(selector?: string): string;
2
2
  export function preprocess(value: string): string;
3
3
  export function parseSelector(sel: string): object;
4
- export function walkAST(ast?: object, toObject?: boolean): {
4
+ export function walkAST(ast?: object, toObject?: boolean, callback?: (arg0: object) => void): {
5
5
  branches: Array<object>;
6
6
  info: object;
7
7
  };
@@ -0,0 +1,12 @@
1
+ export function findNestedHas(leaf: object): object | null;
2
+ export function findLogicalWithNestedHas(leaf: object): object | null;
3
+ export function validateHasNesting(astChildren: Array<object>): boolean;
4
+ export function createHasValidator(globalObj: object): (arg0: object) => void;
5
+ export function isInvalidCombinator(type: string, prevType: string | null, isLast: boolean): boolean;
6
+ export function isSupportedAST(ast: object): boolean;
7
+ export function extractSubjectsRegExp(selector: string, caseSensitive: boolean): Array<{
8
+ id: string | null;
9
+ className: string | null;
10
+ tag: string | null;
11
+ }>;
12
+ export function filterSelector(selector: string, target: string): boolean;
@@ -1,8 +1,6 @@
1
1
  export function getType(o: object): string;
2
2
  export function verifyArray(arr: any[], type: string): any[];
3
3
  export function generateException(msg: string, name: string, globalObject?: object): DOMException;
4
- export function findNestedHas(leaf: object): object | null;
5
- export function findLogicalWithNestedHas(leaf: object): object | null;
6
4
  export function filterNodesByAnB(nodes: Array<object>, anb: {
7
5
  a: number;
8
6
  b: number;
@@ -26,11 +24,3 @@ export function isNamespaceDeclared(ns?: string, node?: object): boolean;
26
24
  export function isPreceding(nodeA: object, nodeB: object): boolean;
27
25
  export function compareNodes(a: object, b: object): number;
28
26
  export function sortNodes(nodes?: Array<object> | Set<object>): Array<object>;
29
- export function concatNestedSelectors(selectors: Array<Array<string>>): string;
30
- export function extractNestedSelectors(css: string): Array<Array<string>>;
31
- export function extractSubjectsRegExp(selector: string, caseSensitive: boolean): Array<{
32
- id: string | null;
33
- className: string | null;
34
- tag: string | null;
35
- }>;
36
- export function filterSelector(selector: string, target: string): boolean;