@sv443-network/userutils 0.5.2 → 1.0.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,5 +1,22 @@
1
1
  # @sv443-network/userutils
2
2
 
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - a500a98: Added ConfigManager to manage persistent user configurations including data versioning and migration
8
+
9
+ ### Patch Changes
10
+
11
+ - 6d0a700: Event interceptor can now be toggled at runtime ([#16](https://github.com/Sv443-Network/UserUtils/issues/16))
12
+ - d038b21: Global (IIFE) build now comes with a header
13
+
14
+ ## 0.5.3
15
+
16
+ ### Patch Changes
17
+
18
+ - f97dae6: change bundling process
19
+
3
20
  ## 0.5.2
4
21
 
5
22
  ### Patch Changes
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <div style="text-align: center;" align="center">
2
2
 
3
3
  ## UserUtils
4
- Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, modify the DOM more easily and more.
4
+ Zero-dependency library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, manage persistent user configurations, modify the DOM more easily and more.
5
5
  Contains builtin TypeScript declarations. Webpack compatible and supports ESM and CJS.
6
6
  If you like using this library, please consider [supporting the development ❤️](https://github.com/sponsors/Sv443)
7
7
 
@@ -31,6 +31,7 @@ If you like using this library, please consider [supporting the development ❤
31
31
  - [mapRange()](#maprange) - map a number from one range to the same spot in another range
32
32
  - [randRange()](#randrange) - generate a random number between a min and max boundary
33
33
  - [Misc:](#misc)
34
+ - [ConfigManager()](#configmanager) - class that manages persistent userscript configurations, including data migration
34
35
  - [autoPlural()](#autoplural) - automatically pluralize a string
35
36
  - [pauseFor()](#pausefor) - pause the execution of a function for a given amount of time
36
37
  - [debounce()](#debounce) - call a function only once, after a given amount of time
@@ -51,10 +52,12 @@ If you like using this library, please consider [supporting the development ❤
51
52
  Then, import it in your script as usual:
52
53
  ```ts
53
54
  import { addGlobalStyle } from "@sv443-network/userutils";
54
- // or
55
- import * as userUtils from "@sv443-network/userutils";
55
+
56
+ // or just import everything (not recommended because this doesn't allow for treeshaking):
57
+
58
+ import * as UserUtils from "@sv443-network/userutils";
56
59
  ```
57
- Shameless plug: I also have a [webpack-based template for userscripts in TypeScript](https://github.com/Sv443/Userscript.ts) that you can use to get started quickly.
60
+ Shameless plug: I made a [template for userscripts in TypeScript](https://github.com/Sv443/Userscript.ts) that you can use to get started quickly. It also includes this library by default.
58
61
 
59
62
  <br>
60
63
 
@@ -79,10 +82,12 @@ If you like using this library, please consider [supporting the development ❤
79
82
  This library is written in TypeScript and contains builtin TypeScript declarations.
80
83
  The usages and examples in this readme are written in TypeScript, but the library can also be used in plain JavaScript.
81
84
 
82
- Some functions require the `@run-at` or `@grant` directives to be tweaked in the userscript header or have other requirements.
85
+ Some features require the `@run-at` or `@grant` directives to be tweaked in the userscript header or have other requirements.
83
86
  Their documentation will contain a section marked by a warning emoji (⚠️) that will go into more detail.
84
87
 
85
- If the usage contains multiple definitions of the function, each line represents an overload and you can choose which one you want to use.
88
+ Each feature has example code that can be expanded by clicking on the text "Example - click to view".
89
+
90
+ If the usage section contains multiple definitions of the function, each occurrence represents an overload and you can choose which one you want to use.
86
91
 
87
92
  <br><br>
88
93
 
@@ -124,7 +129,7 @@ This initialization function has to be called after `DOMContentLoaded` is fired
124
129
 
125
130
  Calling onSelector() before `DOMContentLoaded` is fired will not throw an error, but it also won't trigger listeners until the DOM is accessible.
126
131
 
127
- <details><summary><b>Example - click to view</b></summary>
132
+ <details><summary><h4>Example - click to view</h4></summary>
128
133
 
129
134
  ```ts
130
135
  document.addEventListener("DOMContentLoaded", initOnSelector);
@@ -155,7 +160,7 @@ Usage:
155
160
  ```ts
156
161
  initOnSelector(options?: MutationObserverInit): void
157
162
  ```
158
-
163
+
159
164
  Initializes the MutationObserver that is used by [`onSelector()`](#onselector) to check for the registered selectors whenever a DOM change occurs on the `<body>`
160
165
  By default, this only checks if elements are added or removed (at any depth).
161
166
 
@@ -169,7 +174,7 @@ You may see all options [here](https://developer.mozilla.org/en-US/docs/Web/API/
169
174
  >
170
175
  > ⚠️ Using these extra options can have a performance impact on larger sites or sites with a constantly changing DOM.
171
176
 
172
- <details><summary><b>Example - click to view</b></summary>
177
+ <details><summary><h4>Example - click to view</h4></summary>
173
178
 
174
179
  ```ts
175
180
  document.addEventListener("DOMContentLoaded", () => {
@@ -185,12 +190,15 @@ document.addEventListener("DOMContentLoaded", () => {
185
190
  <br>
186
191
 
187
192
  ### getSelectorMap()
188
- Usage: `getSelectorMap(): Map<string, OnSelectorOptions[]>`
193
+ Usage:
194
+ ```ts
195
+ getSelectorMap(): Map<string, OnSelectorOptions[]>
196
+ ```
189
197
 
190
198
  Returns a Map of all currently registered selectors and their options, including listener function.
191
199
  Since multiple listeners can be registered for the same selector, the value of the Map is an array of `OnSelectorOptions` objects.
192
200
 
193
- <details><summary><b>Example - click to view</b></summary>
201
+ <details><summary><h4>Example - click to view</h4></summary>
194
202
 
195
203
  ```ts
196
204
  document.addEventListener("DOMContentLoaded", initOnSelector);
@@ -225,12 +233,15 @@ const selectorMap = getSelectorMap();
225
233
  <br>
226
234
 
227
235
  ### getUnsafeWindow()
228
- Usage: `getUnsafeWindow(): Window`
236
+ Usage:
237
+ ```ts
238
+ getUnsafeWindow(): Window
239
+ ```
229
240
 
230
241
  Returns the unsafeWindow object or falls back to the regular window object if the `@grant unsafeWindow` is not given.
231
242
  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.
232
243
 
233
- <details><summary><b>Example - click to view</b></summary>
244
+ <details><summary><h4>Example - click to view</h4></summary>
234
245
 
235
246
  ```ts
236
247
  // trick the site into thinking the mouse was moved:
@@ -249,14 +260,17 @@ document.body.dispatchEvent(mouseEvent);
249
260
  <br>
250
261
 
251
262
  ### insertAfter()
252
- Usage: `insertAfter(beforeElement: HTMLElement, afterElement: HTMLElement): HTMLElement`
263
+ Usage:
264
+ ```ts
265
+ insertAfter(beforeElement: HTMLElement, afterElement: HTMLElement): HTMLElement
266
+ ```
253
267
 
254
268
  Inserts the element passed as `afterElement` as a sibling after the passed `beforeElement`.
255
269
  The passed `afterElement` will be returned.
256
270
 
257
271
  ⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
258
272
 
259
- <details><summary><b>Example - click to view</b></summary>
273
+ <details><summary><h4>Example - click to view</h4></summary>
260
274
 
261
275
  ```ts
262
276
  // insert a <div> as a sibling next to an element
@@ -271,14 +285,17 @@ insertAfter(beforeElement, afterElement);
271
285
  <br>
272
286
 
273
287
  ### addParent()
274
- Usage: `addParent(element: HTMLElement, newParent: HTMLElement): HTMLElement`
288
+ Usage:
289
+ ```ts
290
+ addParent(element: HTMLElement, newParent: HTMLElement): HTMLElement
291
+ ```
275
292
 
276
293
  Adds a parent element around the passed `element` and returns the new parent.
277
294
  Previously registered event listeners are kept intact.
278
295
 
279
296
  ⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
280
297
 
281
- <details><summary><b>Example - click to view</b></summary>
298
+ <details><summary><h4>Example - click to view</h4></summary>
282
299
 
283
300
  ```ts
284
301
  // add an <a> around an element
@@ -293,12 +310,15 @@ addParent(element, newParent);
293
310
  <br>
294
311
 
295
312
  ### addGlobalStyle()
296
- Usage: `addGlobalStyle(css: string): void`
313
+ Usage:
314
+ ```ts
315
+ addGlobalStyle(css: string): void
316
+ ```
297
317
 
298
318
  Adds a global style to the page in form of a `<style>` element that's inserted into the `<head>`.
299
319
  ⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
300
320
 
301
- <details><summary><b>Example - click to view</b></summary>
321
+ <details><summary><h4>Example - click to view</h4></summary>
302
322
 
303
323
  ```ts
304
324
  document.addEventListener("DOMContentLoaded", () => {
@@ -315,13 +335,16 @@ document.addEventListener("DOMContentLoaded", () => {
315
335
  <br>
316
336
 
317
337
  ### preloadImages()
318
- Usage: `preloadImages(urls: string[], rejects?: boolean): Promise<void>`
338
+ Usage:
339
+ ```ts
340
+ preloadImages(urls: string[], rejects?: boolean): Promise<void>
341
+ ```
319
342
 
320
343
  Preloads images into browser cache by creating an invisible `<img>` element for each URL passed.
321
344
  The images will be loaded in parallel and the returned Promise will only resolve once all images have been loaded.
322
345
  The resulting PromiseSettledResult array will contain the image elements if resolved, or an ErrorEvent if rejected, but only if `rejects` is set to true.
323
346
 
324
- <details><summary><b>Example - click to view</b></summary>
347
+ <details><summary><h4>Example - click to view</h4></summary>
325
348
 
326
349
  ```ts
327
350
  preloadImages([
@@ -339,7 +362,10 @@ preloadImages([
339
362
  <br>
340
363
 
341
364
  ### openInNewTab()
342
- Usage: `openInNewTab(url: string): void`
365
+ Usage:
366
+ ```ts
367
+ openInNewTab(url: string): void
368
+ ```
343
369
 
344
370
  Creates an invisible anchor with a `_blank` target and clicks it.
345
371
  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.
@@ -347,7 +373,7 @@ This function has to be run in response to a user interaction event, else the br
347
373
 
348
374
  ⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
349
375
 
350
- <details><summary><b>Example - click to view</b></summary>
376
+ <details><summary><h4>Example - click to view</h4></summary>
351
377
 
352
378
  ```ts
353
379
  document.querySelector("#my-button").addEventListener("click", () => {
@@ -360,14 +386,21 @@ document.querySelector("#my-button").addEventListener("click", () => {
360
386
  <br>
361
387
 
362
388
  ### interceptEvent()
363
- Usage: `interceptEvent(eventObject: EventTarget, eventName: string, predicate: () => boolean): void`
389
+ Usage:
390
+ ```ts
391
+ interceptEvent(
392
+ eventObject: EventTarget,
393
+ eventName: string,
394
+ predicate: () => boolean
395
+ ): void
396
+ ```
364
397
 
365
398
  Intercepts all events dispatched on the `eventObject` and prevents the listeners from being called as long as the predicate function returns a truthy value.
366
399
  Calling this function will set the `Error.stackTraceLimit` to 1000 (if it's not already higher) to ensure the stack trace is preserved.
367
400
 
368
401
  ⚠️ 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.
369
402
 
370
- <details><summary><b>Example - click to view</b></summary>
403
+ <details><summary><h4>Example - click to view</h4></summary>
371
404
 
372
405
  ```ts
373
406
  interceptEvent(document.body, "click", () => {
@@ -380,14 +413,20 @@ interceptEvent(document.body, "click", () => {
380
413
  <br>
381
414
 
382
415
  ### interceptWindowEvent()
383
- Usage: `interceptWindowEvent(eventName: string, predicate: () => boolean): void`
416
+ Usage:
417
+ ```ts
418
+ interceptWindowEvent(
419
+ eventName: string,
420
+ predicate: () => boolean
421
+ ): void
422
+ ```
384
423
 
385
424
  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.
386
425
  This is essentially the same as [`interceptEvent()`](#interceptevent), but automatically uses the `unsafeWindow` (or falls back to regular `window`).
387
426
 
388
427
  ⚠️ 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.
389
428
 
390
- <details><summary><b>Example - click to view</b></summary>
429
+ <details><summary><h4>Example - click to view</h4></summary>
391
430
 
392
431
  ```ts
393
432
  interceptWindowEvent("beforeunload", () => {
@@ -400,7 +439,10 @@ interceptWindowEvent("beforeunload", () => {
400
439
  <br>
401
440
 
402
441
  ### amplifyMedia()
403
- Usage: `amplifyMedia(mediaElement: HTMLMediaElement, multiplier?: number): AmplifyMediaResult`
442
+ Usage:
443
+ ```ts
444
+ amplifyMedia(mediaElement: HTMLMediaElement, multiplier?: number): AmplifyMediaResult
445
+ ```
404
446
 
405
447
  Amplifies the gain of a media element (like `<audio>` or `<video>`) by a given multiplier (defaults to 1.0).
406
448
  This is how you can increase the volume of a media element beyond the default maximum volume of 1.0 or 100%.
@@ -408,7 +450,7 @@ Make sure to limit the multiplier to a reasonable value ([clamp()](#clamp) is go
408
450
 
409
451
  ⚠️ This function has to be run in response to a user interaction event, else the browser will reject it because of the strict autoplay policy.
410
452
 
411
- Returns an object with the following properties:
453
+ The returned AmplifyMediaResult object has the following properties:
412
454
  | Property | Description |
413
455
  | :-- | :-- |
414
456
  | `mediaElement` | The passed media element |
@@ -418,7 +460,7 @@ Returns an object with the following properties:
418
460
  | `source` | The MediaElementSourceNode instance |
419
461
  | `gain` | The GainNode instance |
420
462
 
421
- <details><summary><b>Example - click to view</b></summary>
463
+ <details><summary><h4>Example - click to view</h4></summary>
422
464
 
423
465
  ```ts
424
466
  const audio = document.querySelector<HTMLAudioElement>("audio");
@@ -448,17 +490,24 @@ button.addEventListener("click", () => {
448
490
  ## Math:
449
491
 
450
492
  ### clamp()
451
- Usage: `clamp(num: number, min: number, max: number): number`
493
+ Usage:
494
+ ```ts
495
+ clamp(num: number, min: number, max: number): number
496
+ ```
452
497
 
453
- Clamps a number between a min and max value.
498
+ Clamps a number between a min and max boundary (inclusive).
454
499
 
455
- <details><summary><b>Example - click to view</b></summary>
500
+ <details><summary><h4>Example - click to view</h4></summary>
456
501
 
457
502
  ```ts
458
- clamp(5, 0, 10); // 5
459
- clamp(-1, 0, 10); // 0
460
- clamp(7, 0, 10); // 7
461
- clamp(Infinity, 0, 10); // 10
503
+ clamp(7, 0, 10); // 7
504
+ clamp(-1, 0, 10); // 0
505
+ clamp(5, -5, 0); // 0
506
+ clamp(99999, 0, 10); // 10
507
+
508
+ // clamp without a min or max boundary:
509
+ clamp(-99999, -Infinity, 0); // -99999
510
+ clamp(99999, 0, Infinity); // 99999
462
511
  ```
463
512
 
464
513
  </details>
@@ -466,16 +515,27 @@ clamp(Infinity, 0, 10); // 10
466
515
  <br>
467
516
 
468
517
  ### mapRange()
469
- Usage: `mapRange(value: number, range_1_min: number, range_1_max: number, range_2_min: number, range_2_max: number): number`
518
+ Usage:
519
+ ```ts
520
+ mapRange(
521
+ value: number,
522
+ range_1_min: number,
523
+ range_1_max: number,
524
+ range_2_min: number,
525
+ range_2_max: number
526
+ ): number
527
+ ```
470
528
 
471
529
  Maps a number from one range to the spot it would be in another range.
472
530
 
473
- <details><summary><b>Example - click to view</b></summary>
531
+ <details><summary><h4>Example - click to view</h4></summary>
474
532
 
475
533
  ```ts
476
534
  mapRange(5, 0, 10, 0, 100); // 50
477
535
  mapRange(5, 0, 10, 0, 50); // 25
478
- // to calculate a percentage from arbitrary values, use 0 and 100 as the second range:
536
+
537
+ // to calculate a percentage from arbitrary values, use 0 and 100 as the second range
538
+ // for example, if 4 files of a total of 13 were downloaded:
479
539
  mapRange(4, 0, 13, 0, 100); // 30.76923076923077
480
540
  ```
481
541
 
@@ -493,7 +553,7 @@ randRange(max: number): number
493
553
  Returns a random number between `min` and `max` (inclusive).
494
554
  If only one argument is passed, it will be used as the `max` value and `min` will be set to 0.
495
555
 
496
- <details><summary><b>Example - click to view</b></summary>
556
+ <details><summary><h4>Example - click to view</h4></summary>
497
557
 
498
558
  ```ts
499
559
  randRange(0, 10); // 4
@@ -507,13 +567,145 @@ randRange(10); // 7
507
567
 
508
568
  ## Misc:
509
569
 
570
+ ### ConfigManager()
571
+ Usage:
572
+ ```ts
573
+ new ConfigManager(options: ConfigManagerOptions)
574
+ ```
575
+
576
+ A class that manages a userscript's configuration that is persistently saved to and loaded from GM storage.
577
+ Also supports automatic migration of outdated data formats via provided migration functions.
578
+
579
+ ⚠️ The configuration is stored as a JSON string, so only JSON-compatible data can be used.
580
+ ⚠️ The directives `@grant GM.getValue` and `@grant GM.setValue` are required for this to work.
581
+
582
+ The options object has the following properties:
583
+ | Property | Description |
584
+ | :-- | :-- |
585
+ | `id` | A unique internal identification string for this configuration. If two ConfigManagers share the same ID, they will overwrite each other's data. Choose wisely because if it is changed, the previously saved data will not be able to be loaded anymore. |
586
+ | `defaultConfig` | The default config data to use if no data is saved in persistent storage yet. Until the data is loaded from persistent storage, this will be the data returned by `getData()`. For TypeScript, the type of the data passed here is what will be used for all other methods of the instance. |
587
+ | `formatVersion` | An incremental version of the data format. If the format of the data is changed in any way, this number should be incremented, in which case all necessary functions of the migrations dictionary will be run consecutively. Never decrement this number or skip numbers. |
588
+ | `migrations?` | (Optional) A dictionary of functions that can be used to migrate data from older versions of the configuration to newer ones. The keys of the dictionary should be the format version that the functions can migrate to, from the previous whole integer value. The values should be functions that take the data in the old format and return the data in the new format. The functions will be run in order from the oldest to the newest version. If the current format version is not in the dictionary, no migrations will be run. |
589
+
590
+ <br>
591
+
592
+ ### Methods:
593
+ `loadData(): Promise<TData>`
594
+ Asynchronously loads the configuration data from persistent storage and returns it.
595
+ If no data was saved in persistent storage before, the value of `options.defaultConfig` will be returned and written to persistent storage.
596
+ If the formatVersion of the saved data is lower than the current one and the `options.migrations` property is present, the data will be migrated to the latest format before the Promise resolves.
597
+
598
+ `getData(): TData`
599
+ Synchronously returns the current data that is stored in the internal cache.
600
+ If no data was loaded from persistent storage yet using `loadData()`, the value of `options.defaultConfig` will be returned.
601
+
602
+ `setData(data: TData): Promise<void>`
603
+ Writes the given data synchronously to the internal cache and asynchronously to persistent storage.
604
+
605
+ `saveDefaultData(): Promise<void>`
606
+ Writes the default configuration given in `options.defaultConfig` synchronously to the internal cache and asynchronously to persistent storage.
607
+
608
+ `deleteConfig(): Promise<void>`
609
+ Fully deletes the configuration from persistent storage.
610
+ The internal cache will be left untouched, so any subsequent calls to `getData()` will return the data that was last loaded.
611
+ If `loadData()` or `setData()` are called after this, the persistent storage will be populated again.
612
+
613
+ <br>
614
+
615
+ <details><summary><h4>Example - click to view</h4></summary>
616
+
617
+ ```ts
618
+ import { ConfigManager } from "@sv443-network/userutils";
619
+
620
+ interface MyConfig {
621
+ foo: string;
622
+ bar: number;
623
+ baz: string;
624
+ qux: string;
625
+ }
626
+
627
+ /** Default config data */
628
+ const defaultConfig: MyConfig = {
629
+ foo: "hello",
630
+ bar: 42,
631
+ baz: "xyz",
632
+ qux: "something",
633
+ };
634
+ /** If any properties are added to, removed from or renamed in MyConfig, increment this number */
635
+ const formatVersion = 2;
636
+ /** Functions that migrate outdated data to the latest format - make sure a function exists for every previously used formatVersion and that no numbers are skipped! */
637
+ const migrations = {
638
+ // migrate from format version 0 to 1
639
+ 1: (oldData: any) => {
640
+ return {
641
+ foo: oldData.foo,
642
+ bar: oldData.bar,
643
+ baz: "world",
644
+ };
645
+ },
646
+ // asynchronously migrate from format version 1 to 2
647
+ 2: async (oldData: any) => {
648
+ // arbitrary async operation required for the new format
649
+ const qux = JSON.parse(await (await fetch("https://api.example.org/some-data")).text());
650
+ return {
651
+ foo: oldData.foo,
652
+ bar: oldData.bar,
653
+ baz: oldData.baz,
654
+ qux,
655
+ };
656
+ },
657
+ };
658
+
659
+ const configMgr = new ConfigManager({
660
+ /** A unique ID for this configuration - choose wisely as changing it is not supported yet! */
661
+ id: "my-userscript",
662
+ /** Default / fallback configuration data */
663
+ defaultConfig,
664
+ /** The current version of the script's config data format */
665
+ formatVersion,
666
+ /** Data format migration functions */
667
+ migrations,
668
+ });
669
+
670
+ /** Entrypoint of the userscript */
671
+ async function init() {
672
+ // wait for the config to be loaded from persistent storage
673
+ // if no data was saved in persistent storage before or getData() is called before loadData(), the value of options.defaultConfig will be returned
674
+ // if the previously saved data needs to be migrated to a newer version, it will happen in this function call
675
+ const configData = await configMgr.loadData();
676
+
677
+ console.log(configData.foo); // "hello"
678
+
679
+ // update the config
680
+ configData.foo = "world";
681
+ configData.bar = 123;
682
+
683
+ // save the updated config - synchronously to the cache and asynchronously to persistent storage
684
+ configMgr.saveData(configData).then(() => {
685
+ console.log("Config saved to persistent storage!");
686
+ });
687
+
688
+ // the internal cache is updated synchronously, so the updated data can be accessed before the Promise resolves:
689
+ console.log(configMgr.getData().foo); // "world"
690
+ }
691
+
692
+ init();
693
+ ```
694
+
695
+ </details>
696
+
697
+ <br><br>
698
+
510
699
  ### autoPlural()
511
- Usage: `autoPlural(str: string, num: number | Array | NodeList): string`
700
+ Usage:
701
+ ```ts
702
+ autoPlural(str: string, num: number | Array | NodeList): string
703
+ ```
512
704
 
513
705
  Automatically pluralizes a string if the given number is not 1.
514
- If an array or NodeList is passed, the length of it will be used.
706
+ If an array or NodeList is passed, the amount of contained items will be used.
515
707
 
516
- <details><summary><b>Example - click to view</b></summary>
708
+ <details><summary><h4>Example - click to view</h4></summary>
517
709
 
518
710
  ```ts
519
711
  autoPlural("apple", 0); // "apples"
@@ -532,11 +724,14 @@ console.log(`Found ${items.length} ${autoPlural("item", items)}`); // "Found 6 i
532
724
  <br>
533
725
 
534
726
  ### pauseFor()
535
- Usage: `pauseFor(ms: number): Promise<void>`
727
+ Usage:
728
+ ```ts
729
+ pauseFor(ms: number): Promise<void>
730
+ ```
536
731
 
537
732
  Pauses async execution for a given amount of time.
538
733
 
539
- <details><summary><b>Example - click to view</b></summary>
734
+ <details><summary><h4>Example - click to view</h4></summary>
540
735
 
541
736
  ```ts
542
737
  async function run() {
@@ -551,13 +746,17 @@ async function run() {
551
746
  <br>
552
747
 
553
748
  ### debounce()
554
- Usage: `debounce(func: Function, timeout?: number): Function`
749
+ Usage:
750
+ ```ts
751
+ debounce(func: Function, timeout?: number): Function
752
+ ```
555
753
 
556
754
  Debounces a function, meaning that it will only be called once after a given amount of time.
557
755
  This is very useful for functions that are called repeatedly, like event listeners, to remove extraneous calls.
756
+ All passed properties will be passed down to the debounced function.
558
757
  The timeout will default to 300ms if left undefined.
559
758
 
560
- <details><summary><b>Example - click to view</b></summary>
759
+ <details><summary><h4>Example - click to view</h4></summary>
561
760
 
562
761
  ```ts
563
762
  window.addEventListener("resize", debounce((event) => {
@@ -581,7 +780,7 @@ fetchAdvanced(url: string, options?: {
581
780
  A wrapper around the native `fetch()` function that adds options like a timeout property.
582
781
  The timeout will default to 10 seconds if left undefined.
583
782
 
584
- <details><summary><b>Example - click to view</b></summary>
783
+ <details><summary><h4>Example - click to view</h4></summary>
585
784
 
586
785
  ```ts
587
786
  fetchAdvanced("https://api.example.org/data", {
@@ -602,12 +801,15 @@ fetchAdvanced("https://api.example.org/data", {
602
801
  ## Arrays:
603
802
 
604
803
  ### randomItem()
605
- Usage: `randomItem(array: Array): any`
804
+ Usage:
805
+ ```ts
806
+ randomItem(array: Array): any
807
+ ```
606
808
 
607
809
  Returns a random item from an array.
608
810
  Returns undefined if the array is empty.
609
811
 
610
- <details><summary><b>Example - click to view</b></summary>
812
+ <details><summary><h4>Example - click to view</h4></summary>
611
813
 
612
814
  ```ts
613
815
  randomItem(["foo", "bar", "baz"]); // "bar"
@@ -619,12 +821,15 @@ randomItem([ ]); // undefined
619
821
  <br>
620
822
 
621
823
  ### randomItemIndex()
622
- Usage: `randomItemIndex(array: Array): [item: any, index: number]`
824
+ Usage:
825
+ ```ts
826
+ randomItemIndex(array: Array): [item: any, index: number]
827
+ ```
623
828
 
624
829
  Returns a tuple of a random item and its index from an array.
625
830
  If the array is empty, it will return undefined for both values.
626
831
 
627
- <details><summary><b>Example - click to view</b></summary>
832
+ <details><summary><h4>Example - click to view</h4></summary>
628
833
 
629
834
  ```ts
630
835
  randomItemIndex(["foo", "bar", "baz"]); // ["bar", 1]
@@ -640,12 +845,15 @@ const [, index] = randomItemIndex(["foo", "bar", "baz"]);
640
845
  <br>
641
846
 
642
847
  ### takeRandomItem()
643
- Usage: `takeRandomItem(array: Array): any`
848
+ Usage:
849
+ ```ts
850
+ takeRandomItem(array: Array): any
851
+ ```
644
852
 
645
853
  Returns a random item from an array and mutates the array by removing the item.
646
854
  Returns undefined if the array is empty.
647
855
 
648
- <details><summary><b>Example - click to view</b></summary>
856
+ <details><summary><h4>Example - click to view</h4></summary>
649
857
 
650
858
  ```ts
651
859
  const arr = ["foo", "bar", "baz"];
@@ -658,12 +866,15 @@ console.log(arr); // ["foo", "baz"]
658
866
  <br>
659
867
 
660
868
  ### randomizeArray()
661
- Usage: `randomizeArray(array: Array): Array`
869
+ Usage:
870
+ ```ts
871
+ randomizeArray(array: Array): Array
872
+ ```
662
873
 
663
- Returns a copy of the array with its items in a random order.
664
- If the array is empty, the originally passed array will be returned.
874
+ Returns a copy of an array with its items in a random order.
875
+ If the array is empty, the originally passed empty array will be returned without copying.
665
876
 
666
- <details><summary><b>Example - click to view</b></summary>
877
+ <details><summary><h4>Example - click to view</h4></summary>
667
878
 
668
879
  ```ts
669
880
  randomizeArray([1, 2, 3, 4, 5, 6]); // [3, 1, 5, 2, 4, 6]