-
-
Notifications
You must be signed in to change notification settings - Fork 67
/
index.ts
202 lines (183 loc) · 6.49 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
import * as DomUtils from "domutils";
import * as boolbase from "boolbase";
import type {
AnyNode as DomHandlerNode,
Element as DomHandlerElement,
} from "domhandler";
import {
compile as compileRaw,
compileUnsafe,
compileToken,
} from "./compile.js";
import type {
CompiledQuery,
Options,
InternalOptions,
Query,
Adapter,
Predicate,
} from "./types.js";
import { getNextSiblings } from "./pseudo-selectors/subselects.js";
export type { Options };
const defaultEquals = <Node>(a: Node, b: Node) => a === b;
const defaultOptions: InternalOptions<DomHandlerNode, DomHandlerElement> = {
adapter: DomUtils,
equals: defaultEquals,
};
function convertOptionFormats<Node, ElementNode extends Node>(
options?: Options<Node, ElementNode>
): InternalOptions<Node, ElementNode> {
/*
* We force one format of options to the other one.
*/
// @ts-expect-error Default options may have incompatible `Node` / `ElementNode`.
const opts: Options<Node, ElementNode> = options ?? defaultOptions;
// @ts-expect-error Same as above.
opts.adapter ??= DomUtils;
// @ts-expect-error `equals` does not exist on `Options`
opts.equals ??= opts.adapter?.equals ?? defaultEquals;
return opts as InternalOptions<Node, ElementNode>;
}
function wrapCompile<Selector, Node, ElementNode extends Node, R extends Node>(
func: (
selector: Selector,
options: InternalOptions<Node, ElementNode>,
context?: Node[] | Node
) => CompiledQuery<R>
) {
return function addAdapter(
selector: Selector,
options?: Options<Node, ElementNode>,
context?: Node[] | Node
): CompiledQuery<R> {
const opts = convertOptionFormats(options);
return func(selector, opts, context);
};
}
/**
* Compiles the query, returns a function.
*/
export const compile = wrapCompile(compileRaw);
export const _compileUnsafe = wrapCompile(compileUnsafe);
export const _compileToken = wrapCompile(compileToken);
function getSelectorFunc<Node, ElementNode extends Node, T>(
searchFunc: (
query: Predicate<ElementNode>,
elems: Array<Node>,
options: InternalOptions<Node, ElementNode>
) => T
) {
return function select(
query: Query<ElementNode>,
elements: Node[] | Node,
options?: Options<Node, ElementNode>
): T {
const opts = convertOptionFormats(options);
if (typeof query !== "function") {
query = compileUnsafe<Node, ElementNode>(query, opts, elements);
}
const filteredElements = prepareContext(
elements,
opts.adapter,
query.shouldTestNextSiblings
);
return searchFunc(query, filteredElements, opts);
};
}
export function prepareContext<Node, ElementNode extends Node>(
elems: Node | Node[],
adapter: Adapter<Node, ElementNode>,
shouldTestNextSiblings = false
): Node[] {
/*
* Add siblings if the query requires them.
* See https://github.com/fb55/css-select/pull/43#issuecomment-225414692
*/
if (shouldTestNextSiblings) {
elems = appendNextSiblings(elems, adapter);
}
return Array.isArray(elems)
? adapter.removeSubsets(elems)
: adapter.getChildren(elems);
}
function appendNextSiblings<Node, ElementNode extends Node>(
elem: Node | Node[],
adapter: Adapter<Node, ElementNode>
): Node[] {
// Order matters because jQuery seems to check the children before the siblings
const elems = Array.isArray(elem) ? elem.slice(0) : [elem];
const elemsLength = elems.length;
for (let i = 0; i < elemsLength; i++) {
const nextSiblings = getNextSiblings(elems[i], adapter);
elems.push(...nextSiblings);
}
return elems;
}
/**
* @template Node The generic Node type for the DOM adapter being used.
* @template ElementNode The Node type for elements for the DOM adapter being used.
* @param elems Elements to query. If it is an element, its children will be queried..
* @param query can be either a CSS selector string or a compiled query function.
* @param [options] options for querying the document.
* @see compile for supported selector queries.
* @returns All matching elements.
*
*/
export const selectAll = getSelectorFunc(
<Node, ElementNode extends Node>(
query: Predicate<ElementNode>,
elems: Node[] | null,
options: InternalOptions<Node, ElementNode>
): ElementNode[] =>
query === boolbase.falseFunc || !elems || elems.length === 0
? []
: options.adapter.findAll(query, elems)
);
/**
* @template Node The generic Node type for the DOM adapter being used.
* @template ElementNode The Node type for elements for the DOM adapter being used.
* @param elems Elements to query. If it is an element, its children will be queried..
* @param query can be either a CSS selector string or a compiled query function.
* @param [options] options for querying the document.
* @see compile for supported selector queries.
* @returns the first match, or null if there was no match.
*/
export const selectOne = getSelectorFunc(
<Node, ElementNode extends Node>(
query: Predicate<ElementNode>,
elems: Node[] | null,
options: InternalOptions<Node, ElementNode>
): ElementNode | null =>
query === boolbase.falseFunc || !elems || elems.length === 0
? null
: options.adapter.findOne(query, elems)
);
/**
* Tests whether or not an element is matched by query.
*
* @template Node The generic Node type for the DOM adapter being used.
* @template ElementNode The Node type for elements for the DOM adapter being used.
* @param elem The element to test if it matches the query.
* @param query can be either a CSS selector string or a compiled query function.
* @param [options] options for querying the document.
* @see compile for supported selector queries.
* @returns
*/
export function is<Node, ElementNode extends Node>(
elem: ElementNode,
query: Query<ElementNode>,
options?: Options<Node, ElementNode>
): boolean {
const opts = convertOptionFormats(options);
return (typeof query === "function" ? query : compileRaw(query, opts))(
elem
);
}
/**
* Alias for selectAll(query, elems, options).
* @see [compile] for supported selector queries.
*/
export default selectAll;
// Export filters, pseudos and aliases to allow users to supply their own.
/** @deprecated Use the `pseudos` option instead. */
export { filters, pseudos, aliases } from "./pseudo-selectors/index.js";