@asamuzakjp/dom-selector 0.14.0 → 0.15.3

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 CHANGED
@@ -29,52 +29,49 @@ const {
29
29
 
30
30
  ### matches(selector, node, opt)
31
31
 
32
- matches - [Element.matches()][64]
32
+ matches - same functionality as [Element.matches()][64]
33
33
 
34
34
  #### Parameters
35
35
 
36
36
  - `selector` **[string][59]** CSS selector
37
37
  - `node` **[object][60]** Element node
38
38
  - `opt` **[object][60]?** options
39
- - `opt.globalObject` **[object][60]?** global object, e.g. `window`, `globalThis`
40
- - `opt.jsdom` **[boolean][61]?** is jsdom
39
+ - `opt.warn` **[boolean][61]?** console warn e.g. unsupported pseudo-class
41
40
 
42
41
  Returns **[boolean][61]** result
43
42
 
44
43
 
45
44
  ### closest(selector, node, opt)
46
45
 
47
- closest - [Element.closest()][65]
46
+ closest - same functionality as [Element.closest()][65]
48
47
 
49
48
  #### Parameters
50
49
 
51
50
  - `selector` **[string][59]** CSS selector
52
51
  - `node` **[object][60]** Element node
53
52
  - `opt` **[object][60]?** options
54
- - `opt.globalObject` **[object][60]?** global object, e.g. `window`, `globalThis`
55
- - `opt.jsdom` **[boolean][61]?** is jsdom
53
+ - `opt.warn` **[boolean][61]?** console warn e.g. unsupported pseudo-class
56
54
 
57
55
  Returns **[object][60]?** matched node
58
56
 
59
57
 
60
58
  ### querySelector(selector, refPoint, opt)
61
59
 
62
- querySelector - [Document.querySelector()][66], [DocumentFragment.querySelector()][67], [Element.querySelector()][68]
60
+ querySelector - same functionality as [Document.querySelector()][66], [DocumentFragment.querySelector()][67], [Element.querySelector()][68]
63
61
 
64
62
  #### Parameters
65
63
 
66
64
  - `selector` **[string][59]** CSS selector
67
65
  - `refPoint` **[object][60]** Document, DocumentFragment or Element node
68
66
  - `opt` **[object][60]?** options
69
- - `opt.globalObject` **[object][60]?** global object, e.g. `window`, `globalThis`
70
- - `opt.jsdom` **[boolean][61]?** is jsdom
67
+ - `opt.warn` **[boolean][61]?** console warn e.g. unsupported pseudo-class
71
68
 
72
69
  Returns **[object][60]?** matched node
73
70
 
74
71
 
75
72
  ### querySelectorAll(selector, refPoint, opt)
76
73
 
77
- querySelectorAll - [Document.querySelectorAll()][69], [Document.querySelectorAll()][70], [Element.querySelectorAll()][71]
74
+ querySelectorAll - same functionality as [Document.querySelectorAll()][69], [DocumentFragment.querySelectorAll()][70], [Element.querySelectorAll()][71]
78
75
  **NOTE**: returns Array, not NodeList
79
76
 
80
77
  #### Parameters
@@ -82,8 +79,7 @@ querySelectorAll - [Document.querySelectorAll()][69], [Document.querySelectorAll
82
79
  - `selector` **[string][59]** CSS selector
83
80
  - `refPoint` **[object][60]** Document, DocumentFragment or Element node
84
81
  - `opt` **[object][60]?** options
85
- - `opt.globalObject` **[object][60]?** global object, e.g. `window`, `globalThis`
86
- - `opt.jsdom` **[boolean][61]?** is jsdom
82
+ - `opt.warn` **[boolean][61]?** console warn e.g. unsupported pseudo-class
87
83
 
88
84
  Returns **[Array][62]<([object][60] \| [undefined][63])>** array of matched nodes
89
85
 
package/package.json CHANGED
@@ -31,7 +31,7 @@
31
31
  "eslint-plugin-jsdoc": "^44.2.7",
32
32
  "eslint-plugin-regexp": "^1.15.0",
33
33
  "eslint-plugin-unicorn": "^47.0.0",
34
- "jsdom": "^22.0.0",
34
+ "jsdom": "^22.1.0",
35
35
  "mocha": "^10.2.0",
36
36
  "sinon": "^15.1.0",
37
37
  "typescript": "^5.0.4"
@@ -42,5 +42,5 @@
42
42
  "test": "c8 --reporter=text mocha --exit test/**/*.test.js",
43
43
  "tsc": "npx tsc"
44
44
  },
45
- "version": "0.14.0"
45
+ "version": "0.15.3"
46
46
  }
package/src/index.js CHANGED
@@ -14,8 +14,7 @@ const { Matcher } = require('./js/matcher.js');
14
14
  * @param {string} selector - CSS selector
15
15
  * @param {object} node - Element node
16
16
  * @param {object} [opt] - options
17
- * @param {object} [opt.globalObject] - global object
18
- * @param {boolean} [opt.jsdom] - is jsdom
17
+ * @param {object} [opt.warn] - console warn e.g. unsupported pseudo-class
19
18
  * @returns {boolean} - result
20
19
  */
21
20
  const matches = (selector, node, opt) => {
@@ -28,8 +27,7 @@ const matches = (selector, node, opt) => {
28
27
  * @param {string} selector - CSS selector
29
28
  * @param {object} node - Element node
30
29
  * @param {object} [opt] - options
31
- * @param {object} [opt.globalObject] - global object
32
- * @param {boolean} [opt.jsdom] - is jsdom
30
+ * @param {object} [opt.warn] - console warn e.g. unsupported pseudo-class
33
31
  * @returns {?object} - matched node
34
32
  */
35
33
  const closest = (selector, node, opt) => {
@@ -42,8 +40,7 @@ const closest = (selector, node, opt) => {
42
40
  * @param {string} selector - CSS selector
43
41
  * @param {object} refPoint - Document or Element node
44
42
  * @param {object} [opt] - options
45
- * @param {object} [opt.globalObject] - global object
46
- * @param {boolean} [opt.jsdom] - is jsdom
43
+ * @param {object} [opt.warn] - console warn e.g. unsupported pseudo-class
47
44
  * @returns {?object} - matched node
48
45
  */
49
46
  const querySelector = (selector, refPoint, opt) => {
@@ -57,8 +54,7 @@ const querySelector = (selector, refPoint, opt) => {
57
54
  * @param {string} selector - CSS selector
58
55
  * @param {object} refPoint - Document or Element node
59
56
  * @param {object} [opt] - options
60
- * @param {object} [opt.globalObject] - global object
61
- * @param {boolean} [opt.jsdom] - is jsdom
57
+ * @param {object} [opt.warn] - console warn e.g. unsupported pseudo-class
62
58
  * @returns {Array.<object|undefined>} - array of matched nodes
63
59
  */
64
60
  const querySelectorAll = (selector, refPoint, opt) => {
package/src/js/matcher.js CHANGED
@@ -26,7 +26,8 @@ const HEX_CAPTURE = /^([\da-f]{1,6}\s?)/i;
26
26
  const HTML_FORM_INPUT = /^(?:(?:inpu|selec)t|textarea)$/;
27
27
  const HTML_FORM_PARTS = /^(?:button|fieldset|opt(?:group|ion))$/;
28
28
  const HTML_INTERACT = /^d(?:etails|ialog)$/;
29
- const INPUT_TYPE_BARRED = /^(?:(?:butto|hidde)n|reset)$/;
29
+ const INPUT_PLACEHOLDER = /^(?:(?:emai|te|ur)l|number|password|search|text)$/;
30
+ const INPUT_RANGE = /(?:(?:rang|tim)e|date(?:time-local)?|month|number|week)$/;
30
31
  const PSEUDO_FUNC = /^(?:(?:ha|i)s|not|where)$/;
31
32
  const PSEUDO_NTH = /^nth-(?:last-)?(?:child|of-type)$/;
32
33
  const WHITESPACE = /^[\n\r\f]/;
@@ -38,19 +39,19 @@ const WHITESPACE = /^[\n\r\f]/;
38
39
  * @returns {boolean} - result
39
40
  */
40
41
  const isContentEditable = (node = {}) => {
41
- let bool;
42
+ let res;
42
43
  if (node.nodeType === ELEMENT_NODE) {
43
44
  if (node.ownerDocument.designMode === 'on') {
44
- bool = true;
45
+ res = true;
45
46
  } else if (node.hasAttribute('contenteditable')) {
46
47
  const attr = node.getAttribute('contenteditable');
47
48
  if (/^(?:plaintext-only|true)$/.test(attr) || attr === '') {
48
- bool = true;
49
+ res = true;
49
50
  } else if (attr === 'inherit') {
50
51
  let parent = node.parentNode;
51
52
  while (parent) {
52
53
  if (isContentEditable(parent)) {
53
- bool = true;
54
+ res = true;
54
55
  break;
55
56
  }
56
57
  parent = parent.parentNode;
@@ -58,7 +59,7 @@ const isContentEditable = (node = {}) => {
58
59
  }
59
60
  }
60
61
  }
61
- return !!bool;
62
+ return !!res;
62
63
  };
63
64
 
64
65
  /**
@@ -85,6 +86,27 @@ const isNamespaceDeclared = (ns = '', node = {}) => {
85
86
  return !!res;
86
87
  };
87
88
 
89
+ /**
90
+ * is node attached to owner document
91
+ * @param {object} node - Element node
92
+ * @returns {boolean} - result
93
+ */
94
+ const isAttached = (node = {}) => {
95
+ const { nodeType, ownerDocument } = node;
96
+ let res;
97
+ if (nodeType === ELEMENT_NODE && ownerDocument) {
98
+ const root = ownerDocument.documentElement;
99
+ if (node === root) {
100
+ res = true;
101
+ } else {
102
+ const posBit =
103
+ node.compareDocumentPosition(root) & DOCUMENT_POSITION_CONTAINS;
104
+ res = posBit;
105
+ }
106
+ }
107
+ return !!res;
108
+ };
109
+
88
110
  /**
89
111
  * unescape selector
90
112
  * @param {string} selector - CSS selector
@@ -150,7 +172,7 @@ const createSelectorForNode = (node = {}) => {
150
172
  }
151
173
  }
152
174
  }
153
- return res || null;
175
+ return res ?? null;
154
176
  };
155
177
 
156
178
  /**
@@ -888,7 +910,7 @@ const matchDirectionPseudoClass = (ast = {}, node = {}) => {
888
910
  const { type: astType } = ast;
889
911
  const { dir: nodeDir, localName, nodeType, type: inputType } = node;
890
912
  let res;
891
- if (astType === IDENTIFIER && nodeType === ELEMENT_NODE) {
913
+ if (astType === IDENTIFIER && nodeType === ELEMENT_NODE && isAttached(node)) {
892
914
  const astName = unescapeSelector(ast.name);
893
915
  let dir;
894
916
  if (/^(?:ltr|rtl)$/.test(nodeDir)) {
@@ -1028,13 +1050,13 @@ const matchPseudoClassSelector = (
1028
1050
  } else if (astName === 'dir') {
1029
1051
  const res = matchDirectionPseudoClass(branch, node);
1030
1052
  if (res) {
1031
- matched.push(node);
1053
+ matched.push(res);
1032
1054
  }
1033
1055
  // :lang()
1034
1056
  } else if (astName === 'lang') {
1035
1057
  const res = matchLanguagePseudoClass(branch, node);
1036
1058
  if (res) {
1037
- matched.push(node);
1059
+ matched.push(res);
1038
1060
  }
1039
1061
  } else {
1040
1062
  switch (astName) {
@@ -1165,8 +1187,8 @@ const matchPseudoClassSelector = (
1165
1187
  }
1166
1188
  case 'read-only': {
1167
1189
  if (/^(?:input|textarea)$/.test(localName)) {
1168
- if (node.hasAttribute('readonly') ||
1169
- node.hasAttribute('disabled')) {
1190
+ if (node.readonly || node.hasAttribute('readonly') ||
1191
+ node.disabled || node.hasAttribute('disabled')) {
1170
1192
  matched.push(node);
1171
1193
  }
1172
1194
  } else if (!isContentEditable(node)) {
@@ -1176,8 +1198,8 @@ const matchPseudoClassSelector = (
1176
1198
  }
1177
1199
  case 'read-write': {
1178
1200
  if (/^(?:input|textarea)$/.test(localName)) {
1179
- if (!(node.hasAttribute('readonly') ||
1180
- node.hasAttribute('disabled'))) {
1201
+ if (!(node.readonly || node.hasAttribute('readonly') ||
1202
+ node.disabled || node.hasAttribute('disabled'))) {
1181
1203
  matched.push(node);
1182
1204
  }
1183
1205
  } else if (isContentEditable(node)) {
@@ -1186,7 +1208,10 @@ const matchPseudoClassSelector = (
1186
1208
  break;
1187
1209
  }
1188
1210
  case 'placeholder-shown': {
1189
- if (/^(?:input|textarea)$/.test(localName) &&
1211
+ if (((localName === 'input' &&
1212
+ (!node.hasAttribute('type') ||
1213
+ INPUT_PLACEHOLDER.test(node.getAttribute('type')))) ||
1214
+ localName === 'textarea') &&
1190
1215
  node.hasAttribute('placeholder') &&
1191
1216
  node.getAttribute('placeholder').trim().length &&
1192
1217
  node.value === '') {
@@ -1345,26 +1370,26 @@ const matchPseudoClassSelector = (
1345
1370
  break;
1346
1371
  }
1347
1372
  case 'in-range': {
1348
- if (localName === 'input' && !node.hasAttribute('readonly') &&
1349
- !node.hasAttribute('disabled') &&
1373
+ if (localName === 'input' &&
1374
+ !(node.readonly || node.hasAttribute('readonly')) &&
1375
+ !(node.disabled || node.hasAttribute('disabled')) &&
1376
+ node.hasAttribute('type') &&
1377
+ INPUT_RANGE.test(node.getAttribute('type')) &&
1350
1378
  !(node.validity.rangeUnderflow || node.validity.rangeOverflow)) {
1351
- if (!(node.hasAttribute('type') &&
1352
- INPUT_TYPE_BARRED.test(node.getAttribute('type'))) &&
1353
- node.hasAttribute('min') && node.hasAttribute('max')) {
1354
- matched.push(node);
1355
- } else if (node.getAttribute('type') === 'range') {
1379
+ if (node.hasAttribute('min') || node.hasAttribute('max') ||
1380
+ node.getAttribute('type') === 'range') {
1356
1381
  matched.push(node);
1357
1382
  }
1358
1383
  }
1359
1384
  break;
1360
1385
  }
1361
1386
  case 'out-of-range': {
1362
- if (localName === 'input' && !node.hasAttribute('readonly') &&
1363
- !node.hasAttribute('disabled') &&
1364
- (node.validity.rangeUnderflow || node.validity.rangeOverflow) &&
1365
- !(node.hasAttribute('type') &&
1366
- INPUT_TYPE_BARRED.test(node.getAttribute('type'))) &&
1367
- node.hasAttribute('min') && node.hasAttribute('max')) {
1387
+ if (localName === 'input' &&
1388
+ !(node.readonly || node.hasAttribute('readonly')) &&
1389
+ !(node.disabled || node.hasAttribute('disabled')) &&
1390
+ node.hasAttribute('type') &&
1391
+ INPUT_RANGE.test(node.getAttribute('type')) &&
1392
+ (node.validity.rangeUnderflow || node.validity.rangeOverflow)) {
1368
1393
  matched.push(node);
1369
1394
  }
1370
1395
  break;
@@ -1460,12 +1485,13 @@ const matchPseudoClassSelector = (
1460
1485
  }
1461
1486
  break;
1462
1487
  }
1488
+ // legacy pseudo-elements
1463
1489
  case 'after':
1464
1490
  case 'before':
1465
1491
  case 'first-letter':
1466
1492
  case 'first-line': {
1467
- // legacy pseudo-elements
1468
- break;
1493
+ throw new DOMException(`Unsupported pseudo-element ::${astName}`,
1494
+ 'NotSupportedError');
1469
1495
  }
1470
1496
  case 'active':
1471
1497
  case 'autofill':
@@ -1528,11 +1554,11 @@ const matchPseudoElementSelector = (ast = {}, node = {}) => {
1528
1554
  case 'selection':
1529
1555
  case 'slotted':
1530
1556
  case 'target-text': {
1531
- throw new DOMException(`Unsupported pseudo-element ${astName}`,
1557
+ throw new DOMException(`Unsupported pseudo-element ::${astName}`,
1532
1558
  'NotSupportedError');
1533
1559
  }
1534
1560
  default: {
1535
- throw new DOMException(`Unknown pseudo-element ${astName}`,
1561
+ throw new DOMException(`Unknown pseudo-element ::${astName}`,
1536
1562
  'SyntaxError');
1537
1563
  }
1538
1564
  }
@@ -1564,17 +1590,6 @@ class Matcher {
1564
1590
  this.#warn = !!warn;
1565
1591
  }
1566
1592
 
1567
- /**
1568
- * node is attached to document or detached
1569
- * @returns {boolean} - result
1570
- */
1571
- _isAttached() {
1572
- const root = this.#document.documentElement;
1573
- const posBit =
1574
- this.#node.compareDocumentPosition(root) & DOCUMENT_POSITION_CONTAINS;
1575
- return !!posBit;
1576
- };
1577
-
1578
1593
  /**
1579
1594
  * match AST and node
1580
1595
  * @param {object} ast - AST
@@ -1588,21 +1603,21 @@ class Matcher {
1588
1603
  case ID_SELECTOR: {
1589
1604
  const res = matchIDSelector(ast, node);
1590
1605
  if (res) {
1591
- matched.push(node);
1606
+ matched.push(res);
1592
1607
  }
1593
1608
  break;
1594
1609
  }
1595
1610
  case CLASS_SELECTOR: {
1596
1611
  const res = matchClassSelector(ast, node);
1597
1612
  if (res) {
1598
- matched.push(node);
1613
+ matched.push(res);
1599
1614
  }
1600
1615
  break;
1601
1616
  }
1602
1617
  case ATTRIBUTE_SELECTOR: {
1603
1618
  const res = matchAttributeSelector(ast, node);
1604
1619
  if (res) {
1605
- matched.push(node);
1620
+ matched.push(res);
1606
1621
  }
1607
1622
  break;
1608
1623
  }
@@ -1621,7 +1636,7 @@ class Matcher {
1621
1636
  default: {
1622
1637
  const res = matchTypeSelector(ast, node);
1623
1638
  if (res) {
1624
- matched.push(node);
1639
+ matched.push(res);
1625
1640
  }
1626
1641
  }
1627
1642
  }
@@ -1705,7 +1720,7 @@ class Matcher {
1705
1720
  let res;
1706
1721
  try {
1707
1722
  let node;
1708
- if (this._isAttached()) {
1723
+ if (isAttached(this.#node)) {
1709
1724
  node = this.#document;
1710
1725
  } else {
1711
1726
  node = this.#node;
@@ -1821,6 +1836,7 @@ module.exports = {
1821
1836
  collectNthOfType,
1822
1837
  createSelectorForNode,
1823
1838
  groupASTLeaves,
1839
+ isAttached,
1824
1840
  isContentEditable,
1825
1841
  isNamespaceDeclared,
1826
1842
  matchAnPlusB,
package/types/index.d.ts CHANGED
@@ -1,16 +1,12 @@
1
1
  export function closest(selector: string, node: object, opt?: {
2
- globalObject?: object;
3
- jsdom?: boolean;
2
+ warn?: object;
4
3
  }): object | null;
5
4
  export function matches(selector: string, node: object, opt?: {
6
- globalObject?: object;
7
- jsdom?: boolean;
5
+ warn?: object;
8
6
  }): boolean;
9
7
  export function querySelector(selector: string, refPoint: object, opt?: {
10
- globalObject?: object;
11
- jsdom?: boolean;
8
+ warn?: object;
12
9
  }): object | null;
13
10
  export function querySelectorAll(selector: string, refPoint: object, opt?: {
14
- globalObject?: object;
15
- jsdom?: boolean;
11
+ warn?: object;
16
12
  }): Array<object | undefined>;
@@ -2,7 +2,6 @@ export class Matcher {
2
2
  constructor(selector: string, refPoint: object, opt?: {
3
3
  warn?: object;
4
4
  });
5
- _isAttached(): boolean;
6
5
  _match(ast: object, node: object): Array<object | undefined>;
7
6
  _getMatchedNodes(branch?: Array<object>, node?: object): Array<object | undefined>;
8
7
  _find(ast: object, node: object): Array<object | undefined>;
@@ -25,6 +24,7 @@ export function collectNthOfType(anb?: {
25
24
  }, node?: object): Array<object | undefined>;
26
25
  export function createSelectorForNode(node?: object): string | null;
27
26
  export function groupASTLeaves(branch?: Array<object>): Array<object>;
27
+ export function isAttached(node?: object): boolean;
28
28
  export function isContentEditable(node?: object): boolean;
29
29
  export function isNamespaceDeclared(ns?: string, node?: object): boolean;
30
30
  export function matchAnPlusB(nthName: string, ast?: object, node?: object): Array<object | undefined>;