Spaces:
Sleeping
Sleeping
| # DOM Selector | |
| [](https://github.com/asamuzaK/domSelector/actions/workflows/node.js.yml) | |
| [](https://github.com/asamuzaK/domSelector/actions/workflows/github-code-scanning/codeql) | |
| [](https://www.npmjs.com/package/@asamuzakjp/dom-selector) | |
| A CSS selector engine. | |
| ## Install | |
| ```console | |
| npm i @asamuzakjp/dom-selector | |
| ``` | |
| ## Usage | |
| ```javascript | |
| import { DOMSelector } from '@asamuzakjp/dom-selector'; | |
| import { JSDOM } from 'jsdom'; | |
| const { window } = new JSDOM(); | |
| const { | |
| closest, matches, querySelector, querySelectorAll | |
| } = new DOMSelector(window); | |
| ``` | |
| <!-- Generated by documentation.js. Update this documentation by updating the source code. --> | |
| ### matches(selector, node, opt) | |
| matches - equivalent to [Element.matches()][64] | |
| #### Parameters | |
| - `selector` **[string][59]** CSS selector | |
| - `node` **[object][60]** Element node | |
| - `opt` **[object][60]?** options | |
| - `opt.noexcept` **[boolean][61]?** no exception | |
| - `opt.warn` **[boolean][61]?** console warn e.g. unsupported pseudo-class | |
| Returns **[boolean][61]** `true` if matched, `false` otherwise | |
| ### closest(selector, node, opt) | |
| closest - equivalent to [Element.closest()][65] | |
| #### Parameters | |
| - `selector` **[string][59]** CSS selector | |
| - `node` **[object][60]** Element node | |
| - `opt` **[object][60]?** options | |
| - `opt.noexcept` **[boolean][61]?** no exception | |
| - `opt.warn` **[boolean][61]?** console warn e.g. unsupported pseudo-class | |
| Returns **[object][60]?** matched node | |
| ### querySelector(selector, node, opt) | |
| querySelector - equivalent to [Document.querySelector()][66], [DocumentFragment.querySelector()][67] and [Element.querySelector()][68] | |
| #### Parameters | |
| - `selector` **[string][59]** CSS selector | |
| - `node` **[object][60]** Document, DocumentFragment or Element node | |
| - `opt` **[object][60]?** options | |
| - `opt.noexcept` **[boolean][61]?** no exception | |
| - `opt.warn` **[boolean][61]?** console warn e.g. unsupported pseudo-class | |
| Returns **[object][60]?** matched node | |
| ### querySelectorAll(selector, node, opt) | |
| querySelectorAll - equivalent to [Document.querySelectorAll()][69], [DocumentFragment.querySelectorAll()][70] and [Element.querySelectorAll()][71] | |
| **NOTE**: returns Array, not NodeList | |
| #### Parameters | |
| - `selector` **[string][59]** CSS selector | |
| - `node` **[object][60]** Document, DocumentFragment or Element node | |
| - `opt` **[object][60]?** options | |
| - `opt.noexcept` **[boolean][61]?** no exception | |
| - `opt.warn` **[boolean][61]?** console warn e.g. unsupported pseudo-class | |
| Returns **[Array][62]<([object][60] \| [undefined][63])>** array of matched nodes | |
| ## Monkey patch jsdom | |
| ``` javascript | |
| import { DOMSelector } from '@asamuzakjp/dom-selector'; | |
| import { JSDOM } from 'jsdom'; | |
| const dom = new JSDOM('', { | |
| runScripts: 'dangerously', | |
| url: 'http://localhost/', | |
| beforeParse: window => { | |
| const domSelector = new DOMSelector(window); | |
| const matches = domSelector.matches.bind(domSelector); | |
| window.Element.prototype.matches = function (...args) { | |
| if (!args.length) { | |
| throw new window.TypeError('1 argument required, but only 0 present.'); | |
| } | |
| const [selector] = args; | |
| return matches(selector, this); | |
| }; | |
| const closest = domSelector.closest.bind(domSelector); | |
| window.Element.prototype.closest = function (...args) { | |
| if (!args.length) { | |
| throw new window.TypeError('1 argument required, but only 0 present.'); | |
| } | |
| const [selector] = args; | |
| return closest(selector, this); | |
| }; | |
| const querySelector = domSelector.querySelector.bind(domSelector); | |
| window.Document.prototype.querySelector = function (...args) { | |
| if (!args.length) { | |
| throw new window.TypeError('1 argument required, but only 0 present.'); | |
| } | |
| const [selector] = args; | |
| return querySelector(selector, this); | |
| }; | |
| window.DocumentFragment.prototype.querySelector = function (...args) { | |
| if (!args.length) { | |
| throw new window.TypeError('1 argument required, but only 0 present.'); | |
| } | |
| const [selector] = args; | |
| return querySelector(selector, this); | |
| }; | |
| window.Element.prototype.querySelector = function (...args) { | |
| if (!args.length) { | |
| throw new window.TypeError('1 argument required, but only 0 present.'); | |
| } | |
| const [selector] = args; | |
| return querySelector(selector, this); | |
| }; | |
| const querySelectorAll = domSelector.querySelectorAll.bind(domSelector); | |
| window.Document.prototype.querySelectorAll = function (...args) { | |
| if (!args.length) { | |
| throw new window.TypeError('1 argument required, but only 0 present.'); | |
| } | |
| const [selector] = args; | |
| return querySelectorAll(selector, this); | |
| }; | |
| window.DocumentFragment.prototype.querySelectorAll = function (...args) { | |
| if (!args.length) { | |
| throw new window.TypeError('1 argument required, but only 0 present.'); | |
| } | |
| const [selector] = args; | |
| return querySelectorAll(selector, this); | |
| }; | |
| window.Element.prototype.querySelectorAll = function (...args) { | |
| if (!args.length) { | |
| throw new window.TypeError('1 argument required, but only 0 present.'); | |
| } | |
| const [selector] = args; | |
| return querySelectorAll(selector, this); | |
| }; | |
| } | |
| }); | |
| ``` | |
| ## Supported CSS selectors | |
| |Pattern|Supported|Note| | |
| |:--------|:-------:|:--------| | |
| |\*|β| | | |
| |E|β| | | |
| |ns\|E|β| | | |
| |\*\|E|β| | | |
| |\|E|β| | | |
| |E F|β| | | |
| |E > F|β| | | |
| |E + F|β| | | |
| |E ~ F|β| | | |
| |F \|\| E|Unsupported| | | |
| |E.warning|β| | | |
| |E#myid|β| | | |
| |E\[foo\]|β| | | |
| |E\[foo="bar"\]|β| | | |
| |E\[foo="bar" i\]|β| | | |
| |E\[foo="bar" s\]|β| | | |
| |E\[foo~="bar"\]|β| | | |
| |E\[foo^="bar"\]|β| | | |
| |E\[foo$="bar"\]|β| | | |
| |E\[foo*="bar"\]|β| | | |
| |E\[foo\|="en"\]|β| | | |
| |E:is(s1, s2, β¦)|β| | | |
| |E:not(s1, s2, β¦)|β| | | |
| |E:where(s1, s2, β¦)|β| | | |
| |E:has(rs1, rs2, β¦)|β| | | |
| |E:defined|Partially supported|Matching with MathML is not yet supported.| | |
| |E:dir(ltr)|β| | | |
| |E:lang(en)|β| | | |
| |E:any‑link|β| | | |
| |E:link|β| | | |
| |E:visited|β|Returns `false` or `null` to prevent fingerprinting.| | |
| |E:local‑link|β| | | |
| |E:target|β| | | |
| |E:target‑within|β| | | |
| |E:scope|β| | | |
| |E:hover|β| | | |
| |E:active|β| | | |
| |E:focus|β| | | |
| |E:focus‑visible|β| | | |
| |E:focus‑within|β| | | |
| |E:current|Unsupported| | | |
| |E:current(s)|Unsupported| | | |
| |E:past|Unsupported| | | |
| |E:future|Unsupported| | | |
| |E:open<br>E:closed|Partially supported|Matching with <select>, e.g. `select:open`, is not supported.| | |
| |E:popover-open|Unsupported| | | |
| |E:enabled<br>E:disabled|β| | | |
| |E:read‑write<br>E:read‑only|β| | | |
| |E:placeholder‑shown|β| | | |
| |E:default|β| | | |
| |E:checked|β| | | |
| |E:indeterminate|β| | | |
| |E:blank|Unsupported| | | |
| |E:valid<br>E:invalid|β| | | |
| |E:in-range<br>E:out-of-range|β| | | |
| |E:required<br>E:optional|β| | | |
| |E:user‑valid<br>E:user‑invalid|Unsupported| | | |
| |E:root|β| | | |
| |E:empty|β| | | |
| |E:nth‑child(n [of S]?)|β| | | |
| |E:nth‑last‑child(n [of S]?)|β| | | |
| |E:first‑child|β| | | |
| |E:last‑child|β| | | |
| |E:only‑child|β| | | |
| |E:nth‑of‑type(n)|β| | | |
| |E:nth‑last‑of‑type(n)|β| | | |
| |E:first‑of‑type|β| | | |
| |E:last‑of‑type|β| | | |
| |E:only‑of‑type|β| | | |
| |E:nth‑col(n)|Unsupported| | | |
| |E:nth‑last‑col(n)|Unsupported| | | |
| |CE:state(v)|β|*1| | |
| |:host|β| | | |
| |:host(s)|β| | | |
| |:host(:state(v))|β|*1| | |
| |:host:has(rs1, rs2, ...)|β| | | |
| |:host(s):has(rs1, rs2, ...)|β| | | |
| |:host‑context(s)|β| | | |
| |:host‑context(s):has(rs1, rs2, ...)|β| | | |
| |&|β|Only supports outermost `&`, i.e. equivalent to `:scope`| | |
| *1: `ElementInternals.states`, i.e. `CustomStateSet`, is not implemented in jsdom, so you need to apply a patch in the custom element constructor. | |
| ``` javascript | |
| class LabeledCheckbox extends window.HTMLElement { | |
| #internals; | |
| constructor() { | |
| super(); | |
| this.#internals = this.attachInternals(); | |
| // patch CustomStateSet | |
| if (!this.#internals.states) { | |
| this.#internals.states = new Set(); | |
| } | |
| this.addEventListener('click', this._onClick.bind(this)); | |
| } | |
| get checked() { | |
| return this.#internals.states.has('checked'); | |
| } | |
| set checked(flag) { | |
| if (flag) { | |
| this.#internals.states.add('checked'); | |
| } else { | |
| this.#internals.states.delete('checked'); | |
| } | |
| } | |
| _onClick(event) { | |
| this.checked = !this.checked; | |
| } | |
| } | |
| ``` | |
| ## Performance | |
| See [benchmark](https://github.com/asamuzaK/domSelector/actions/workflows/benchmark.yml) for the latest results. | |
| ## Acknowledgments | |
| The following resources have been of great help in the development of the DOM Selector. | |
| - [CSSTree](https://github.com/csstree/csstree) | |
| - [selery](https://github.com/danburzo/selery) | |
| - [jsdom](https://github.com/jsdom/jsdom) | |
| - [nwsapi](https://github.com/dperini/nwsapi) | |
| --- | |
| Copyright (c) 2023 [asamuzaK (Kazz)](https://github.com/asamuzaK/) | |
| [1]: #matches | |
| [2]: #parameters | |
| [3]: #closest | |
| [4]: #parameters-1 | |
| [5]: #queryselector | |
| [6]: #parameters-2 | |
| [7]: #queryselectorall | |
| [8]: #parameters-3 | |
| [59]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String | |
| [60]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object | |
| [61]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean | |
| [62]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array | |
| [63]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/undefined | |
| [64]: https://developer.mozilla.org/docs/Web/API/Element/matches | |
| [65]: https://developer.mozilla.org/docs/Web/API/Element/closest | |
| [66]: https://developer.mozilla.org/docs/Web/API/Document/querySelector | |
| [67]: https://developer.mozilla.org/docs/Web/API/DocumentFragment/querySelector | |
| [68]: https://developer.mozilla.org/docs/Web/API/Element/querySelector | |
| [69]: https://developer.mozilla.org/docs/Web/API/Document/querySelectorAll | |
| [70]: https://developer.mozilla.org/docs/Web/API/DocumentFragment/querySelectorAll | |
| [71]: https://developer.mozilla.org/docs/Web/API/Element/querySelectorAll | |