DOM Selector
A CSS selector engine.
Install
npm i @asamuzakjp/dom-selector
Usage
import { DOMSelector } from '@asamuzakjp/dom-selector';
import { JSDOM } from 'jsdom';
const { window } = new JSDOM();
const {
closest, matches, querySelector, querySelectorAll
} = new DOMSelector(window);
matches(selector, node, opt)
matches - equivalent to Element.matches()
Parameters
Returns boolean true
if matched, false
otherwise
closest(selector, node, opt)
closest - equivalent to Element.closest()
Parameters
Returns object? matched node
querySelector(selector, node, opt)
querySelector - equivalent to Document.querySelector(), DocumentFragment.querySelector() and Element.querySelector()
Parameters
selector
string CSS selectornode
object Document, DocumentFragment or Element nodeopt
object? options
Returns object? matched node
querySelectorAll(selector, node, opt)
querySelectorAll - equivalent to Document.querySelectorAll(), DocumentFragment.querySelectorAll() and Element.querySelectorAll()
NOTE: returns Array, not NodeList
Parameters
selector
string CSS selectornode
object Document, DocumentFragment or Element nodeopt
object? options
Returns Array<(object | undefined)> array of matched nodes
Monkey patch jsdom
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 E:closed |
Partially supported | Matching with <select>, e.g. select:open , is not supported. |
||
E:popover-open | ✓ | |||
E:enabled E:disabled |
✓ | |||
E:read‑write E:read‑only |
✓ | |||
E:placeholder‑shown | ✓ | |||
E:default | ✓ | |||
E:checked | ✓ | |||
E:indeterminate | ✓ | |||
E:blank | Unsupported | |||
E:valid E:invalid |
✓ | |||
E:in-range E:out-of-range |
✓ | |||
E:required E:optional |
✓ | |||
E:user‑valid 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.
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 for the latest results.
Acknowledgments
The following resources have been of great help in the development of the DOM Selector.
Copyright (c) 2023 asamuzaK (Kazz)