@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)
6 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
10 months ago
12 months ago
12 months ago
12 months ago
8 months ago
9 months ago
7 months ago
8 months ago
7 months ago
7 months ago
10 months ago
12 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
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
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
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
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
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