@asamuzakjp/dom-selector v6.5.0
DOM Selector
A CSS selector engine.
Install
npm i @asamuzakjp/dom-selectorUsage
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
selectorstring CSS selectornodeobject Document, DocumentFragment or Element nodeoptobject? 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
selectorstring CSS selectornodeobject Document, DocumentFragment or Element nodeoptobject? 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 |
|---|---|---|
| * | ✓ | |
| ns|E | ✓ | |
| *|E | ✓ | |
| |E | ✓ | |
| E | ✓ | |
| E:not(s1, s2, …) | ✓ | |
| E:is(s1, s2, …) | ✓ | |
| E:where(s1, s2, …) | ✓ | |
| E:has(rs1, rs2, …) | ✓ | |
| 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: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:current | Unsupported | |
| E:current(s) | Unsupported | |
| E:past | Unsupported | |
| E:future | Unsupported | |
| E:active | ✓ | |
| E:hover | ✓ | |
| E:focus | ✓ | |
| E:focus‑within | ✓ | |
| E:focus‑visible | ✓ | |
| E:openE:closed | Partially supported | Matching with <select>, e.g. select:open, is not supported. |
| E:enabledE:disabled | ✓ | |
| E:read‑writeE:read‑only | ✓ | |
| E:placeholder‑shown | ✓ | |
| E:default | ✓ | |
| E:checked | ✓ | |
| E:indeterminate | ✓ | |
| E:validE:invalid | ✓ | |
| E:requiredE:optional | ✓ | |
| E:blank | Unsupported | |
| E:user‑validE: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 F | ✓ | |
| E > F | ✓ | |
| E + F | ✓ | |
| E ~ F | ✓ | |
| F || E | Unsupported | |
| E:nth‑col(n) | Unsupported | |
| E:nth‑last‑col(n) | Unsupported | |
| E:popover-open | ✓ | |
| E:state(v) | ✓ | *1 |
| :host | ✓ | |
| :host(s) | ✓ | |
| :host‑context(s) | ✓ | |
| :host(:state(v)) | ✓ | *1 |
| :host:has(rs1, rs2, ...) | ✓ | |
| :host(s):has(rs1, rs2, ...) | ✓ | |
| :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)
10 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
10 months ago
11 months ago
11 months ago
10 months ago
11 months ago
10 months ago
10 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
11 months ago
1 year ago
11 months ago
11 months ago
11 months ago
11 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago