@markuplint/selector 4.7.7 → 4.7.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,211 @@
1
+ # Selector Matching
2
+
3
+ Detailed documentation of the two selector matching systems in `@markuplint/selector`.
4
+
5
+ ## Overview
6
+
7
+ The package provides two independent matching systems:
8
+
9
+ 1. **CSS Selector Matching** -- Standard CSS selectors parsed via `postcss-selector-parser`
10
+ 2. **Regex Selector Matching** -- Pattern-based matching using regular expressions
11
+
12
+ Both systems return specificity information and can be used through the unified `matchSelector()` function.
13
+
14
+ ## CSS Selector Matching Flow
15
+
16
+ ### 1. Entry Point
17
+
18
+ ```
19
+ createSelector(selectorString, specs?)
20
+ → new Selector(selectorString, extendedPseudoClasses)
21
+ → Ruleset.parse(selectorString, extended)
22
+ → postcss-selector-parser processes the string
23
+ → Returns parser.Selector[] AST nodes
24
+ ```
25
+
26
+ ### 2. Parsing
27
+
28
+ `Ruleset.parse()` uses `postcss-selector-parser` to parse the selector string into an AST. Each comma-separated selector becomes a `parser.Selector` node. The `Ruleset` wraps each one in a `StructuredSelector`.
29
+
30
+ ### 3. StructuredSelector Chain Building
31
+
32
+ Each `StructuredSelector` walks the AST nodes and builds a chain of `SelectorTarget` objects linked by combinators:
33
+
34
+ ```
35
+ div > .class:not(.other) span
36
+ → SelectorTarget("div") → child combinator →
37
+ SelectorTarget(".class:not(.other)") → descendant combinator →
38
+ SelectorTarget("span")
39
+ ```
40
+
41
+ The chain is built left-to-right from the AST but matched right-to-left (starting from the current element).
42
+
43
+ ### 4. SelectorTarget Matching
44
+
45
+ Each `SelectorTarget` matches its compound selector components in this order:
46
+
47
+ 1. **Namespace check** -- If present, validates the element's namespace (only `svg` and `*` are supported)
48
+ 2. **ID selector** (`#id`) -- Matches `el.id`, specificity `[1, 0, 0]`
49
+ 3. **Tag selector** (`div`) -- Matches `el.localName` (case-insensitive for pure HTML elements), specificity `[0, 0, 1]`. Universal selector (`*`) is handled as a tag type but adds no specificity.
50
+ 4. **Class selector** (`.class`) -- Matches `el.classList`, specificity `[0, 1, 0]`
51
+ 5. **Attribute selector** (`[attr=val]`) -- Matches element attributes with operator support, specificity `[0, 1, 0]`
52
+ 6. **Pseudo-class** (`:not()`, `:has()`, etc.) -- Dispatched to specialized handlers
53
+
54
+ If any component fails to match, the entire `SelectorTarget` fails (early termination).
55
+
56
+ ### 5. Combinator Matching
57
+
58
+ When a `SelectorTarget` matches, the `StructuredSelector` follows the combinator to the next target:
59
+
60
+ | Combinator | Symbol | DOM Traversal |
61
+ | ------------------ | ----------- | ------------------------------------------------ |
62
+ | Descendant | ` ` (space) | Walk up through `parentElement` chain |
63
+ | Child | `>` | Check immediate `parentElement` |
64
+ | Next-sibling | `+` | Check `previousElementSibling` |
65
+ | Subsequent-sibling | `~` | Walk back through `previousElementSibling` chain |
66
+
67
+ ## Pseudo-Class Handling
68
+
69
+ ### Standard Pseudo-Classes
70
+
71
+ | Pseudo-Class | Behavior |
72
+ | ------------------ | -------------------------------------------------------------------------------------- |
73
+ | `:not(selector)` | Matches if the inner selector does NOT match. Specificity equals the inner selector's. |
74
+ | `:is(selector)` | Matches if ANY inner selector matches. Specificity equals the most specific match. |
75
+ | `:where(selector)` | Same as `:is()` but always contributes `[0, 0, 0]` specificity. |
76
+ | `:has(selector)` | Matches if a descendant (or sibling with `+`/`~` combinator) matches. |
77
+ | `:scope` | Matches the scope element (or root if no scope). Specificity `[0, 1, 0]`. |
78
+ | `:root` | Matches the `<html>` element. Specificity `[0, 1, 0]`. |
79
+
80
+ ### Custom: `:closest(selector)`
81
+
82
+ Walks up the ancestor chain and matches if any ancestor matches the inner selector. This is a markuplint extension not in the W3C specification.
83
+
84
+ ### Extended Pseudo-Classes
85
+
86
+ Extended pseudo-classes are dispatched through the `ExtendedPseudoClass` registry:
87
+
88
+ #### `:aria(syntax)`
89
+
90
+ | Syntax | Behavior |
91
+ | ------------- | ------------------------------------------------------ |
92
+ | `has name` | Matches if `getAccname(el)` returns a non-empty string |
93
+ | `has no name` | Matches if `getAccname(el)` returns an empty string |
94
+
95
+ Supports version syntax: `:aria(has name|1.2)` (version parameter is parsed but not yet used for filtering).
96
+
97
+ #### `:role(roleName)` / `:role(roleName|version)`
98
+
99
+ Matches if `getComputedRole(specs, el, version)` returns a role whose `name` equals the specified `roleName`. The version defaults to `ARIA_RECOMMENDED_VERSION`.
100
+
101
+ #### `:model(category)`
102
+
103
+ Matches if the element belongs to the specified HTML content model category. Uses `contentModelCategoryToTagNames()` to get the list of matching selectors for the category, then tests each against the element.
104
+
105
+ Special cases:
106
+
107
+ - `#custom` -- Matches custom elements (elements with `isCustomElement` property)
108
+ - `#text` -- Always returns unmatched (text nodes are not elements)
109
+
110
+ ## Regex Selector Matching Flow
111
+
112
+ ### 1. Entry Point
113
+
114
+ ```
115
+ matchSelector(el, regexSelector)
116
+ → regexSelect(el, regexSelector)
117
+ → Builds SelectorTarget chain from combination links
118
+ → Matches from the edge (deepest combination) back to root
119
+ ```
120
+
121
+ ### 2. SelectorTarget Chain Building
122
+
123
+ The `RegexSelector` type supports chained combinations:
124
+
125
+ ```typescript
126
+ {
127
+ nodeName: "/^div$/",
128
+ combination: {
129
+ combinator: ">",
130
+ nodeName: "/^span$/",
131
+ combination: {
132
+ combinator: "+",
133
+ attrName: "/^data-/"
134
+ }
135
+ }
136
+ }
137
+ ```
138
+
139
+ This builds a chain: `SelectorTarget(div) → > → SelectorTarget(span) → + → SelectorTarget([data-*])`.
140
+
141
+ ### 3. Pattern Matching
142
+
143
+ `regexSelectorMatches(pattern, value, ignoreCase)` handles pattern matching:
144
+
145
+ - **Plain string**: Wrapped as `^pattern$` (exact match)
146
+ - **Regex literal** (`/pattern/flags`): Used as-is with the specified flags
147
+ - **Case sensitivity**: HTML elements use case-insensitive matching (`ignoreCase = true` when `isPureHTMLElement()`)
148
+
149
+ ### 4. Regex Combinators
150
+
151
+ Standard CSS combinators are supported, plus two extra:
152
+
153
+ | Combinator | Symbol | DOM Traversal |
154
+ | -------------------- | ----------- | ------------------------------------------------ |
155
+ | Descendant | `' '` | Walk up `parentElement` chain |
156
+ | Child | `'>'` | Check immediate `parentElement` |
157
+ | Next-sibling | `'+'` | Check `previousElementSibling` |
158
+ | Subsequent-sibling | `'~'` | Walk back through `previousElementSibling` chain |
159
+ | Prev-sibling | `':has(+)'` | Check `nextElementSibling` |
160
+ | Subsequent (forward) | `':has(~)'` | Walk forward through `nextElementSibling` chain |
161
+
162
+ ### 5. Data Capture
163
+
164
+ Matched regex capture groups are collected into a `data` object:
165
+
166
+ ```typescript
167
+ // Pattern: "/^(?<prefix>[a-z]+)-(?<suffix>[a-z]+)$/"
168
+ // Value: "data-value"
169
+ // Result: { $0: "data-value", $1: "data", $2: "value", prefix: "data", suffix: "value" }
170
+ ```
171
+
172
+ The `$0` capture from `nodeName` matching is deleted (it's the full match, redundant with the element name). Data from all targets in the chain is merged.
173
+
174
+ ### 6. Specificity Calculation
175
+
176
+ Regex selector specificity is calculated per target:
177
+
178
+ - `nodeName` match: `[0, 0, 1]` (type specificity)
179
+ - Each matched attribute: `[0, 1, 0]` (class-level specificity)
180
+ - Specificity from combined targets is summed
181
+
182
+ ## Caching
183
+
184
+ `createSelector()` maintains a `Map<string, Selector>` cache. Subsequent calls with the same selector string return the same `Selector` instance, avoiding repeated parsing by `postcss-selector-parser`.
185
+
186
+ ## Supported vs Unsupported Selectors
187
+
188
+ ### Supported
189
+
190
+ - Universal (`*`), type (`div`), ID (`#id`), class (`.class`)
191
+ - All attribute selector operators (`=`, `~=`, `|=`, `*=`, `^=`, `$=`, case-insensitive `i` flag)
192
+ - Combinators: descendant (` `), child (`>`), next-sibling (`+`), subsequent-sibling (`~`)
193
+ - Multiple selectors (`,`)
194
+ - `:not()`, `:is()`, `:where()`, `:has()`, `:scope`, `:root`
195
+ - `:closest()` (markuplint extension)
196
+ - Extended: `:aria()`, `:role()`, `:model()`
197
+ - Namespace selectors (`svg|text`, `*|div`). Note: only `svg` and `*` namespaces are supported; other namespaces (e.g., `html`) throw `InvalidSelectorError`.
198
+
199
+ ### Unsupported (throws error)
200
+
201
+ Structural pseudo-classes: `:empty`, `:nth-child()`, `:nth-last-child()`, `:first-child`, `:last-child`, `:only-child`, `:nth-of-type()`, `:nth-last-of-type()`, `:first-of-type`, `:last-of-type`, `:only-of-type`, `:nth-col()`, `:nth-last-col()`
202
+
203
+ Input pseudo-classes: `:enable`, `:disable`, `:read-write`, `:read-only`, `:placeholder-shown`, `:default`, `:checked`, `:indeterminate`, `:valid`, `:invalid`, `:in-range`, `:out-of-range`, `:required`, `:optional`, `:blank`, `:user-invalid`
204
+
205
+ ### Ignored (throws error)
206
+
207
+ User interaction / dynamic pseudo-classes: `:dir()`, `:lang()`, `:any-link`, `:link`, `:visited`, `:local-link`, `:target`, `:target-within`, `:current`, `:past`, `:future`, `:active`, `:hover`, `:focus`, `:focus-within`, `:focus-visible`
208
+
209
+ Pseudo-elements: `::before`, `::after`
210
+
211
+ Column combinator: `||`
@@ -1,2 +1,10 @@
1
1
  import type { Specificity } from './types.js';
2
+ /**
3
+ * Compares two CSS specificity tuples using the standard comparison algorithm.
4
+ * Compares from left (ID) to right (type) component.
5
+ *
6
+ * @param a - The first specificity tuple `[id, class, type]`
7
+ * @param b - The second specificity tuple `[id, class, type]`
8
+ * @returns `-1` if `a` is less specific, `1` if `a` is more specific, `0` if equal
9
+ */
2
10
  export declare function compareSpecificity(a: Specificity, b: Specificity): 1 | -1 | 0;
@@ -1,3 +1,11 @@
1
+ /**
2
+ * Compares two CSS specificity tuples using the standard comparison algorithm.
3
+ * Compares from left (ID) to right (type) component.
4
+ *
5
+ * @param a - The first specificity tuple `[id, class, type]`
6
+ * @param b - The second specificity tuple `[id, class, type]`
7
+ * @returns `-1` if `a` is less specific, `1` if `a` is more specific, `0` if equal
8
+ */
1
9
  export function compareSpecificity(a, b) {
2
10
  if (a[0] < b[0]) {
3
11
  return -1;
@@ -1,3 +1,16 @@
1
1
  import type { MLMLSpec } from '@markuplint/ml-spec';
2
2
  import { Selector } from './selector.js';
3
+ /**
4
+ * Creates a cached {@link Selector} instance for the given CSS selector string.
5
+ *
6
+ * Results are cached by selector string so subsequent calls with the same
7
+ * selector return the same instance.
8
+ *
9
+ * When `specs` is provided, markuplint's extended pseudo-classes
10
+ * (`:model()`, `:aria()`, `:role()`) are available.
11
+ *
12
+ * @param selector - The CSS selector string to parse
13
+ * @param specs - Optional HTML/ARIA specification data for extended pseudo-classes
14
+ * @returns A reusable Selector instance
15
+ */
3
16
  export declare function createSelector(selector: string, specs?: MLMLSpec): Selector;
@@ -3,6 +3,19 @@ import { ariaRolePseudoClass } from './extended-selector/aria-role-pseudo-class.
3
3
  import { contentModelPseudoClass } from './extended-selector/content-model-pseudo-class.js';
4
4
  import { Selector } from './selector.js';
5
5
  const caches = new Map();
6
+ /**
7
+ * Creates a cached {@link Selector} instance for the given CSS selector string.
8
+ *
9
+ * Results are cached by selector string so subsequent calls with the same
10
+ * selector return the same instance.
11
+ *
12
+ * When `specs` is provided, markuplint's extended pseudo-classes
13
+ * (`:model()`, `:aria()`, `:role()`) are available.
14
+ *
15
+ * @param selector - The CSS selector string to parse
16
+ * @param specs - Optional HTML/ARIA specification data for extended pseudo-classes
17
+ * @returns A reusable Selector instance
18
+ */
6
19
  export function createSelector(selector, specs) {
7
20
  let instance = caches.get(selector);
8
21
  if (instance) {
package/lib/debug.d.ts CHANGED
@@ -1,3 +1,8 @@
1
1
  import debug from 'debug';
2
+ /** Debug logger instance for the `selector` namespace. */
2
3
  export declare const log: debug.Debugger;
4
+ /**
5
+ * Enables debug logging for the selector and markuplint-cli namespaces.
6
+ * Once enabled, logs are output via the `debug` package.
7
+ */
3
8
  export declare function enableDebug(): void;
package/lib/debug.js CHANGED
@@ -1,6 +1,11 @@
1
1
  import debug from 'debug';
2
2
  const CLI_NS = 'markuplint-cli';
3
+ /** Debug logger instance for the `selector` namespace. */
3
4
  export const log = debug('selector');
5
+ /**
6
+ * Enables debug logging for the selector and markuplint-cli namespaces.
7
+ * Once enabled, logs are output via the `debug` package.
8
+ */
4
9
  export function enableDebug() {
5
10
  if (!log.enabled) {
6
11
  debug.enable(`${log.namespace}*`);
@@ -1,5 +1,11 @@
1
1
  import type { SelectorResult } from '../types.js';
2
2
  /**
3
- * Version Syntax is not support yet.
3
+ * Creates the `:aria()` extended pseudo-class handler.
4
+ *
5
+ * Matches elements by accessible name presence using `getAccname()`.
6
+ * Supports `has name` and `has no name` syntax.
7
+ * Version syntax is parsed but not yet used for filtering.
8
+ *
9
+ * @returns An extended pseudo-class handler function
4
10
  */
5
11
  export declare function ariaPseudoClass(): (content: string) => (el: Element) => SelectorResult;
@@ -1,6 +1,12 @@
1
1
  import { validateAriaVersion, ARIA_RECOMMENDED_VERSION, getAccname } from '@markuplint/ml-spec';
2
2
  /**
3
- * Version Syntax is not support yet.
3
+ * Creates the `:aria()` extended pseudo-class handler.
4
+ *
5
+ * Matches elements by accessible name presence using `getAccname()`.
6
+ * Supports `has name` and `has no name` syntax.
7
+ * Version syntax is parsed but not yet used for filtering.
8
+ *
9
+ * @returns An extended pseudo-class handler function
4
10
  */
5
11
  export function ariaPseudoClass() {
6
12
  return (content) => (
@@ -1,3 +1,12 @@
1
1
  import type { SelectorResult } from '../types.js';
2
2
  import type { MLMLSpec } from '@markuplint/ml-spec';
3
+ /**
4
+ * Creates the `:role()` extended pseudo-class handler.
5
+ *
6
+ * Matches elements whose computed ARIA role equals the specified role name.
7
+ * Supports version syntax: `:role(roleName|version)`.
8
+ *
9
+ * @param specs - The HTML/ARIA specification data used for role computation
10
+ * @returns An extended pseudo-class handler function
11
+ */
3
12
  export declare function ariaRolePseudoClass(specs: MLMLSpec): (content: string) => (el: Element) => SelectorResult;
@@ -1,4 +1,13 @@
1
1
  import { validateAriaVersion, ARIA_RECOMMENDED_VERSION, getComputedRole } from '@markuplint/ml-spec';
2
+ /**
3
+ * Creates the `:role()` extended pseudo-class handler.
4
+ *
5
+ * Matches elements whose computed ARIA role equals the specified role name.
6
+ * Supports version syntax: `:role(roleName|version)`.
7
+ *
8
+ * @param specs - The HTML/ARIA specification data used for role computation
9
+ * @returns An extended pseudo-class handler function
10
+ */
2
11
  export function ariaRolePseudoClass(specs) {
3
12
  return (content) => (
4
13
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
@@ -1,3 +1,12 @@
1
1
  import type { SelectorResult } from '../types.js';
2
2
  import type { MLMLSpec } from '@markuplint/ml-spec';
3
+ /**
4
+ * Creates the `:model()` extended pseudo-class handler.
5
+ *
6
+ * Matches elements that belong to the specified HTML content model category
7
+ * (e.g., `interactive`, `phrasing`, `flow`).
8
+ *
9
+ * @param specs - The HTML/ARIA specification data containing content model definitions
10
+ * @returns An extended pseudo-class handler function
11
+ */
3
12
  export declare function contentModelPseudoClass(specs: MLMLSpec): (category: string) => (el: Element) => SelectorResult;
@@ -1,5 +1,14 @@
1
1
  import { contentModelCategoryToTagNames } from '@markuplint/ml-spec';
2
2
  import { createSelector } from '../create-selector.js';
3
+ /**
4
+ * Creates the `:model()` extended pseudo-class handler.
5
+ *
6
+ * Matches elements that belong to the specified HTML content model category
7
+ * (e.g., `interactive`, `phrasing`, `flow`).
8
+ *
9
+ * @param specs - The HTML/ARIA specification data containing content model definitions
10
+ * @returns An extended pseudo-class handler function
11
+ */
3
12
  export function contentModelPseudoClass(specs) {
4
13
  return (category) => (
5
14
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
@@ -1,5 +1,13 @@
1
+ /**
2
+ * Error thrown when a CSS selector string cannot be parsed.
3
+ */
1
4
  export declare class InvalidSelectorError extends Error {
2
5
  name: string;
6
+ /** The invalid selector string that caused this error */
3
7
  selector: string;
8
+ /**
9
+ * @param selector - The invalid selector string
10
+ * @param message - An optional custom error message
11
+ */
4
12
  constructor(selector: string, message?: string);
5
13
  }
@@ -1,4 +1,11 @@
1
+ /**
2
+ * Error thrown when a CSS selector string cannot be parsed.
3
+ */
1
4
  export class InvalidSelectorError extends Error {
5
+ /**
6
+ * @param selector - The invalid selector string
7
+ * @param message - An optional custom error message
8
+ */
2
9
  constructor(selector, message) {
3
10
  super(message ?? `Invalid selector: "${selector}"`);
4
11
  this.name = 'InvalidSelectorError';
package/lib/is.d.ts CHANGED
@@ -1,4 +1,17 @@
1
+ /**
2
+ * Checks whether the given node is an Element node.
3
+ *
4
+ * @param node - The DOM node to check
5
+ * @returns `true` if the node is an Element
6
+ */
1
7
  export declare function isElement(node: Node): node is Element;
8
+ /**
9
+ * Checks whether the given node is a non-DocumentType child node
10
+ * (i.e., has `previousElementSibling` and `nextElementSibling` properties).
11
+ *
12
+ * @param node - The DOM node to check
13
+ * @returns `true` if the node is an Element or CharacterData
14
+ */
2
15
  export declare function isNonDocumentTypeChildNode(node: Node): node is Element | CharacterData;
3
16
  /**
4
17
  * Checks if the given element is a pure HTML element.
package/lib/is.js CHANGED
@@ -1,8 +1,21 @@
1
+ /**
2
+ * Checks whether the given node is an Element node.
3
+ *
4
+ * @param node - The DOM node to check
5
+ * @returns `true` if the node is an Element
6
+ */
1
7
  export function isElement(
2
8
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
3
9
  node) {
4
10
  return node.nodeType === node.ELEMENT_NODE;
5
11
  }
12
+ /**
13
+ * Checks whether the given node is a non-DocumentType child node
14
+ * (i.e., has `previousElementSibling` and `nextElementSibling` properties).
15
+ *
16
+ * @param node - The DOM node to check
17
+ * @returns `true` if the node is an Element or CharacterData
18
+ */
6
19
  export function isNonDocumentTypeChildNode(
7
20
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
8
21
  node) {
@@ -1,5 +1,9 @@
1
1
  import type { Specificity, RegexSelector } from './types.js';
2
2
  import type { MLMLSpec } from '@markuplint/ml-spec';
3
+ /**
4
+ * The result of matching a selector against a node.
5
+ * Either a successful match with specificity and captured data, or an unsuccessful match.
6
+ */
3
7
  export type SelectorMatches = SelectorMatched | SelectorUnmatched;
4
8
  type SelectorMatched = {
5
9
  readonly matched: true;
@@ -10,5 +14,17 @@ type SelectorMatched = {
10
14
  type SelectorUnmatched = {
11
15
  readonly matched: false;
12
16
  };
17
+ /**
18
+ * Matches a CSS selector or regex selector against a DOM node.
19
+ *
20
+ * Supports both standard CSS selectors (as strings) and markuplint's
21
+ * {@link RegexSelector} for pattern-based matching with captured groups.
22
+ *
23
+ * @param el - The DOM node to test
24
+ * @param selector - A CSS selector string, a regex selector object, or `undefined`
25
+ * @param scope - The scope element for `:scope` pseudo-class resolution
26
+ * @param specs - The HTML/ARIA specification data for extended pseudo-classes
27
+ * @returns A match result with specificity and captured data, or `{ matched: false }`
28
+ */
13
29
  export declare function matchSelector(el: Node, selector: string | RegexSelector | undefined, scope?: ParentNode | null, specs?: MLMLSpec): SelectorMatches;
14
30
  export {};
@@ -13,6 +13,18 @@ var _SelectorTarget_combinedFrom, _SelectorTarget_selector;
13
13
  import { isElement, isNonDocumentTypeChildNode, isPureHTMLElement } from './is.js';
14
14
  import { regexSelectorMatches } from './regex-selector-matches.js';
15
15
  import { createSelector } from './create-selector.js';
16
+ /**
17
+ * Matches a CSS selector or regex selector against a DOM node.
18
+ *
19
+ * Supports both standard CSS selectors (as strings) and markuplint's
20
+ * {@link RegexSelector} for pattern-based matching with captured groups.
21
+ *
22
+ * @param el - The DOM node to test
23
+ * @param selector - A CSS selector string, a regex selector object, or `undefined`
24
+ * @param scope - The scope element for `:scope` pseudo-class resolution
25
+ * @param specs - The HTML/ARIA specification data for extended pseudo-classes
26
+ * @returns A match result with specificity and captured data, or `{ matched: false }`
27
+ */
16
28
  export function matchSelector(
17
29
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
18
30
  el, selector,
@@ -1,3 +1,15 @@
1
+ /**
2
+ * Tests a raw string value against a regex selector pattern and returns
3
+ * the captured groups if matched.
4
+ *
5
+ * Plain strings are treated as exact-match patterns (`^pattern$`).
6
+ * Regex literals (`/pattern/flags`) are used as-is.
7
+ *
8
+ * @param reg - The regex pattern string, or `undefined` to skip matching
9
+ * @param raw - The raw string value to test against the pattern
10
+ * @param ignoreCase - Whether to perform case-insensitive matching
11
+ * @returns An object of captured groups (`$0`, `$1`, ... and named groups), or `null` if unmatched or `reg` is `undefined`
12
+ */
1
13
  export declare function regexSelectorMatches(reg: string | undefined, raw: string, ignoreCase: boolean): {
2
14
  [x: string]: string;
3
15
  } | null;
@@ -1,3 +1,15 @@
1
+ /**
2
+ * Tests a raw string value against a regex selector pattern and returns
3
+ * the captured groups if matched.
4
+ *
5
+ * Plain strings are treated as exact-match patterns (`^pattern$`).
6
+ * Regex literals (`/pattern/flags`) are used as-is.
7
+ *
8
+ * @param reg - The regex pattern string, or `undefined` to skip matching
9
+ * @param raw - The raw string value to test against the pattern
10
+ * @param ignoreCase - Whether to perform case-insensitive matching
11
+ * @returns An object of captured groups (`$0`, `$1`, ... and named groups), or `null` if unmatched or `reg` is `undefined`
12
+ */
1
13
  export function regexSelectorMatches(reg, raw, ignoreCase) {
2
14
  if (!reg) {
3
15
  return null;
package/lib/selector.d.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  import type { SelectorResult, Specificity } from './types.js';
2
2
  type ExtendedPseudoClass = Readonly<Record<string, (content: string) => (el: Element) => SelectorResult>>;
3
+ /**
4
+ * CSS selector matcher that parses a selector string and matches it against DOM nodes.
5
+ *
6
+ * Use {@link createSelector} to create cached instances with extended pseudo-class support.
7
+ */
3
8
  export declare class Selector {
4
9
  #private;
5
10
  constructor(selector: string, extended?: ExtendedPseudoClass);
package/lib/selector.js CHANGED
@@ -18,6 +18,11 @@ import { InvalidSelectorError } from './invalid-selector-error.js';
18
18
  import { isElement, isNonDocumentTypeChildNode, isPureHTMLElement } from './is.js';
19
19
  const selLog = coreLog.extend('selector');
20
20
  const resLog = coreLog.extend('result');
21
+ /**
22
+ * CSS selector matcher that parses a selector string and matches it against DOM nodes.
23
+ *
24
+ * Use {@link createSelector} to create cached instances with extended pseudo-class support.
25
+ */
21
26
  export class Selector {
22
27
  constructor(selector, extended = {}) {
23
28
  _Selector_ruleset.set(this, void 0);
package/lib/types.d.ts CHANGED
@@ -1,24 +1,66 @@
1
+ /**
2
+ * A CSS specificity tuple: `[id, class, type]`.
3
+ * Each component counts selectors of the corresponding category.
4
+ */
1
5
  export type Specificity = readonly [number, number, number];
6
+ /**
7
+ * The result of evaluating a parsed selector against a node.
8
+ */
2
9
  export type SelectorResult = SelectorMatchedResult | SelectorUnmatchedResult;
10
+ /**
11
+ * A successful selector match result, including the specificity,
12
+ * the matched nodes, and any `:has()` sub-match results.
13
+ */
3
14
  export type SelectorMatchedResult = {
15
+ /** The computed specificity of the matched selector */
4
16
  readonly specificity: Specificity;
5
17
  readonly matched: true;
18
+ /** The DOM nodes that were matched */
6
19
  readonly nodes: readonly (Element | Text)[];
20
+ /** Results from `:has()` pseudo-class sub-matches */
7
21
  readonly has: readonly SelectorMatchedResult[];
8
22
  };
23
+ /**
24
+ * An unsuccessful selector match result.
25
+ */
9
26
  export type SelectorUnmatchedResult = {
27
+ /** The computed specificity of the unmatched selector */
10
28
  readonly specificity: Specificity;
11
29
  readonly matched: false;
30
+ /** Results from `:not()` pseudo-class sub-matches that did match */
12
31
  readonly not?: readonly SelectorMatchedResult[];
13
32
  };
33
+ /**
34
+ * A regex-based selector that matches elements by node name and/or attribute
35
+ * patterns using regular expressions. Supports combinators for chained matching.
36
+ */
14
37
  export type RegexSelector = RegexSelectorWithoutCombination & {
38
+ /** An optional chained selector with a combinator */
15
39
  readonly combination?: {
16
40
  readonly combinator: RegexSelectorCombinator;
17
41
  } & RegexSelector;
18
42
  };
43
+ /**
44
+ * CSS combinator types supported by regex selectors.
45
+ *
46
+ * - `' '` – Descendant combinator
47
+ * - `'>'` – Child combinator
48
+ * - `'+'` – Next-sibling combinator
49
+ * - `'~'` – Subsequent-sibling combinator
50
+ * - `':has(+)'`– Previous-sibling combinator (via `:has()`)
51
+ * - `':has(~)'`– Previous subsequent-sibling combinator (via `:has()`)
52
+ */
19
53
  export type RegexSelectorCombinator = ' ' | '>' | '+' | '~' | ':has(+)' | ':has(~)';
54
+ /**
55
+ * The non-combinatorial part of a regex selector.
56
+ * Matches against the element's node name, attribute name, and/or attribute value
57
+ * using regular expression patterns.
58
+ */
20
59
  export type RegexSelectorWithoutCombination = {
60
+ /** Regex pattern to match against the element's local name */
21
61
  readonly nodeName?: string;
62
+ /** Regex pattern to match against attribute names */
22
63
  readonly attrName?: string;
64
+ /** Regex pattern to match against attribute values */
23
65
  readonly attrValue?: string;
24
66
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markuplint/selector",
3
- "version": "4.7.7",
3
+ "version": "4.7.8",
4
4
  "description": "Extended W3C Selectors matcher",
5
5
  "repository": "git@github.com:markuplint/markuplint.git",
6
6
  "author": "Yusuke Hirao <yusukehirao@me.com>",
@@ -24,15 +24,15 @@
24
24
  "clean": "tsc --build --clean tsconfig.build.json"
25
25
  },
26
26
  "dependencies": {
27
- "@markuplint/ml-spec": "4.10.1",
27
+ "@markuplint/ml-spec": "4.10.2",
28
28
  "@types/debug": "4.1.12",
29
29
  "debug": "4.4.3",
30
- "postcss-selector-parser": "7.1.0",
30
+ "postcss-selector-parser": "7.1.1",
31
31
  "type-fest": "4.41.0"
32
32
  },
33
33
  "devDependencies": {
34
- "@types/jsdom": "21.1.7",
34
+ "@types/jsdom": "27.0.0",
35
35
  "jsdom": "26.1.0"
36
36
  },
37
- "gitHead": "6213ea30269ef404f030e67bbcc7fc7443ec1060"
37
+ "gitHead": "193ee7c1262bbed95424e38efdf1a8e56ff049f4"
38
38
  }