DOM Selector
A CSS selector engine built for strict specification compliance.
Features
- Strict Specification Compliance: Strictly adheres to modern web standards. It accurately parses, evaluates, and extracts elements across complex combinations of pseudo-classes and HTML attributes. Features comprehensive support for CSS Selectors Level 4 (e.g.,
:is(),:not(),:where(),:has()) and Shadow DOM pseudo-classes (:host,:host-context). - Utility Functions: Provides utility methods alongside standard querying, such as
check()for AST evaluation andextractSubjects()for extracting subject keys from selectors. - jsdom's Default Engine: Adopted as the CSS selector engine for jsdom.
Install
npm i @asamuzakjp/dom-selector
Usage
import { DOMSelector } from '@asamuzakjp/dom-selector';
import { JSDOM } from 'jsdom';
const { window } = new JSDOM();
// Destructuring methods (all methods are bound to the instance)
const {
check, closest, extractSubjects, matches, querySelector, querySelectorAll, supports
} = new DOMSelector(window);
API
new DOMSelector(window, document?, opt?)
Creates an instance of the DOMSelector.
window{Window} The window object.document{Document}? The document object. Defaults to window.document.opt{object}? Options:opt.cacheSize{number}? Maximum number of items to store in the internal cache. Default is 2048.
matches(selector, node, opt?)
Equivalent to Element.matches().
selector{string} CSS selector.node{Element} Element node.opt{object}? Options:opt.noexcept{boolean}? Do not throw exceptions.opt.warn{boolean}? Console warn (e.g. unsupported pseudo-class).
- Returns {boolean}
trueif matched,falseotherwise.
closest(selector, node, opt?)
Equivalent to Element.closest().
selector{string} CSS selector.node{Element} Element node.opt{object}? Options:opt.noexcept{boolean}? Do not throw exceptions.opt.warn{boolean}? Console warn (e.g. unsupported pseudo-class).
- Returns {Element | null} The matched ancestor node or
null.
querySelector(selector, node, opt?)
Equivalent to Document.querySelector(), DocumentFragment.querySelector() and Element.querySelector().
selector{string} CSS selector.node{Document | DocumentFragment | Element} Node to find within.opt{object}? Options:opt.noexcept{boolean}? Do not throw exceptions.opt.warn{boolean}? Console warn (e.g. unsupported pseudo-class).
- Returns {Element | null} The matched node or
null.
querySelectorAll(selector, node, opt?)
Equivalent to Document.querySelectorAll(), DocumentFragment.querySelectorAll() and Element.querySelectorAll().
NOTE: Returns a standard Array, not a NodeList.
selector{string} CSS selector.node{Document | DocumentFragment | Element} Node to find within.opt{object}? Options:opt.noexcept{boolean}? Do not throw exceptions.opt.warn{boolean}? Console warn (e.g. unsupported pseudo-class).
- Returns {Array} Array of matched nodes.
check(selector, node, opt?)
Checks if an element matches a CSS selector and returns additional abstract syntax tree (AST) information. NOTE: Any pseudo-elements in the selector are excluded from the matching evaluation.
selector{string} CSS selector.node{Element} Element node.opt{object}? Options:opt.noexcept{boolean}? Do not throw exceptions.opt.warn{boolean}? Console warn (e.g. unsupported pseudo-class).
- Returns {object} An object containing the following properties:
match{boolean}trueif the element matches the selector,falseotherwise.pseudoElement{string | null} The pseudo-element extracted from the selector, if any.ast{object | null} The parsed AST object.
extractSubjects(selector, caseSensitive?)
Parses a selector and extracts the rightmost subject keys (Id, Class, Tag).
selector{string} CSS selector.caseSensitive{boolean}?trueif the tag key should be case sensitive. Defaults tofalse.- Returns {Array<{id: string|null, className: string|null, tag: string|null}>} An array of extracted keys.
supports(selector)
Checks if the given CSS selector is supported by this engine. See the table below for the full list of supported selectors.
selector{string} CSS selector.- Returns {boolean}
trueif the selector is supported,falseotherwise.
clear(clearAll?)
Clears the internal caches to free up memory.
clearAll{boolean}? Whether to clear all caches. If false, only cached matching results are cleared. Defaults tofalse.- Returns {void}
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 |
Removed from spec. | |
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 |
Partially supported | Matching with <select>, e.g. select:open, or <input type="color"> are not supported. |
E:popover-open |
Unsupported | |
E:enabledE:disabled |
✓ | |
E:read-writeE:read-only |
✓ | |
E:placeholder-shown |
✓ | |
E:default |
✓ | |
E:checked |
✓ | |
E:indeterminate |
✓ | |
E:blank |
Unsupported | |
E:validE:invalid |
✓ | |
E:in-rangeE:out-of-range |
✓ | |
E:requiredE:optional |
✓ | |
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: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)