-
-
Notifications
You must be signed in to change notification settings - Fork 8
/
index.ts
201 lines (178 loc) · 6.94 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
import type {ParseSelector} from 'typed-query-selector/parser.js';
// WARNING: Overloads have to repeated in that fashion because the actual function’s signature is discarded; Only the 2 overloads are brought into the .d.ts file. Tests pass because `tsd` reads from this file instead of `.d.ts`
// ParentNode is inherited by Element, Document, DocumentFragment
type BaseElements = ParentNode | Iterable<ParentNode>;
// Type predicate for TypeScript
function isQueryable(object: BaseElements): object is ParentNode {
return typeof (object as any).querySelectorAll === 'function';
}
/**
* @param selectors One or more CSS selectors separated by commas
* @param [baseElement] The element to look inside of
* @return The element found, if any
*/
function $<Selector extends string, Selected extends Element = ParseSelector<Selector, HTMLElement>>(
selectors: Selector | readonly Selector[],
baseElement?: ParentNode
): Selected | undefined;
function $<Selected extends Element = HTMLElement>(
selectors: string | readonly string[],
baseElement?: ParentNode
): Selected | undefined;
function $<Selected extends Element>(
selectors: string | readonly string[],
baseElement?: ParentNode,
): Selected | undefined {
// Shortcut with specified-but-null baseElement
if (arguments.length === 2 && !baseElement) {
return;
}
return (baseElement ?? document).querySelector<Selected>(String(selectors)) ?? undefined;
}
export class ElementNotFoundError extends Error {
override name = 'ElementNotFoundError';
}
/**
* @param selectors One or more CSS selectors separated by commas
* @param [baseElement] The element to look inside of
* @return The element found, or an error
*/
function expectElement<Selector extends string, Selected extends Element = ParseSelector<Selector, HTMLElement>>(
selectors: Selector | readonly Selector[],
baseElement?: ParentNode
): Selected;
function expectElement<Selected extends Element = HTMLElement>(
selectors: string | readonly string[],
baseElement?: ParentNode
): Selected;
function expectElement<Selected extends Element>(
selectors: string | readonly string[],
baseElement?: ParentNode,
): Selected {
// Shortcut with specified-but-null baseElement
if (arguments.length === 2 && !baseElement) {
throw new ElementNotFoundError('Expected element not found because the base is specified but null');
}
const element = (baseElement ?? document).querySelector<Selected>(String(selectors));
if (element) {
return element;
}
throw new ElementNotFoundError(`Expected element not found: ${String(selectors)}`);
}
/**
* @param selectors One or more CSS selectors separated by commas
* @param [baseElement] The element to look inside of
* @return The element found, if any
*/
function lastElement<Selector extends string, Selected extends Element = ParseSelector<Selector, HTMLElement>>(
selectors: Selector | readonly Selector[],
baseElement?: ParentNode
): Selected | undefined;
function lastElement<Selected extends Element = HTMLElement>(
selectors: string | readonly string[],
baseElement?: ParentNode
): Selected | undefined;
function lastElement<Selected extends Element>(
selectors: string | readonly string[],
baseElement?: ParentNode,
): Selected | undefined {
// Shortcut with specified-but-null baseElement
if (arguments.length === 2 && !baseElement) {
return undefined;
}
const all = (baseElement ?? document).querySelectorAll<Selected>(String(selectors));
// eslint-disable-next-line unicorn/prefer-at -- Not an Array, not worth converting it
return all[all.length - 1];
}
/**
* @param selectors One or more CSS selectors separated by commas
* @param [baseElement] The element to look inside of
* @return Whether it's been found
*/
function elementExists(
selectors: string | readonly string[],
baseElement?: ParentNode,
): boolean {
// Shortcut with specified-but-null baseElement
if (arguments.length === 2 && !baseElement) {
return false;
}
return Boolean((baseElement ?? document).querySelector(String(selectors)));
}
/**
* @param selectors One or more CSS selectors separated by commas
* @param [baseElement] The element to look inside of
* @return The number of elements found
*/
function countElements(
selectors: string | readonly string[],
baseElement?: ParentNode,
): number {
// Shortcut with specified-but-null baseElement
if (arguments.length === 2 && !baseElement) {
return 0;
}
return (baseElement ?? document).querySelectorAll(String(selectors)).length;
}
/**
* @param selectors One or more CSS selectors separated by commas
* @param [baseElements] The element or list of elements to look inside of
* @return An array of elements found
*/
function $$<Selector extends string, Selected extends Element = ParseSelector<Selector, HTMLElement>>(
selectors: Selector | readonly Selector[],
baseElements?: BaseElements
): Selected[];
function $$<Selected extends Element = HTMLElement>(
selectors: string | readonly string[],
baseElements?: BaseElements
): Selected[];
function $$<Selected extends Element>(
selectors: string | readonly string[],
baseElements?: BaseElements,
): Selected[] {
// Shortcut with specified-but-null baseElements
if (arguments.length === 2 && !baseElements) {
return [];
}
// Can be: select.all('selectors') or select.all('selectors', singleElementOrDocument)
if (!baseElements || isQueryable(baseElements)) {
const elements = (baseElements ?? document).querySelectorAll<Selected>(String(selectors));
return Array.prototype.slice.call(elements) as Selected[];
}
const elements = new Set<Selected>();
for (const baseElement of baseElements) {
for (const element of baseElement.querySelectorAll<Selected>(String(selectors))) {
elements.add(element);
}
}
return [...elements]; // Convert to array
}
/**
* @param selectors One or more CSS selectors separated by commas
* @param [baseElements] The element or list of elements to look inside of
* @return An array of elements found
*/
function expectElements<Selector extends string, Selected extends Element = ParseSelector<Selector, HTMLElement>>(
selectors: Selector | readonly Selector[],
baseElements?: BaseElements
): Selected[];
function expectElements<Selected extends Element = HTMLElement>(
selectors: string | readonly string[],
baseElements?: BaseElements
): Selected[];
function expectElements<Selected extends Element>(
selectors: string | readonly string[],
baseElements?: BaseElements,
): Selected[] {
// Shortcut with specified-but-null baseElements
if (arguments.length === 2 && !baseElements) {
throw new ElementNotFoundError('Expected elements not found because the base is specified but null');
}
const elements = $$<Selected>(selectors, baseElements);
if (elements.length > 0) {
return elements;
}
throw new ElementNotFoundError(`Expected elements not found: ${String(selectors)}`);
}
export {$, $$, lastElement, elementExists, expectElement, expectElements, countElements};