@sv443-network/userutils 0.1.4 → 0.3.0

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.
package/CHANGELOG.md CHANGED
@@ -1,22 +1,16 @@
1
1
  # @sv443-network/userutils
2
2
 
3
- ## 0.1.4
3
+ ## 0.3.0
4
4
 
5
- ### Patch Changes
6
-
7
- - a1e8dec: please just work thanks
8
-
9
- ## 0.1.3
10
-
11
- ### Patch Changes
5
+ ### Minor Changes
12
6
 
13
- - 9673cad: more testing
7
+ - 07ec443: add getSelectorMap() to return all currently registered selectors
14
8
 
15
- ## 0.1.2
9
+ ## 0.2.0
16
10
 
17
- ### Patch Changes
11
+ ### Minor Changes
18
12
 
19
- - 6eafb79: test publish workflow lol
13
+ - 0cf2254: add onSelector() to call a listener once a selector is found in the DOM
20
14
 
21
15
  ## 0.1.1
22
16
 
package/README.md CHANGED
@@ -8,6 +8,8 @@ Contains builtin TypeScript declarations.
8
8
  - [Installation](#installation)
9
9
  - [Features](#features)
10
10
  - [onSelector()](#onselector) - call a listener once a selector is found in the DOM
11
+ - [initOnSelector()](#initonselector) - needs to be called once to be able to use `onSelector()`
12
+ - [getSelectorMap()](#getselectormap) - returns all currently registered selectors, listeners and options
11
13
  - [autoPlural()](#autoplural) - automatically pluralize a string
12
14
  - [clamp()](#clamp) - clamp a number between a min and max value
13
15
  - [pauseFor()](#pausefor) - pause the execution of a function for a given amount of time
@@ -54,16 +56,129 @@ If you like using this library, please consider [supporting development](https:/
54
56
  ## Features:
55
57
 
56
58
  ### onSelector()
57
- \- UNFINISHED -
59
+ Usage:
60
+ ```ts
61
+ onSelector<TElement = HTMLElement>(selector: string, options: {
62
+ listener: (elements: TElement | NodeListOf<TElement>) => void,
63
+ all?: boolean,
64
+ continuous?: boolean,
65
+ }): void
66
+ ```
58
67
 
59
68
  Registers a listener to be called whenever the element(s) behind a selector is/are found in the DOM.
60
- In order to use this function, the MutationObservers have to be initialized with `initOnSelector()` first.
69
+ If the selector already exists, the listener will be called immediately.
70
+
71
+ If `all` is set to `true`, querySelectorAll() will be used instead and the listener will return a NodeList of matching elements.
72
+ This will also include elements that were already found in a previous listener call.
73
+ If set to `false` (default), querySelector() will be used and only the first matching element will be returned.
74
+
75
+ If `continuous` is set to `true`, the listener will not be deregistered after it was called once (defaults to false).
76
+
77
+ When using TypeScript, the generic `TElement` can be used to specify the type of the element(s) that the listener will return.
61
78
 
62
- Example:
79
+ ⚠️ In order to use this function, [`initOnSelector()`](#initonselector) has to be called as soon as possible.
80
+ This initialization function has to be called after `DOMContentLoaded` is fired (or immediately if `@run-at document-end` is set).
81
+
82
+ Calling onSelector() before `DOMContentLoaded` is fired will not throw an error, but it also won't trigger listeners until the DOM is accessible.
83
+
84
+ <details><summary><b>Example - click to view</b></summary>
85
+
63
86
  ```ts
64
- // TODO
87
+ document.addEventListener("DOMContentLoaded", initOnSelector);
88
+
89
+ // Continuously checks if `div` elements are added to the DOM, then returns all of them (even previously detected ones) in a NodeList
90
+ onSelector<HTMLDivElement>("div", {
91
+ listener: (elements) => {
92
+ console.log("Elements found:", elements); // type = NodeListOf<HTMLDivElement>
93
+ },
94
+ all: true,
95
+ continuous: true,
96
+ });
97
+
98
+ // Checks if an input element with a value attribute of "5" is added to the DOM, then returns it and deregisters the listener
99
+ onSelector<HTMLInputElement>("input[value=\"5\"]", {
100
+ listener: (element) => {
101
+ console.log("Element found:", element); // type = HTMLInputElement
102
+ },
103
+ });
65
104
  ```
66
105
 
106
+ </details>
107
+
108
+ <br>
109
+
110
+ ### initOnSelector()
111
+ Usage:
112
+ ```ts
113
+ initOnSelector(options?: {
114
+ attributes?: boolean,
115
+ characterData?: boolean,
116
+ }): void
117
+ ```
118
+
119
+ Initializes the MutationObserver that is used by [`onSelector()`](#onselector) to check for the registered selectors whenever a DOM change occurs on the `<body>`
120
+ By default, this only checks if elements are added or removed (at any depth).
121
+
122
+ Set `attributes` to `true` to also check for attribute changes on every single descendant of the `<body>` (defaults to false).
123
+ Set `characterData` to `true` to also check for character data changes on every single descendant of the `<body>` (defaults to false).
124
+
125
+ ⚠️ Using these extra options can have a performance impact on larger sites or sites with a constantly changing DOM.
126
+
127
+ ⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
128
+
129
+ <details><summary><b>Example - click to view</b></summary>
130
+
131
+ ```ts
132
+ document.addEventListener("DOMContentLoaded", () => {
133
+ initOnSelector({
134
+ attributes: true,
135
+ characterData: true,
136
+ });
137
+ });
138
+ ```
139
+
140
+ </details>
141
+
142
+ <br>
143
+
144
+ ### getSelectorMap()
145
+ Usage: `getSelectorMap(): Map<string, OnSelectorOptions[]>`
146
+
147
+ Returns a Map of all currently registered selectors and their options, including listener function.
148
+ Since multiple listeners can be registered for the same selector, the value of the Map is an array of `OnSelectorOptions` objects.
149
+
150
+ <details><summary><b>Example - click to view</b></summary>
151
+
152
+ ```ts
153
+ document.addEventListener("DOMContentLoaded", initOnSelector);
154
+
155
+ onSelector<HTMLDivElement>("div", {
156
+ listener: (elements) => void 0,
157
+ all: true,
158
+ continuous: true,
159
+ });
160
+
161
+ onSelector<HTMLDivElement>("div", {
162
+ listener: (elements) => void 0,
163
+ });
164
+
165
+ const selectorMap = getSelectorMap();
166
+ // Map(1) {
167
+ // "div" => [
168
+ // {
169
+ // listener: (elements) => void 0,
170
+ // all: true,
171
+ // continuous: true,
172
+ // },
173
+ // {
174
+ // listener: (elements) => void 0,
175
+ // },
176
+ // ]
177
+ // }
178
+ ```
179
+
180
+ </details>
181
+
67
182
  <br>
68
183
 
69
184
  ### autoPlural()
@@ -72,7 +187,8 @@ Usage: `autoPlural(str: string, num: number | Array | NodeList): string`
72
187
  Automatically pluralizes a string if the given number is not 1.
73
188
  If an array or NodeList is passed, the length of it will be used.
74
189
 
75
- Example:
190
+ <details><summary><b>Example - click to view</b></summary>
191
+
76
192
  ```ts
77
193
  autoPlural("apple", 0); // "apples"
78
194
  autoPlural("apple", 1); // "apple"
@@ -80,8 +196,13 @@ autoPlural("apple", 2); // "apples"
80
196
 
81
197
  autoPlural("apple", [1]); // "apple"
82
198
  autoPlural("apple", [1, 2]); // "apples"
199
+
200
+ const items = [1, 2, 3, 4, "foo", "bar"];
201
+ console.log(`Found ${items.length} ${autoPlural("item", items)}`); // "Found 6 items"
83
202
  ```
84
203
 
204
+ </details>
205
+
85
206
  <br>
86
207
 
87
208
  ### clamp()
@@ -89,13 +210,17 @@ Usage: `clamp(num: number, min: number, max: number): number`
89
210
 
90
211
  Clamps a number between a min and max value.
91
212
 
92
- Example:
213
+ <details><summary><b>Example - click to view</b></summary>
214
+
93
215
  ```ts
94
216
  clamp(5, 0, 10); // 5
95
217
  clamp(-1, 0, 10); // 0
218
+ clamp(7, 0, 10); // 7
96
219
  clamp(Infinity, 0, 10); // 10
97
220
  ```
98
221
 
222
+ </details>
223
+
99
224
  <br>
100
225
 
101
226
  ### pauseFor()
@@ -103,7 +228,8 @@ Usage: `pauseFor(ms: number): Promise<void>`
103
228
 
104
229
  Pauses async execution for a given amount of time.
105
230
 
106
- Example:
231
+ <details><summary><b>Example - click to view</b></summary>
232
+
107
233
  ```ts
108
234
  async function run() {
109
235
  console.log("Hello");
@@ -112,6 +238,8 @@ async function run() {
112
238
  }
113
239
  ```
114
240
 
241
+ </details>
242
+
115
243
  <br>
116
244
 
117
245
  ### debounce()
@@ -121,13 +249,16 @@ Debounces a function, meaning that it will only be called once after a given amo
121
249
  This is very useful for functions that are called repeatedly, like event listeners, to remove extraneous calls.
122
250
  The timeout will default to 300ms if left undefined.
123
251
 
124
- Example:
252
+ <details><summary><b>Example - click to view</b></summary>
253
+
125
254
  ```ts
126
255
  window.addEventListener("resize", debounce((event) => {
127
256
  console.log("Window was resized:", event);
128
257
  }, 500)); // 500ms timeout
129
258
  ```
130
259
 
260
+ </details>
261
+
131
262
  <br>
132
263
 
133
264
  ### getUnsafeWindow()
@@ -136,55 +267,76 @@ Usage: `getUnsafeWindow(): Window`
136
267
  Returns the unsafeWindow object or falls back to the regular window object if the `@grant unsafeWindow` is not given.
137
268
  Userscripts are sandboxed and do not have access to the regular window object, so this function is useful for websites that reject some events that were dispatched by the userscript.
138
269
 
139
- Example:
270
+ <details><summary><b>Example - click to view</b></summary>
271
+
140
272
  ```ts
141
- const mouseEvent = new MouseEvent("click", {
273
+ // trick the site into thinking the mouse was moved:
274
+ const mouseEvent = new MouseEvent("mousemove", {
142
275
  view: getUnsafeWindow(),
276
+ screenY: 69,
277
+ screenX: 420,
278
+ movementX: 10,
279
+ movementY: 0,
143
280
  });
144
281
  document.body.dispatchEvent(mouseEvent);
145
282
  ```
146
283
 
284
+ </details>
285
+
147
286
  <br>
148
287
 
149
288
  ### insertAfter()
150
289
  Usage: `insertAfter(beforeElement: HTMLElement, afterElement: HTMLElement): HTMLElement`
151
290
 
152
291
  Inserts the element passed as `afterElement` as a sibling after the passed `beforeElement`.
153
- The `afterElement` will be returned.
292
+ The passed `afterElement` will be returned.
293
+
294
+ ⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
154
295
 
155
- Example:
296
+ <details><summary><b>Example - click to view</b></summary>
297
+
156
298
  ```ts
299
+ // insert a <div> as a sibling next to an element
157
300
  const beforeElement = document.querySelector("#before");
158
301
  const afterElement = document.createElement("div");
159
302
  afterElement.innerText = "After";
160
303
  insertAfter(beforeElement, afterElement);
161
304
  ```
162
305
 
306
+ </details>
307
+
163
308
  <br>
164
309
 
165
310
  ### addParent()
166
311
  Usage: `addParent(element: HTMLElement, newParent: HTMLElement): HTMLElement`
167
312
 
168
313
  Adds a parent element around the passed `element` and returns the new parent.
169
- Previously registered event listeners should be kept intact.
314
+ Previously registered event listeners are kept intact.
170
315
 
171
- Example:
316
+ ⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
317
+
318
+ <details><summary><b>Example - click to view</b></summary>
319
+
172
320
  ```ts
321
+ // add an <a> around an element
173
322
  const element = document.querySelector("#element");
174
323
  const newParent = document.createElement("a");
175
324
  newParent.href = "https://example.org/";
176
325
  addParent(element, newParent);
177
326
  ```
178
327
 
328
+ </details>
329
+
179
330
  <br>
180
331
 
181
332
  ### addGlobalStyle()
182
333
  Usage: `addGlobalStyle(css: string): void`
183
334
 
184
335
  Adds a global style to the page in form of a `<style>` element that's inserted into the `<head>`.
185
- This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
336
+ ⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
186
337
 
187
- Example:
338
+ <details><summary><b>Example - click to view</b></summary>
339
+
188
340
  ```ts
189
341
  document.addEventListener("DOMContentLoaded", () => {
190
342
  addGlobalStyle(`
@@ -195,43 +347,62 @@ document.addEventListener("DOMContentLoaded", () => {
195
347
  });
196
348
  ```
197
349
 
350
+ </details>
351
+
198
352
  <br>
199
353
 
200
354
  ### preloadImages()
201
- Usage: `preloadImages(...urls: string[]): Promise<void>`
355
+ Usage: `preloadImages(urls: string[], rejects?: boolean): Promise<void>`
202
356
 
203
357
  Preloads images into browser cache by creating an invisible `<img>` element for each URL passed.
204
358
  The images will be loaded in parallel and the returned Promise will only resolve once all images have been loaded.
205
- The resulting PromiseSettledResult array will contain the image elements if resolved, or an ErrorEvent if rejected.
359
+ The resulting PromiseSettledResult array will contain the image elements if resolved, or an ErrorEvent if rejected, but only if `rejects` is set to true.
206
360
 
207
- Example:
361
+ <details><summary><b>Example - click to view</b></summary>
362
+
208
363
  ```ts
209
- preloadImages(
364
+ preloadImages([
210
365
  "https://example.org/image1.png",
211
366
  "https://example.org/image2.png",
212
367
  "https://example.org/image3.png",
213
- ).then((results) => {
214
- console.log("Images preloaded. Results:", results);
215
- });
368
+ ], true)
369
+ .then((results) => {
370
+ console.log("Images preloaded. Results:", results);
371
+ });
216
372
  ```
217
373
 
374
+ </details>
375
+
218
376
  <br>
219
377
 
220
378
  ### fetchAdvanced()
221
- Usage: `fetchAdvanced(url: string, options?: FetchAdvancedOptions): Promise<Response>`
379
+ Usage:
380
+ ```ts
381
+ fetchAdvanced(url: string, options?: {
382
+ timeout?: number,
383
+ // any other options from fetch() except for signal
384
+ }): Promise<Response>
385
+ ```
222
386
 
223
387
  A wrapper around the native `fetch()` function that adds options like a timeout property.
224
388
  The timeout will default to 10 seconds if left undefined.
225
389
 
226
- Example:
390
+ <details><summary><b>Example - click to view</b></summary>
391
+
227
392
  ```ts
228
- fetchAdvanced("https://example.org/", {
393
+ fetchAdvanced("https://api.example.org/data", {
229
394
  timeout: 5000,
230
- }).then((response) => {
231
- console.log("Response:", response);
395
+ // also accepts any other fetch options like headers:
396
+ headers: {
397
+ "Accept": "application/json",
398
+ },
399
+ }).then(async (response) => {
400
+ console.log("Data:", await response.json());
232
401
  });
233
402
  ```
234
403
 
404
+ </details>
405
+
235
406
  <br>
236
407
 
237
408
  ### openInNewTab()
@@ -241,29 +412,38 @@ Creates an invisible anchor with a `_blank` target and clicks it.
241
412
  Contrary to `window.open()`, this has a lesser chance to get blocked by the browser's popup blocker and doesn't open the URL as a new window.
242
413
  This function has to be run in relatively quick succession in response to a user interaction event, else the browser might reject it.
243
414
 
244
- Example:
415
+ ⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
416
+
417
+ <details><summary><b>Example - click to view</b></summary>
418
+
245
419
  ```ts
246
- document.querySelector("#button").addEventListener("click", () => {
420
+ document.querySelector("#my-button").addEventListener("click", () => {
247
421
  openInNewTab("https://example.org/");
248
422
  });
249
423
  ```
250
424
 
425
+ </details>
426
+
251
427
  <br>
252
428
 
253
429
  ### interceptEvent()
254
430
  Usage: `interceptEvent(eventObject: EventTarget, eventName: string, predicate: () => boolean): void`
255
431
 
256
432
  Intercepts all events dispatched on the `eventObject` and prevents the listeners from being called as long as the predicate function returns a truthy value.
257
- This function should be called as soon as possible (I recommend using `@run-at document-start`), as it will only intercept events that are added after this function is called.
258
- Calling this function will set the `Error.stackTraceLimit` to 1000 to ensure the stack trace is preserved.
433
+ Calling this function will set the `Error.stackTraceLimit` to 1000 (if it's not already higher) to ensure the stack trace is preserved.
259
434
 
260
- Example:
435
+ ⚠️ This function should be called as soon as possible (I recommend using `@run-at document-start`), as it will only intercept events that are *attached* after this function is called.
436
+
437
+ <details><summary><b>Example - click to view</b></summary>
438
+
261
439
  ```ts
262
440
  interceptEvent(document.body, "click", () => {
263
- return true; // prevent all click events on the body
441
+ return true; // prevent all click events on the body element
264
442
  });
265
443
  ```
266
444
 
445
+ </details>
446
+
267
447
  <br>
268
448
 
269
449
  ### interceptWindowEvent()
@@ -272,13 +452,18 @@ Usage: `interceptWindowEvent(eventName: string, predicate: () => boolean): void`
272
452
  Intercepts all events dispatched on the `window` object and prevents the listeners from being called as long as the predicate function returns a truthy value.
273
453
  This is essentially the same as [`interceptEvent()`](#interceptevent), but automatically uses the `unsafeWindow` (or falls back to regular `window`).
274
454
 
275
- Example:
455
+ ⚠️ This function should be called as soon as possible (I recommend using `@run-at document-start`), as it will only intercept events that are *attached* after this function is called.
456
+
457
+ <details><summary><b>Example - click to view</b></summary>
458
+
276
459
  ```ts
277
460
  interceptWindowEvent("beforeunload", () => {
278
461
  return true; // prevent the pesky "Are you sure you want to leave this page?" popup
279
462
  });
280
463
  ```
281
464
 
465
+ </details>
466
+
282
467
 
283
468
  <br><br>
284
469
 
package/dist/index.d.mts CHANGED
@@ -1,12 +1,28 @@
1
- type SelectorExistsOpts = {
2
- /** The selector to check for */
3
- selector: string;
4
- /** Whether to use `querySelectorAll()` instead */
5
- all?: boolean;
6
- /** Whether to call the listener continuously instead of once */
1
+ type InitOnSelectorOpts = {
2
+ /** Set to true if mutations to any element's attributes are to also trigger the onSelector check (warning: this might draw a lot of performance on larger sites) */
3
+ attributes?: boolean;
4
+ /** Set to true if mutations to any element's character data are to also trigger the onSelector check (warning: this might draw a lot of performance on larger sites) */
5
+ characterData?: boolean;
6
+ };
7
+ type OnSelectorOpts<TElem extends Element = HTMLElement> = SelectorOptsOne<TElem> | SelectorOptsAll<TElem>;
8
+ type SelectorOptsOne<TElem extends Element> = SelectorOptsBase & {
9
+ /** Whether to use `querySelectorAll()` instead - default is false */
10
+ all?: false;
11
+ /** Gets called whenever the selector was found in the DOM */
12
+ listener: (element: TElem) => void;
13
+ };
14
+ type SelectorOptsAll<TElem extends Element> = SelectorOptsBase & {
15
+ /** Whether to use `querySelectorAll()` instead - default is false */
16
+ all: true;
17
+ /** Gets called whenever the selector was found in the DOM */
18
+ listener: (elements: NodeListOf<TElem>) => void;
19
+ };
20
+ type SelectorOptsBase = {
21
+ /** Whether to call the listener continuously instead of once - default is false */
7
22
  continuous?: boolean;
8
23
  };
9
24
  type FetchAdvancedOpts = RequestInit & Partial<{
25
+ /** Timeout in milliseconds after which the fetch call will be canceled with an AbortController signal */
10
26
  timeout: number;
11
27
  }>;
12
28
 
@@ -77,10 +93,22 @@ declare function interceptWindowEvent(eventName: keyof WindowEventMap, predicate
77
93
  * Calls the `listener` as soon as the `selector` exists in the DOM.
78
94
  * Listeners are deleted when they are called once, unless `options.continuous` is set.
79
95
  * Multiple listeners with the same selector may be registered.
80
- * @template TElem The type of element that this selector will return - FIXME: listener inferring doesn't work when this generic is given
96
+ * @param selector The selector to listen for
97
+ * @param options Used for switching to `querySelectorAll()` and for calling the listener continuously
98
+ * @template TElem The type of element that the listener will return as its argument (defaults to the generic HTMLElement)
99
+ */
100
+ declare function onSelector<TElem extends Element = HTMLElement>(selector: string, options: OnSelectorOpts<TElem>): void;
101
+ /**
102
+ * Removes all listeners registered in `onSelector()` that have the given selector
103
+ * @returns Returns true when all listeners with the associated selector were found and removed, false otherwise
104
+ */
105
+ declare function removeOnSelector(selector: string): boolean;
106
+ /**
107
+ * Initializes a MutationObserver that checks for all registered selectors whenever an element is added to or removed from the `<body>`
108
+ * @param opts For fine-tuning when the MutationObserver checks for the selectors
81
109
  */
82
- declare function onSelector<TElem = HTMLElement, TOpts extends SelectorExistsOpts = SelectorExistsOpts>(options: TOpts, listener: (element: TOpts["all"] extends true ? (TElem extends HTMLElement ? NodeListOf<TElem> : TElem) : TElem) => void): void;
83
- /** Removes all listeners registered in `onSelector()` with a matching selector property */
84
- declare function removeOnSelector(selector: string): void;
110
+ declare function initOnSelector(opts?: InitOnSelectorOpts): void;
111
+ /** Returns all currently registered selectors, as a map of selector strings to their associated options */
112
+ declare function getSelectorMap(): Map<string, OnSelectorOpts[]>;
85
113
 
86
- export { FetchAdvancedOpts, SelectorExistsOpts, addGlobalStyle, addParent, autoPlural, clamp, debounce, fetchAdvanced, getUnsafeWindow, insertAfter, interceptEvent, interceptWindowEvent, onSelector, openInNewTab, pauseFor, preloadImages, removeOnSelector };
114
+ export { FetchAdvancedOpts, InitOnSelectorOpts, OnSelectorOpts, addGlobalStyle, addParent, autoPlural, clamp, debounce, fetchAdvanced, getSelectorMap, getUnsafeWindow, initOnSelector, insertAfter, interceptEvent, interceptWindowEvent, onSelector, openInNewTab, pauseFor, preloadImages, removeOnSelector };
package/dist/index.d.ts CHANGED
@@ -1,12 +1,28 @@
1
- type SelectorExistsOpts = {
2
- /** The selector to check for */
3
- selector: string;
4
- /** Whether to use `querySelectorAll()` instead */
5
- all?: boolean;
6
- /** Whether to call the listener continuously instead of once */
1
+ type InitOnSelectorOpts = {
2
+ /** Set to true if mutations to any element's attributes are to also trigger the onSelector check (warning: this might draw a lot of performance on larger sites) */
3
+ attributes?: boolean;
4
+ /** Set to true if mutations to any element's character data are to also trigger the onSelector check (warning: this might draw a lot of performance on larger sites) */
5
+ characterData?: boolean;
6
+ };
7
+ type OnSelectorOpts<TElem extends Element = HTMLElement> = SelectorOptsOne<TElem> | SelectorOptsAll<TElem>;
8
+ type SelectorOptsOne<TElem extends Element> = SelectorOptsBase & {
9
+ /** Whether to use `querySelectorAll()` instead - default is false */
10
+ all?: false;
11
+ /** Gets called whenever the selector was found in the DOM */
12
+ listener: (element: TElem) => void;
13
+ };
14
+ type SelectorOptsAll<TElem extends Element> = SelectorOptsBase & {
15
+ /** Whether to use `querySelectorAll()` instead - default is false */
16
+ all: true;
17
+ /** Gets called whenever the selector was found in the DOM */
18
+ listener: (elements: NodeListOf<TElem>) => void;
19
+ };
20
+ type SelectorOptsBase = {
21
+ /** Whether to call the listener continuously instead of once - default is false */
7
22
  continuous?: boolean;
8
23
  };
9
24
  type FetchAdvancedOpts = RequestInit & Partial<{
25
+ /** Timeout in milliseconds after which the fetch call will be canceled with an AbortController signal */
10
26
  timeout: number;
11
27
  }>;
12
28
 
@@ -77,10 +93,22 @@ declare function interceptWindowEvent(eventName: keyof WindowEventMap, predicate
77
93
  * Calls the `listener` as soon as the `selector` exists in the DOM.
78
94
  * Listeners are deleted when they are called once, unless `options.continuous` is set.
79
95
  * Multiple listeners with the same selector may be registered.
80
- * @template TElem The type of element that this selector will return - FIXME: listener inferring doesn't work when this generic is given
96
+ * @param selector The selector to listen for
97
+ * @param options Used for switching to `querySelectorAll()` and for calling the listener continuously
98
+ * @template TElem The type of element that the listener will return as its argument (defaults to the generic HTMLElement)
99
+ */
100
+ declare function onSelector<TElem extends Element = HTMLElement>(selector: string, options: OnSelectorOpts<TElem>): void;
101
+ /**
102
+ * Removes all listeners registered in `onSelector()` that have the given selector
103
+ * @returns Returns true when all listeners with the associated selector were found and removed, false otherwise
104
+ */
105
+ declare function removeOnSelector(selector: string): boolean;
106
+ /**
107
+ * Initializes a MutationObserver that checks for all registered selectors whenever an element is added to or removed from the `<body>`
108
+ * @param opts For fine-tuning when the MutationObserver checks for the selectors
81
109
  */
82
- declare function onSelector<TElem = HTMLElement, TOpts extends SelectorExistsOpts = SelectorExistsOpts>(options: TOpts, listener: (element: TOpts["all"] extends true ? (TElem extends HTMLElement ? NodeListOf<TElem> : TElem) : TElem) => void): void;
83
- /** Removes all listeners registered in `onSelector()` with a matching selector property */
84
- declare function removeOnSelector(selector: string): void;
110
+ declare function initOnSelector(opts?: InitOnSelectorOpts): void;
111
+ /** Returns all currently registered selectors, as a map of selector strings to their associated options */
112
+ declare function getSelectorMap(): Map<string, OnSelectorOpts[]>;
85
113
 
86
- export { FetchAdvancedOpts, SelectorExistsOpts, addGlobalStyle, addParent, autoPlural, clamp, debounce, fetchAdvanced, getUnsafeWindow, insertAfter, interceptEvent, interceptWindowEvent, onSelector, openInNewTab, pauseFor, preloadImages, removeOnSelector };
114
+ export { FetchAdvancedOpts, InitOnSelectorOpts, OnSelectorOpts, addGlobalStyle, addParent, autoPlural, clamp, debounce, fetchAdvanced, getSelectorMap, getUnsafeWindow, initOnSelector, insertAfter, interceptEvent, interceptWindowEvent, onSelector, openInNewTab, pauseFor, preloadImages, removeOnSelector };
package/dist/index.js CHANGED
@@ -1,19 +1,21 @@
1
1
  'use strict';
2
2
 
3
- var E=Object.defineProperty,T=Object.defineProperties;var x=Object.getOwnPropertyDescriptors;var l=Object.getOwnPropertySymbols;var v=Object.prototype.hasOwnProperty,b=Object.prototype.propertyIsEnumerable;var p=(t,e,n)=>e in t?E(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,u=(t,e)=>{for(var n in e||(e={}))v.call(e,n)&&p(t,n,e[n]);if(l)for(var n of l(e))b.call(e,n)&&p(t,n,e[n]);return t},m=(t,e)=>T(t,x(e));var f=(t,e,n)=>new Promise((o,r)=>{var a=s=>{try{c(n.next(s));}catch(d){r(d);}},i=s=>{try{c(n.throw(s));}catch(d){r(d);}},c=s=>s.done?o(s.value):Promise.resolve(s.value).then(a,i);c((n=n.apply(t,e)).next());});function h(t,e){return (Array.isArray(e)||e instanceof NodeList)&&(e=e.length),`${t}${e===1?"":"s"}`}function O(t,e,n){return Math.max(Math.min(t,n),e)}function w(t){return new Promise(e=>{setTimeout(e,t);})}function A(t,e=300){let n;return function(...o){clearTimeout(n),n=setTimeout(()=>t.apply(this,o),e);}}function y(){try{return unsafeWindow}catch(t){return window}}function M(t,e){var n;return (n=t.parentNode)==null||n.insertBefore(e,t.nextSibling),e}function _(t,e){let n=t.parentNode;if(!n)throw new Error("Element doesn't have a parent node");return n.replaceChild(e,t),e.appendChild(t),e}function k(t){let e=document.createElement("style");e.innerHTML=t,document.head.appendChild(e);}function S(t,e=!1){let n=t.map(o=>new Promise((r,a)=>{let i=new Image;i.src=o,i.addEventListener("load",()=>r(i)),i.addEventListener("error",c=>e&&a(c));}));return Promise.allSettled(n)}function H(n){return f(this,arguments,function*(t,e={}){let{timeout:o=1e4}=e,r=new AbortController,a=setTimeout(()=>r.abort(),o),i=yield fetch(t,m(u({},e),{signal:r.signal}));return clearTimeout(a),i})}function N(t){let e=document.createElement("a");Object.assign(e,{className:"userutils-open-in-new-tab",target:"_blank",rel:"noopener noreferrer",href:t}),e.style.display="none",document.body.appendChild(e),e.click(),setTimeout(e.remove,50);}function g(t,e,n){typeof Error.stackTraceLimit=="number"&&Error.stackTraceLimit<1e3&&(Error.stackTraceLimit=1e3),function(o){element.__proto__.addEventListener=function(...r){if(!(r[0]===e&&n()))return o.apply(this,r)};}(t.__proto__.addEventListener);}function j(t,e){return g(y(),t,e)}function F(t,e){}function W(t){}
3
+ var b=Object.defineProperty,y=Object.defineProperties;var g=Object.getOwnPropertyDescriptors;var m=Object.getOwnPropertySymbols;var v=Object.prototype.hasOwnProperty,x=Object.prototype.propertyIsEnumerable;var f=(t,e,n)=>e in t?b(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,u=(t,e)=>{for(var n in e||(e={}))v.call(e,n)&&f(t,n,e[n]);if(m)for(var n of m(e))x.call(e,n)&&f(t,n,e[n]);return t},d=(t,e)=>y(t,g(e));var E=(t,e,n)=>new Promise((r,o)=>{var s=a=>{try{l(n.next(a));}catch(p){o(p);}},i=a=>{try{l(n.throw(a));}catch(p){o(p);}},l=a=>a.done?r(a.value):Promise.resolve(a.value).then(s,i);l((n=n.apply(t,e)).next());});function w(t,e){return (Array.isArray(e)||e instanceof NodeList)&&(e=e.length),`${t}${e===1?"":"s"}`}function M(t,e,n){return Math.max(Math.min(t,n),e)}function S(t){return new Promise(e=>{setTimeout(e,t);})}function A(t,e=300){let n;return function(...r){clearTimeout(n),n=setTimeout(()=>t.apply(this,r),e);}}function h(){try{return unsafeWindow}catch(t){return window}}function _(t,e){var n;return (n=t.parentNode)==null||n.insertBefore(e,t.nextSibling),e}function k(t,e){let n=t.parentNode;if(!n)throw new Error("Element doesn't have a parent node");return n.replaceChild(e,t),e.appendChild(t),e}function H(t){let e=document.createElement("style");e.innerHTML=t,document.head.appendChild(e);}function I(t,e=!1){let n=t.map(r=>new Promise((o,s)=>{let i=new Image;i.src=r,i.addEventListener("load",()=>o(i)),i.addEventListener("error",l=>e&&s(l));}));return Promise.allSettled(n)}function j(n){return E(this,arguments,function*(t,e={}){let{timeout:r=1e4}=e,o=new AbortController,s=setTimeout(()=>o.abort(),r),i=yield fetch(t,d(u({},e),{signal:o.signal}));return clearTimeout(s),i})}function C(t){let e=document.createElement("a");Object.assign(e,{className:"userutils-open-in-new-tab",target:"_blank",rel:"noopener noreferrer",href:t}),e.style.display="none",document.body.appendChild(e),e.click(),setTimeout(e.remove,50);}function O(t,e,n){typeof Error.stackTraceLimit=="number"&&Error.stackTraceLimit<1e3&&(Error.stackTraceLimit=1e3),function(r){element.__proto__.addEventListener=function(...o){if(!(o[0]===e&&n()))return r.apply(this,o)};}(t.__proto__.addEventListener);}function N(t,e){return O(h(),t,e)}var c=new Map;function W(t,e){let n=[];c.has(t)&&(n=c.get(t)),n.push(e),c.set(t,n),T(t,n);}function $(t){return c.delete(t)}function T(t,e){let n=[];if(e.forEach((r,o)=>{try{let s=r.all?document.querySelectorAll(t):document.querySelector(t);s&&(r.listener(s),r.continuous||n.push(o));}catch(s){console.error(`Couldn't call listener for selector '${t}'`,s);}}),n.length>0){let r=e.filter((o,s)=>!n.includes(s));r.length===0?c.delete(t):c.set(t,r);}}function q(t={}){new MutationObserver(()=>{for(let[n,r]of c.entries())T(n,r);}).observe(document.body,d(u({},t),{childList:!0}));}function B(){return c}
4
4
 
5
- exports.addGlobalStyle = k;
6
- exports.addParent = _;
7
- exports.autoPlural = h;
8
- exports.clamp = O;
5
+ exports.addGlobalStyle = H;
6
+ exports.addParent = k;
7
+ exports.autoPlural = w;
8
+ exports.clamp = M;
9
9
  exports.debounce = A;
10
- exports.fetchAdvanced = H;
11
- exports.getUnsafeWindow = y;
12
- exports.insertAfter = M;
13
- exports.interceptEvent = g;
14
- exports.interceptWindowEvent = j;
15
- exports.onSelector = F;
16
- exports.openInNewTab = N;
17
- exports.pauseFor = w;
18
- exports.preloadImages = S;
19
- exports.removeOnSelector = W;
10
+ exports.fetchAdvanced = j;
11
+ exports.getSelectorMap = B;
12
+ exports.getUnsafeWindow = h;
13
+ exports.initOnSelector = q;
14
+ exports.insertAfter = _;
15
+ exports.interceptEvent = O;
16
+ exports.interceptWindowEvent = N;
17
+ exports.onSelector = W;
18
+ exports.openInNewTab = C;
19
+ exports.pauseFor = S;
20
+ exports.preloadImages = I;
21
+ exports.removeOnSelector = $;
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- var E=Object.defineProperty,T=Object.defineProperties;var x=Object.getOwnPropertyDescriptors;var l=Object.getOwnPropertySymbols;var v=Object.prototype.hasOwnProperty,b=Object.prototype.propertyIsEnumerable;var p=(t,e,n)=>e in t?E(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,u=(t,e)=>{for(var n in e||(e={}))v.call(e,n)&&p(t,n,e[n]);if(l)for(var n of l(e))b.call(e,n)&&p(t,n,e[n]);return t},m=(t,e)=>T(t,x(e));var f=(t,e,n)=>new Promise((o,r)=>{var a=s=>{try{c(n.next(s));}catch(d){r(d);}},i=s=>{try{c(n.throw(s));}catch(d){r(d);}},c=s=>s.done?o(s.value):Promise.resolve(s.value).then(a,i);c((n=n.apply(t,e)).next());});function h(t,e){return (Array.isArray(e)||e instanceof NodeList)&&(e=e.length),`${t}${e===1?"":"s"}`}function O(t,e,n){return Math.max(Math.min(t,n),e)}function w(t){return new Promise(e=>{setTimeout(e,t);})}function A(t,e=300){let n;return function(...o){clearTimeout(n),n=setTimeout(()=>t.apply(this,o),e);}}function y(){try{return unsafeWindow}catch(t){return window}}function M(t,e){var n;return (n=t.parentNode)==null||n.insertBefore(e,t.nextSibling),e}function _(t,e){let n=t.parentNode;if(!n)throw new Error("Element doesn't have a parent node");return n.replaceChild(e,t),e.appendChild(t),e}function k(t){let e=document.createElement("style");e.innerHTML=t,document.head.appendChild(e);}function S(t,e=!1){let n=t.map(o=>new Promise((r,a)=>{let i=new Image;i.src=o,i.addEventListener("load",()=>r(i)),i.addEventListener("error",c=>e&&a(c));}));return Promise.allSettled(n)}function H(n){return f(this,arguments,function*(t,e={}){let{timeout:o=1e4}=e,r=new AbortController,a=setTimeout(()=>r.abort(),o),i=yield fetch(t,m(u({},e),{signal:r.signal}));return clearTimeout(a),i})}function N(t){let e=document.createElement("a");Object.assign(e,{className:"userutils-open-in-new-tab",target:"_blank",rel:"noopener noreferrer",href:t}),e.style.display="none",document.body.appendChild(e),e.click(),setTimeout(e.remove,50);}function g(t,e,n){typeof Error.stackTraceLimit=="number"&&Error.stackTraceLimit<1e3&&(Error.stackTraceLimit=1e3),function(o){element.__proto__.addEventListener=function(...r){if(!(r[0]===e&&n()))return o.apply(this,r)};}(t.__proto__.addEventListener);}function j(t,e){return g(y(),t,e)}function F(t,e){}function W(t){}
1
+ var b=Object.defineProperty,y=Object.defineProperties;var g=Object.getOwnPropertyDescriptors;var m=Object.getOwnPropertySymbols;var v=Object.prototype.hasOwnProperty,x=Object.prototype.propertyIsEnumerable;var f=(t,e,n)=>e in t?b(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,u=(t,e)=>{for(var n in e||(e={}))v.call(e,n)&&f(t,n,e[n]);if(m)for(var n of m(e))x.call(e,n)&&f(t,n,e[n]);return t},d=(t,e)=>y(t,g(e));var E=(t,e,n)=>new Promise((r,o)=>{var s=a=>{try{l(n.next(a));}catch(p){o(p);}},i=a=>{try{l(n.throw(a));}catch(p){o(p);}},l=a=>a.done?r(a.value):Promise.resolve(a.value).then(s,i);l((n=n.apply(t,e)).next());});function w(t,e){return (Array.isArray(e)||e instanceof NodeList)&&(e=e.length),`${t}${e===1?"":"s"}`}function M(t,e,n){return Math.max(Math.min(t,n),e)}function S(t){return new Promise(e=>{setTimeout(e,t);})}function A(t,e=300){let n;return function(...r){clearTimeout(n),n=setTimeout(()=>t.apply(this,r),e);}}function h(){try{return unsafeWindow}catch(t){return window}}function _(t,e){var n;return (n=t.parentNode)==null||n.insertBefore(e,t.nextSibling),e}function k(t,e){let n=t.parentNode;if(!n)throw new Error("Element doesn't have a parent node");return n.replaceChild(e,t),e.appendChild(t),e}function H(t){let e=document.createElement("style");e.innerHTML=t,document.head.appendChild(e);}function I(t,e=!1){let n=t.map(r=>new Promise((o,s)=>{let i=new Image;i.src=r,i.addEventListener("load",()=>o(i)),i.addEventListener("error",l=>e&&s(l));}));return Promise.allSettled(n)}function j(n){return E(this,arguments,function*(t,e={}){let{timeout:r=1e4}=e,o=new AbortController,s=setTimeout(()=>o.abort(),r),i=yield fetch(t,d(u({},e),{signal:o.signal}));return clearTimeout(s),i})}function C(t){let e=document.createElement("a");Object.assign(e,{className:"userutils-open-in-new-tab",target:"_blank",rel:"noopener noreferrer",href:t}),e.style.display="none",document.body.appendChild(e),e.click(),setTimeout(e.remove,50);}function O(t,e,n){typeof Error.stackTraceLimit=="number"&&Error.stackTraceLimit<1e3&&(Error.stackTraceLimit=1e3),function(r){element.__proto__.addEventListener=function(...o){if(!(o[0]===e&&n()))return r.apply(this,o)};}(t.__proto__.addEventListener);}function N(t,e){return O(h(),t,e)}var c=new Map;function W(t,e){let n=[];c.has(t)&&(n=c.get(t)),n.push(e),c.set(t,n),T(t,n);}function $(t){return c.delete(t)}function T(t,e){let n=[];if(e.forEach((r,o)=>{try{let s=r.all?document.querySelectorAll(t):document.querySelector(t);s&&(r.listener(s),r.continuous||n.push(o));}catch(s){console.error(`Couldn't call listener for selector '${t}'`,s);}}),n.length>0){let r=e.filter((o,s)=>!n.includes(s));r.length===0?c.delete(t):c.set(t,r);}}function q(t={}){new MutationObserver(()=>{for(let[n,r]of c.entries())T(n,r);}).observe(document.body,d(u({},t),{childList:!0}));}function B(){return c}
2
2
 
3
- export { k as addGlobalStyle, _ as addParent, h as autoPlural, O as clamp, A as debounce, H as fetchAdvanced, y as getUnsafeWindow, M as insertAfter, g as interceptEvent, j as interceptWindowEvent, F as onSelector, N as openInNewTab, w as pauseFor, S as preloadImages, W as removeOnSelector };
3
+ export { H as addGlobalStyle, k as addParent, w as autoPlural, M as clamp, A as debounce, j as fetchAdvanced, B as getSelectorMap, h as getUnsafeWindow, q as initOnSelector, _ as insertAfter, O as interceptEvent, N as interceptWindowEvent, W as onSelector, C as openInNewTab, S as pauseFor, I as preloadImages, $ as removeOnSelector };
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@sv443-network/userutils",
3
- "version": "0.1.4",
3
+ "version": "0.3.0",
4
4
  "description": "Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, modify the DOM more easily and more ",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",
8
8
  "scripts": {
9
9
  "lint": "tsc && eslint .",
10
- "prepare-build": "tsup lib/index.ts --format cjs,esm --dts --clean --treeshake",
11
- "build": "npm run prepare-build -- --minify",
12
- "build-dev": "npm run prepare-build -- --sourcemap --watch",
10
+ "build-common": "tsup lib/index.ts --format cjs,esm --dts --clean --treeshake",
11
+ "build": "npm run build-common -- --minify",
12
+ "dev": "npm run build-common -- --sourcemap --watch",
13
13
  "publish-package": "npm run build && changeset publish"
14
14
  },
15
15
  "repository": {