@sv443-network/userutils 1.0.0 → 1.1.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 +10 -0
- package/README.md +100 -15
- package/dist/index.js +17 -16
- package/dist/index.mjs +2 -2
- package/dist/lib/dom.d.ts +7 -2
- package/dist/lib/onSelector.d.ts +3 -3
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @sv443-network/userutils
|
|
2
2
|
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- db5cded: Added `isScrollable()` to check whether an element has a horizontal or vertical scroll bar
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 9e26464: Replaced most occurrences of `HTMLElement` in the docs with `Element` for better compatibility
|
|
12
|
+
|
|
3
13
|
## 1.0.0
|
|
4
14
|
|
|
5
15
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## UserUtils
|
|
4
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
6
|
Contains builtin TypeScript declarations. Webpack compatible and supports ESM and CJS.
|
|
6
7
|
If you like using this library, please consider [supporting the development ❤️](https://github.com/sponsors/Sv443)
|
|
7
8
|
|
|
@@ -26,6 +27,7 @@ If you like using this library, please consider [supporting the development ❤
|
|
|
26
27
|
- [interceptEvent()](#interceptevent) - conditionally intercepts events registered by `addEventListener()` on any given EventTarget object
|
|
27
28
|
- [interceptWindowEvent()](#interceptwindowevent) - conditionally intercepts events registered by `addEventListener()` on the window object
|
|
28
29
|
- [amplifyMedia()](#amplifymedia) - amplify an audio or video element's volume past the maximum of 100%
|
|
30
|
+
- [isScrollable()](#isscrollable) - check if an element has a horizontal or vertical scroll bar
|
|
29
31
|
- [Math:](#math)
|
|
30
32
|
- [clamp()](#clamp) - constrain a number between a min and max value
|
|
31
33
|
- [mapRange()](#maprange) - map a number from one range to the same spot in another range
|
|
@@ -80,14 +82,13 @@ If you like using this library, please consider [supporting the development ❤
|
|
|
80
82
|
|
|
81
83
|
## Preamble:
|
|
82
84
|
This library is written in TypeScript and contains builtin TypeScript declarations.
|
|
83
|
-
The usages and examples in this readme are written in TypeScript, but the library can also be used in plain JavaScript.
|
|
84
|
-
|
|
85
|
-
Some features require the `@run-at` or `@grant` directives to be tweaked in the userscript header or have other requirements.
|
|
86
|
-
Their documentation will contain a section marked by a warning emoji (⚠️) that will go into more detail.
|
|
87
85
|
|
|
88
86
|
Each feature has example code that can be expanded by clicking on the text "Example - click to view".
|
|
87
|
+
The usages and examples are written in TypeScript, but the library can also be used in plain JavaScript after removing the type annotations (and changing the imports if you are using CommonJS).
|
|
88
|
+
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.
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
Some features require the `@run-at` or `@grant` directives to be tweaked in the userscript header or have other requirements.
|
|
91
|
+
Their documentation will contain a section marked by a warning emoji (⚠️) that will go into more detail.
|
|
91
92
|
|
|
92
93
|
<br><br>
|
|
93
94
|
|
|
@@ -123,6 +124,7 @@ If set to `false` (default), querySelector() will be used and only the first mat
|
|
|
123
124
|
If `continuous` is set to `true`, the listener will not be deregistered after it was called once (defaults to false).
|
|
124
125
|
|
|
125
126
|
When using TypeScript, the generic `TElement` can be used to specify the type of the element(s) that the listener will return.
|
|
127
|
+
It will default to `HTMLElement` if left undefined.
|
|
126
128
|
|
|
127
129
|
⚠️ In order to use this function, [`initOnSelector()`](#initonselector) has to be called as soon as possible.
|
|
128
130
|
This initialization function has to be called after `DOMContentLoaded` is fired (or immediately if `@run-at document-end` is set).
|
|
@@ -132,6 +134,8 @@ Calling onSelector() before `DOMContentLoaded` is fired will not throw an error,
|
|
|
132
134
|
<details><summary><h4>Example - click to view</h4></summary>
|
|
133
135
|
|
|
134
136
|
```ts
|
|
137
|
+
import { initOnSelector, onSelector } from "@sv443-network/userutils";
|
|
138
|
+
|
|
135
139
|
document.addEventListener("DOMContentLoaded", initOnSelector);
|
|
136
140
|
|
|
137
141
|
// Continuously checks if `div` elements are added to the DOM, then returns all of them (even previously detected ones) in a NodeList
|
|
@@ -177,6 +181,8 @@ You may see all options [here](https://developer.mozilla.org/en-US/docs/Web/API/
|
|
|
177
181
|
<details><summary><h4>Example - click to view</h4></summary>
|
|
178
182
|
|
|
179
183
|
```ts
|
|
184
|
+
import { initOnSelector } from "@sv443-network/userutils";
|
|
185
|
+
|
|
180
186
|
document.addEventListener("DOMContentLoaded", () => {
|
|
181
187
|
initOnSelector({
|
|
182
188
|
attributes: true,
|
|
@@ -201,6 +207,8 @@ Since multiple listeners can be registered for the same selector, the value of t
|
|
|
201
207
|
<details><summary><h4>Example - click to view</h4></summary>
|
|
202
208
|
|
|
203
209
|
```ts
|
|
210
|
+
import { initOnSelector, onSelector, getSelectorMap } from "@sv443-network/userutils";
|
|
211
|
+
|
|
204
212
|
document.addEventListener("DOMContentLoaded", initOnSelector);
|
|
205
213
|
|
|
206
214
|
onSelector<HTMLDivElement>("div", {
|
|
@@ -244,6 +252,8 @@ Userscripts are sandboxed and do not have access to the regular window object, s
|
|
|
244
252
|
<details><summary><h4>Example - click to view</h4></summary>
|
|
245
253
|
|
|
246
254
|
```ts
|
|
255
|
+
import { getUnsafeWindow } from "@sv443-network/userutils";
|
|
256
|
+
|
|
247
257
|
// trick the site into thinking the mouse was moved:
|
|
248
258
|
const mouseEvent = new MouseEvent("mousemove", {
|
|
249
259
|
view: getUnsafeWindow(),
|
|
@@ -252,6 +262,7 @@ const mouseEvent = new MouseEvent("mousemove", {
|
|
|
252
262
|
movementX: 10,
|
|
253
263
|
movementY: 0,
|
|
254
264
|
});
|
|
265
|
+
|
|
255
266
|
document.body.dispatchEvent(mouseEvent);
|
|
256
267
|
```
|
|
257
268
|
|
|
@@ -262,7 +273,7 @@ document.body.dispatchEvent(mouseEvent);
|
|
|
262
273
|
### insertAfter()
|
|
263
274
|
Usage:
|
|
264
275
|
```ts
|
|
265
|
-
insertAfter(beforeElement:
|
|
276
|
+
insertAfter(beforeElement: Element, afterElement: Element): Element
|
|
266
277
|
```
|
|
267
278
|
|
|
268
279
|
Inserts the element passed as `afterElement` as a sibling after the passed `beforeElement`.
|
|
@@ -273,10 +284,13 @@ The passed `afterElement` will be returned.
|
|
|
273
284
|
<details><summary><h4>Example - click to view</h4></summary>
|
|
274
285
|
|
|
275
286
|
```ts
|
|
287
|
+
import { insertAfter } from "@sv443-network/userutils";
|
|
288
|
+
|
|
276
289
|
// insert a <div> as a sibling next to an element
|
|
277
290
|
const beforeElement = document.querySelector("#before");
|
|
278
291
|
const afterElement = document.createElement("div");
|
|
279
292
|
afterElement.innerText = "After";
|
|
293
|
+
|
|
280
294
|
insertAfter(beforeElement, afterElement);
|
|
281
295
|
```
|
|
282
296
|
|
|
@@ -287,7 +301,7 @@ insertAfter(beforeElement, afterElement);
|
|
|
287
301
|
### addParent()
|
|
288
302
|
Usage:
|
|
289
303
|
```ts
|
|
290
|
-
addParent(element:
|
|
304
|
+
addParent(element: Element, newParent: Element): Element
|
|
291
305
|
```
|
|
292
306
|
|
|
293
307
|
Adds a parent element around the passed `element` and returns the new parent.
|
|
@@ -298,10 +312,13 @@ Previously registered event listeners are kept intact.
|
|
|
298
312
|
<details><summary><h4>Example - click to view</h4></summary>
|
|
299
313
|
|
|
300
314
|
```ts
|
|
315
|
+
import { addParent } from "@sv443-network/userutils";
|
|
316
|
+
|
|
301
317
|
// add an <a> around an element
|
|
302
318
|
const element = document.querySelector("#element");
|
|
303
319
|
const newParent = document.createElement("a");
|
|
304
320
|
newParent.href = "https://example.org/";
|
|
321
|
+
|
|
305
322
|
addParent(element, newParent);
|
|
306
323
|
```
|
|
307
324
|
|
|
@@ -321,6 +338,8 @@ Adds a global style to the page in form of a `<style>` element that's inserted i
|
|
|
321
338
|
<details><summary><h4>Example - click to view</h4></summary>
|
|
322
339
|
|
|
323
340
|
```ts
|
|
341
|
+
import { addGlobalStyle } from "@sv443-network/userutils";
|
|
342
|
+
|
|
324
343
|
document.addEventListener("DOMContentLoaded", () => {
|
|
325
344
|
addGlobalStyle(`
|
|
326
345
|
body {
|
|
@@ -347,6 +366,8 @@ The resulting PromiseSettledResult array will contain the image elements if reso
|
|
|
347
366
|
<details><summary><h4>Example - click to view</h4></summary>
|
|
348
367
|
|
|
349
368
|
```ts
|
|
369
|
+
import { preloadImages } from "@sv443-network/userutils";
|
|
370
|
+
|
|
350
371
|
preloadImages([
|
|
351
372
|
"https://example.org/image1.png",
|
|
352
373
|
"https://example.org/image2.png",
|
|
@@ -376,6 +397,8 @@ This function has to be run in response to a user interaction event, else the br
|
|
|
376
397
|
<details><summary><h4>Example - click to view</h4></summary>
|
|
377
398
|
|
|
378
399
|
```ts
|
|
400
|
+
import { openInNewTab } from "@sv443-network/userutils";
|
|
401
|
+
|
|
379
402
|
document.querySelector("#my-button").addEventListener("click", () => {
|
|
380
403
|
openInNewTab("https://example.org/");
|
|
381
404
|
});
|
|
@@ -391,7 +414,7 @@ Usage:
|
|
|
391
414
|
interceptEvent(
|
|
392
415
|
eventObject: EventTarget,
|
|
393
416
|
eventName: string,
|
|
394
|
-
predicate: () => boolean
|
|
417
|
+
predicate: (event: Event) => boolean
|
|
395
418
|
): void
|
|
396
419
|
```
|
|
397
420
|
|
|
@@ -403,7 +426,10 @@ Calling this function will set the `Error.stackTraceLimit` to 1000 (if it's not
|
|
|
403
426
|
<details><summary><h4>Example - click to view</h4></summary>
|
|
404
427
|
|
|
405
428
|
```ts
|
|
406
|
-
interceptEvent
|
|
429
|
+
import { interceptEvent } from "@sv443-network/userutils";
|
|
430
|
+
|
|
431
|
+
interceptEvent(document.body, "click", (event) => {
|
|
432
|
+
console.log("Intercepting click event:", event);
|
|
407
433
|
return true; // prevent all click events on the body element
|
|
408
434
|
});
|
|
409
435
|
```
|
|
@@ -417,7 +443,7 @@ Usage:
|
|
|
417
443
|
```ts
|
|
418
444
|
interceptWindowEvent(
|
|
419
445
|
eventName: string,
|
|
420
|
-
predicate: () => boolean
|
|
446
|
+
predicate: (event: Event) => boolean
|
|
421
447
|
): void
|
|
422
448
|
```
|
|
423
449
|
|
|
@@ -425,11 +451,14 @@ Intercepts all events dispatched on the `window` object and prevents the listene
|
|
|
425
451
|
This is essentially the same as [`interceptEvent()`](#interceptevent), but automatically uses the `unsafeWindow` (or falls back to regular `window`).
|
|
426
452
|
|
|
427
453
|
⚠️ 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.
|
|
454
|
+
⚠️ In order for all events to be interceptable, the directive `@grant unsafeWindow` should be set.
|
|
428
455
|
|
|
429
456
|
<details><summary><h4>Example - click to view</h4></summary>
|
|
430
457
|
|
|
431
458
|
```ts
|
|
432
|
-
interceptWindowEvent
|
|
459
|
+
import { interceptWindowEvent } from "@sv443-network/userutils";
|
|
460
|
+
|
|
461
|
+
interceptWindowEvent("beforeunload", (event) => {
|
|
433
462
|
return true; // prevent the pesky "Are you sure you want to leave this page?" popup
|
|
434
463
|
});
|
|
435
464
|
```
|
|
@@ -463,6 +492,8 @@ The returned AmplifyMediaResult object has the following properties:
|
|
|
463
492
|
<details><summary><h4>Example - click to view</h4></summary>
|
|
464
493
|
|
|
465
494
|
```ts
|
|
495
|
+
import { amplifyMedia } from "@sv443-network/userutils";
|
|
496
|
+
|
|
466
497
|
const audio = document.querySelector<HTMLAudioElement>("audio");
|
|
467
498
|
const button = document.querySelector<HTMLButtonElement>("button");
|
|
468
499
|
|
|
@@ -485,6 +516,31 @@ button.addEventListener("click", () => {
|
|
|
485
516
|
|
|
486
517
|
</details>
|
|
487
518
|
|
|
519
|
+
<br>
|
|
520
|
+
|
|
521
|
+
### isScrollable()
|
|
522
|
+
Usage:
|
|
523
|
+
```ts
|
|
524
|
+
isScrollable(element: Element): { horizontal: boolean, vertical: boolean }
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
Checks if an element has a horizontal or vertical scroll bar.
|
|
528
|
+
This uses the computed style of the element, so it will also work if the element is hidden.
|
|
529
|
+
|
|
530
|
+
<details><summary><h4>Example - click to view</h4></summary>
|
|
531
|
+
|
|
532
|
+
```ts
|
|
533
|
+
import { isScrollable } from "@sv443-network/userutils";
|
|
534
|
+
|
|
535
|
+
const element = document.querySelector("#element");
|
|
536
|
+
const { horizontal, vertical } = isScrollable(element);
|
|
537
|
+
|
|
538
|
+
console.log("Element has a horizontal scroll bar:", horizontal);
|
|
539
|
+
console.log("Element has a vertical scroll bar:", vertical);
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
</details>
|
|
543
|
+
|
|
488
544
|
<br><br>
|
|
489
545
|
|
|
490
546
|
## Math:
|
|
@@ -500,6 +556,8 @@ Clamps a number between a min and max boundary (inclusive).
|
|
|
500
556
|
<details><summary><h4>Example - click to view</h4></summary>
|
|
501
557
|
|
|
502
558
|
```ts
|
|
559
|
+
import { clamp } from "@sv443-network/userutils";
|
|
560
|
+
|
|
503
561
|
clamp(7, 0, 10); // 7
|
|
504
562
|
clamp(-1, 0, 10); // 0
|
|
505
563
|
clamp(5, -5, 0); // 0
|
|
@@ -531,6 +589,8 @@ Maps a number from one range to the spot it would be in another range.
|
|
|
531
589
|
<details><summary><h4>Example - click to view</h4></summary>
|
|
532
590
|
|
|
533
591
|
```ts
|
|
592
|
+
import { mapRange } from "@sv443-network/userutils";
|
|
593
|
+
|
|
534
594
|
mapRange(5, 0, 10, 0, 100); // 50
|
|
535
595
|
mapRange(5, 0, 10, 0, 50); // 25
|
|
536
596
|
|
|
@@ -556,6 +616,8 @@ If only one argument is passed, it will be used as the `max` value and `min` wil
|
|
|
556
616
|
<details><summary><h4>Example - click to view</h4></summary>
|
|
557
617
|
|
|
558
618
|
```ts
|
|
619
|
+
import { randRange } from "@sv443-network/userutils";
|
|
620
|
+
|
|
559
621
|
randRange(0, 10); // 4
|
|
560
622
|
randRange(10, 20); // 17
|
|
561
623
|
randRange(10); // 7
|
|
@@ -609,6 +671,7 @@ Writes the default configuration given in `options.defaultConfig` synchronously
|
|
|
609
671
|
Fully deletes the configuration from persistent storage.
|
|
610
672
|
The internal cache will be left untouched, so any subsequent calls to `getData()` will return the data that was last loaded.
|
|
611
673
|
If `loadData()` or `setData()` are called after this, the persistent storage will be populated again.
|
|
674
|
+
If you want to use this method, the additional directive `@grant GM.deleteValue` is required.
|
|
612
675
|
|
|
613
676
|
<br>
|
|
614
677
|
|
|
@@ -708,6 +771,8 @@ If an array or NodeList is passed, the amount of contained items will be used.
|
|
|
708
771
|
<details><summary><h4>Example - click to view</h4></summary>
|
|
709
772
|
|
|
710
773
|
```ts
|
|
774
|
+
import { autoPlural } from "@sv443-network/userutils";
|
|
775
|
+
|
|
711
776
|
autoPlural("apple", 0); // "apples"
|
|
712
777
|
autoPlural("apple", 1); // "apple"
|
|
713
778
|
autoPlural("apple", 2); // "apples"
|
|
@@ -734,6 +799,8 @@ Pauses async execution for a given amount of time.
|
|
|
734
799
|
<details><summary><h4>Example - click to view</h4></summary>
|
|
735
800
|
|
|
736
801
|
```ts
|
|
802
|
+
import { pauseFor } from "@sv443-network/userutils";
|
|
803
|
+
|
|
737
804
|
async function run() {
|
|
738
805
|
console.log("Hello");
|
|
739
806
|
await pauseFor(3000); // waits for 3 seconds
|
|
@@ -759,6 +826,8 @@ The timeout will default to 300ms if left undefined.
|
|
|
759
826
|
<details><summary><h4>Example - click to view</h4></summary>
|
|
760
827
|
|
|
761
828
|
```ts
|
|
829
|
+
import { debounce } from "@sv443-network/userutils";
|
|
830
|
+
|
|
762
831
|
window.addEventListener("resize", debounce((event) => {
|
|
763
832
|
console.log("Window was resized:", event);
|
|
764
833
|
}, 500)); // 500ms timeout
|
|
@@ -783,7 +852,9 @@ The timeout will default to 10 seconds if left undefined.
|
|
|
783
852
|
<details><summary><h4>Example - click to view</h4></summary>
|
|
784
853
|
|
|
785
854
|
```ts
|
|
786
|
-
fetchAdvanced
|
|
855
|
+
import { fetchAdvanced } from "@sv443-network/userutils";
|
|
856
|
+
|
|
857
|
+
fetchAdvanced("https://jokeapi.dev/joke/Any?safe-mode", {
|
|
787
858
|
timeout: 5000,
|
|
788
859
|
// also accepts any other fetch options like headers:
|
|
789
860
|
headers: {
|
|
@@ -812,6 +883,8 @@ Returns undefined if the array is empty.
|
|
|
812
883
|
<details><summary><h4>Example - click to view</h4></summary>
|
|
813
884
|
|
|
814
885
|
```ts
|
|
886
|
+
import { randomItem } from "@sv443-network/userutils";
|
|
887
|
+
|
|
815
888
|
randomItem(["foo", "bar", "baz"]); // "bar"
|
|
816
889
|
randomItem([ ]); // undefined
|
|
817
890
|
```
|
|
@@ -832,12 +905,15 @@ If the array is empty, it will return undefined for both values.
|
|
|
832
905
|
<details><summary><h4>Example - click to view</h4></summary>
|
|
833
906
|
|
|
834
907
|
```ts
|
|
908
|
+
import { randomItemIndex } from "@sv443-network/userutils";
|
|
909
|
+
|
|
835
910
|
randomItemIndex(["foo", "bar", "baz"]); // ["bar", 1]
|
|
836
911
|
randomItemIndex([ ]); // [undefined, undefined]
|
|
912
|
+
|
|
837
913
|
// using array destructuring:
|
|
838
|
-
const [item, index] = randomItemIndex(["foo", "bar", "baz"]);
|
|
914
|
+
const [item, index] = randomItemIndex(["foo", "bar", "baz"]); // ["bar", 1]
|
|
839
915
|
// or if you only want the index:
|
|
840
|
-
const [, index] = randomItemIndex(["foo", "bar", "baz"]);
|
|
916
|
+
const [, index] = randomItemIndex(["foo", "bar", "baz"]); // 1
|
|
841
917
|
```
|
|
842
918
|
|
|
843
919
|
</details>
|
|
@@ -856,6 +932,8 @@ Returns undefined if the array is empty.
|
|
|
856
932
|
<details><summary><h4>Example - click to view</h4></summary>
|
|
857
933
|
|
|
858
934
|
```ts
|
|
935
|
+
import { takeRandomItem } from "@sv443-network/userutils";
|
|
936
|
+
|
|
859
937
|
const arr = ["foo", "bar", "baz"];
|
|
860
938
|
takeRandomItem(arr); // "bar"
|
|
861
939
|
console.log(arr); // ["foo", "baz"]
|
|
@@ -877,7 +955,14 @@ If the array is empty, the originally passed empty array will be returned withou
|
|
|
877
955
|
<details><summary><h4>Example - click to view</h4></summary>
|
|
878
956
|
|
|
879
957
|
```ts
|
|
880
|
-
|
|
958
|
+
import { randomizeArray } from "@sv443-network/userutils";
|
|
959
|
+
|
|
960
|
+
const foo = [1, 2, 3, 4, 5, 6];
|
|
961
|
+
|
|
962
|
+
console.log(randomizeArray(foo)); // [3, 1, 5, 2, 4, 6]
|
|
963
|
+
console.log(randomizeArray(foo)); // [4, 5, 2, 1, 6, 3]
|
|
964
|
+
|
|
965
|
+
console.log(foo); // [1, 2, 3, 4, 5, 6] - original array is not mutated
|
|
881
966
|
```
|
|
882
967
|
|
|
883
968
|
</details>
|
package/dist/index.js
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var v=Object.defineProperty,x=Object.defineProperties;var w=Object.getOwnPropertyDescriptors;var h=Object.getOwnPropertySymbols;var
|
|
3
|
+
var v=Object.defineProperty,x=Object.defineProperties;var w=Object.getOwnPropertyDescriptors;var h=Object.getOwnPropertySymbols;var O=Object.prototype.hasOwnProperty,M=Object.prototype.propertyIsEnumerable;var p=(t,e,n)=>e in t?v(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,f=(t,e)=>{for(var n in e||(e={}))O.call(e,n)&&p(t,n,e[n]);if(h)for(var n of h(e))M.call(e,n)&&p(t,n,e[n]);return t},b=(t,e)=>x(t,w(e));var m=(t,e,n)=>(p(t,typeof e!="symbol"?e+"":e,n),n);var c=(t,e,n)=>new Promise((r,i)=>{var o=s=>{try{u(n.next(s));}catch(l){i(l);}},a=s=>{try{u(n.throw(s));}catch(l){i(l);}},u=s=>s.done?r(s.value):Promise.resolve(s.value).then(o,a);u((n=n.apply(t,e)).next());});function D(t,e,n){return Math.max(Math.min(t,n),e)}function L(t,e,n,r,i){return Number(e)===0&&Number(r)===0?t*(i/n):(t-e)*((i-r)/(n-e))+r}function g(...t){let e,n;if(typeof t[0]=="number"&&typeof t[1]=="number")[e,n]=t;else if(typeof t[0]=="number"&&typeof t[1]!="number")e=0,n=t[0];else throw new TypeError(`Wrong parameter(s) provided - expected: "number" and "number|undefined", got: "${typeof t[0]}" and "${typeof t[1]}"`);if(e=Number(e),n=Number(n),isNaN(e)||isNaN(n))throw new TypeError(`Parameters "min" and "max" can't be NaN`);if(e>n)throw new TypeError(`Parameter "min" can't be bigger than "max"`);return Math.floor(Math.random()*(n-e+1))+e}function V(t){return T(t)[0]}function T(t){if(t.length===0)return [void 0,void 0];let e=g(t.length-1);return [t[e],e]}function $(t){let[e,n]=T(t);if(n!==void 0)return t.splice(n,1),e}function k(t){let e=[...t];if(t.length===0)return t;for(let n=e.length-1;n>0;n--){let r=Math.floor(g(0,1e4)/1e4*(n+1));[e[n],e[r]]=[e[r],e[n]];}return e}var y=class{constructor(e){m(this,"id");m(this,"formatVersion");m(this,"defaultConfig");m(this,"cachedConfig");m(this,"migrations");this.id=e.id,this.formatVersion=e.formatVersion,this.defaultConfig=e.defaultConfig,this.cachedConfig=e.defaultConfig,this.migrations=e.migrations;}loadData(){return c(this,null,function*(){try{let e=yield GM.getValue(`_uucfg-${this.id}`,this.defaultConfig),n=Number(yield GM.getValue(`_uucfgver-${this.id}`));if(typeof e!="string")return yield this.saveDefaultData(),this.defaultConfig;isNaN(n)&&(yield GM.setValue(`_uucfgver-${this.id}`,n=this.formatVersion));let r=JSON.parse(e);return n<this.formatVersion&&this.migrations&&(r=yield this.runMigrations(r,n)),this.cachedConfig=typeof r=="object"?r:void 0}catch(e){return yield this.saveDefaultData(),this.defaultConfig}})}getData(){return this.deepCopy(this.cachedConfig)}setData(e){return this.cachedConfig=e,new Promise(n=>c(this,null,function*(){yield Promise.allSettled([GM.setValue(`_uucfg-${this.id}`,JSON.stringify(e)),GM.setValue(`_uucfgver-${this.id}`,this.formatVersion)]),n();}))}saveDefaultData(){return c(this,null,function*(){return this.cachedConfig=this.defaultConfig,new Promise(e=>c(this,null,function*(){yield Promise.allSettled([GM.setValue(`_uucfg-${this.id}`,JSON.stringify(this.defaultConfig)),GM.setValue(`_uucfgver-${this.id}`,this.formatVersion)]),e();}))})}deleteConfig(){return c(this,null,function*(){yield Promise.allSettled([GM.deleteValue(`_uucfg-${this.id}`),GM.deleteValue(`_uucfgver-${this.id}`)]);})}runMigrations(e,n){return c(this,null,function*(){if(!this.migrations)return e;let r=e,i=Object.entries(this.migrations).sort(([a],[u])=>Number(a)-Number(u)),o=n;for(let[a,u]of i){let s=Number(a);if(n<this.formatVersion&&n<s)try{let l=u(r);r=l instanceof Promise?yield l:l,o=n=s;}catch(l){console.error(`Error while running migration function for format version ${a}:`,l);}}return yield Promise.allSettled([GM.setValue(`_uucfg-${this.id}`,JSON.stringify(r)),GM.setValue(`_uucfgver-${this.id}`,o)]),r})}deepCopy(e){return JSON.parse(JSON.stringify(e))}};function S(){try{return unsafeWindow}catch(t){return window}}function _(t,e){var n;return (n=t.parentNode)==null||n.insertBefore(e,t.nextSibling),e}function j(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 W(t){let e=document.createElement("style");e.innerHTML=t,document.head.appendChild(e);}function F(t,e=!1){let n=t.map(r=>new Promise((i,o)=>{let a=new Image;a.src=r,a.addEventListener("load",()=>i(a)),a.addEventListener("error",u=>e&&o(u));}));return Promise.allSettled(n)}function H(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 C(t,e,n){typeof Error.stackTraceLimit=="number"&&Error.stackTraceLimit<1e3&&(Error.stackTraceLimit=1e3),function(r){t.__proto__.addEventListener=function(...i){var a,u;let o=typeof i[1]=="function"?i[1]:(u=(a=i[1])==null?void 0:a.handleEvent)!=null?u:()=>{};i[1]=function(...s){if(!(i[0]===e&&n(Array.isArray(s)?s[0]:s)))return o.apply(this,s)},r.apply(this,i);};}(t.__proto__.addEventListener);}function J(t,e){return C(S(),t,e)}function q(t,e=1){let n=new(window.AudioContext||window.webkitAudioContext),r={mediaElement:t,amplify:i=>{r.gain.gain.value=i;},getAmpLevel:()=>r.gain.gain.value,context:n,source:n.createMediaElementSource(t),gain:n.createGain()};return r.source.connect(r.gain),r.gain.connect(n.destination),r.amplify(e),r}function K(t){let{overflowX:e,overflowY:n}=getComputedStyle(t);return {vertical:(n==="scroll"||n==="auto")&&t.scrollHeight>t.clientHeight,horizontal:(e==="scroll"||e==="auto")&&t.scrollWidth>t.clientWidth}}function B(t,e){return (Array.isArray(e)||e instanceof NodeList)&&(e=e.length),`${t}${e===1?"":"s"}`}function U(t){return new Promise(e=>{setTimeout(()=>e(),t);})}function X(t,e=300){let n;return function(...r){clearTimeout(n),n=setTimeout(()=>t.apply(this,r),e);}}function Y(n){return c(this,arguments,function*(t,e={}){let{timeout:r=1e4}=e,i=new AbortController,o=setTimeout(()=>i.abort(),r),a=yield fetch(t,b(f({},e),{signal:i.signal}));return clearTimeout(o),a})}var d=new Map;function ee(t,e){let n=[];d.has(t)&&(n=d.get(t)),n.push(e),d.set(t,n),E(t,n);}function te(t){return d.delete(t)}function E(t,e){let n=[];if(e.forEach((r,i)=>{try{let o=r.all?document.querySelectorAll(t):document.querySelector(t);(o!==null&&o instanceof NodeList&&o.length>0||o!==null)&&(r.listener(o),r.continuous||n.push(i));}catch(o){console.error(`Couldn't call listener for selector '${t}'`,o);}}),n.length>0){let r=e.filter((i,o)=>!n.includes(o));r.length===0?d.delete(t):d.set(t,r);}}function ne(t={}){new MutationObserver(()=>{for(let[n,r]of d.entries())E(n,r);}).observe(document.body,f({subtree:!0,childList:!0},t));}function re(){return d}
|
|
4
4
|
|
|
5
5
|
exports.ConfigManager = y;
|
|
6
|
-
exports.addGlobalStyle =
|
|
7
|
-
exports.addParent =
|
|
8
|
-
exports.amplifyMedia =
|
|
9
|
-
exports.autoPlural =
|
|
6
|
+
exports.addGlobalStyle = W;
|
|
7
|
+
exports.addParent = j;
|
|
8
|
+
exports.amplifyMedia = q;
|
|
9
|
+
exports.autoPlural = B;
|
|
10
10
|
exports.clamp = D;
|
|
11
|
-
exports.debounce =
|
|
12
|
-
exports.fetchAdvanced =
|
|
13
|
-
exports.getSelectorMap =
|
|
11
|
+
exports.debounce = X;
|
|
12
|
+
exports.fetchAdvanced = Y;
|
|
13
|
+
exports.getSelectorMap = re;
|
|
14
14
|
exports.getUnsafeWindow = S;
|
|
15
|
-
exports.initOnSelector =
|
|
16
|
-
exports.insertAfter =
|
|
17
|
-
exports.interceptEvent =
|
|
18
|
-
exports.interceptWindowEvent =
|
|
15
|
+
exports.initOnSelector = ne;
|
|
16
|
+
exports.insertAfter = _;
|
|
17
|
+
exports.interceptEvent = C;
|
|
18
|
+
exports.interceptWindowEvent = J;
|
|
19
|
+
exports.isScrollable = K;
|
|
19
20
|
exports.mapRange = L;
|
|
20
|
-
exports.onSelector =
|
|
21
|
-
exports.openInNewTab =
|
|
22
|
-
exports.pauseFor =
|
|
21
|
+
exports.onSelector = ee;
|
|
22
|
+
exports.openInNewTab = H;
|
|
23
|
+
exports.pauseFor = U;
|
|
23
24
|
exports.preloadImages = F;
|
|
24
25
|
exports.randRange = g;
|
|
25
26
|
exports.randomItem = V;
|
|
26
27
|
exports.randomItemIndex = T;
|
|
27
28
|
exports.randomizeArray = k;
|
|
28
|
-
exports.removeOnSelector =
|
|
29
|
+
exports.removeOnSelector = te;
|
|
29
30
|
exports.takeRandomItem = $;
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var v=Object.defineProperty,x=Object.defineProperties;var w=Object.getOwnPropertyDescriptors;var h=Object.getOwnPropertySymbols;var
|
|
1
|
+
var v=Object.defineProperty,x=Object.defineProperties;var w=Object.getOwnPropertyDescriptors;var h=Object.getOwnPropertySymbols;var O=Object.prototype.hasOwnProperty,M=Object.prototype.propertyIsEnumerable;var p=(t,e,n)=>e in t?v(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,f=(t,e)=>{for(var n in e||(e={}))O.call(e,n)&&p(t,n,e[n]);if(h)for(var n of h(e))M.call(e,n)&&p(t,n,e[n]);return t},b=(t,e)=>x(t,w(e));var m=(t,e,n)=>(p(t,typeof e!="symbol"?e+"":e,n),n);var c=(t,e,n)=>new Promise((r,i)=>{var o=s=>{try{u(n.next(s));}catch(l){i(l);}},a=s=>{try{u(n.throw(s));}catch(l){i(l);}},u=s=>s.done?r(s.value):Promise.resolve(s.value).then(o,a);u((n=n.apply(t,e)).next());});function D(t,e,n){return Math.max(Math.min(t,n),e)}function L(t,e,n,r,i){return Number(e)===0&&Number(r)===0?t*(i/n):(t-e)*((i-r)/(n-e))+r}function g(...t){let e,n;if(typeof t[0]=="number"&&typeof t[1]=="number")[e,n]=t;else if(typeof t[0]=="number"&&typeof t[1]!="number")e=0,n=t[0];else throw new TypeError(`Wrong parameter(s) provided - expected: "number" and "number|undefined", got: "${typeof t[0]}" and "${typeof t[1]}"`);if(e=Number(e),n=Number(n),isNaN(e)||isNaN(n))throw new TypeError(`Parameters "min" and "max" can't be NaN`);if(e>n)throw new TypeError(`Parameter "min" can't be bigger than "max"`);return Math.floor(Math.random()*(n-e+1))+e}function V(t){return T(t)[0]}function T(t){if(t.length===0)return [void 0,void 0];let e=g(t.length-1);return [t[e],e]}function $(t){let[e,n]=T(t);if(n!==void 0)return t.splice(n,1),e}function k(t){let e=[...t];if(t.length===0)return t;for(let n=e.length-1;n>0;n--){let r=Math.floor(g(0,1e4)/1e4*(n+1));[e[n],e[r]]=[e[r],e[n]];}return e}var y=class{constructor(e){m(this,"id");m(this,"formatVersion");m(this,"defaultConfig");m(this,"cachedConfig");m(this,"migrations");this.id=e.id,this.formatVersion=e.formatVersion,this.defaultConfig=e.defaultConfig,this.cachedConfig=e.defaultConfig,this.migrations=e.migrations;}loadData(){return c(this,null,function*(){try{let e=yield GM.getValue(`_uucfg-${this.id}`,this.defaultConfig),n=Number(yield GM.getValue(`_uucfgver-${this.id}`));if(typeof e!="string")return yield this.saveDefaultData(),this.defaultConfig;isNaN(n)&&(yield GM.setValue(`_uucfgver-${this.id}`,n=this.formatVersion));let r=JSON.parse(e);return n<this.formatVersion&&this.migrations&&(r=yield this.runMigrations(r,n)),this.cachedConfig=typeof r=="object"?r:void 0}catch(e){return yield this.saveDefaultData(),this.defaultConfig}})}getData(){return this.deepCopy(this.cachedConfig)}setData(e){return this.cachedConfig=e,new Promise(n=>c(this,null,function*(){yield Promise.allSettled([GM.setValue(`_uucfg-${this.id}`,JSON.stringify(e)),GM.setValue(`_uucfgver-${this.id}`,this.formatVersion)]),n();}))}saveDefaultData(){return c(this,null,function*(){return this.cachedConfig=this.defaultConfig,new Promise(e=>c(this,null,function*(){yield Promise.allSettled([GM.setValue(`_uucfg-${this.id}`,JSON.stringify(this.defaultConfig)),GM.setValue(`_uucfgver-${this.id}`,this.formatVersion)]),e();}))})}deleteConfig(){return c(this,null,function*(){yield Promise.allSettled([GM.deleteValue(`_uucfg-${this.id}`),GM.deleteValue(`_uucfgver-${this.id}`)]);})}runMigrations(e,n){return c(this,null,function*(){if(!this.migrations)return e;let r=e,i=Object.entries(this.migrations).sort(([a],[u])=>Number(a)-Number(u)),o=n;for(let[a,u]of i){let s=Number(a);if(n<this.formatVersion&&n<s)try{let l=u(r);r=l instanceof Promise?yield l:l,o=n=s;}catch(l){console.error(`Error while running migration function for format version ${a}:`,l);}}return yield Promise.allSettled([GM.setValue(`_uucfg-${this.id}`,JSON.stringify(r)),GM.setValue(`_uucfgver-${this.id}`,o)]),r})}deepCopy(e){return JSON.parse(JSON.stringify(e))}};function S(){try{return unsafeWindow}catch(t){return window}}function _(t,e){var n;return (n=t.parentNode)==null||n.insertBefore(e,t.nextSibling),e}function j(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 W(t){let e=document.createElement("style");e.innerHTML=t,document.head.appendChild(e);}function F(t,e=!1){let n=t.map(r=>new Promise((i,o)=>{let a=new Image;a.src=r,a.addEventListener("load",()=>i(a)),a.addEventListener("error",u=>e&&o(u));}));return Promise.allSettled(n)}function H(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 C(t,e,n){typeof Error.stackTraceLimit=="number"&&Error.stackTraceLimit<1e3&&(Error.stackTraceLimit=1e3),function(r){t.__proto__.addEventListener=function(...i){var a,u;let o=typeof i[1]=="function"?i[1]:(u=(a=i[1])==null?void 0:a.handleEvent)!=null?u:()=>{};i[1]=function(...s){if(!(i[0]===e&&n(Array.isArray(s)?s[0]:s)))return o.apply(this,s)},r.apply(this,i);};}(t.__proto__.addEventListener);}function J(t,e){return C(S(),t,e)}function q(t,e=1){let n=new(window.AudioContext||window.webkitAudioContext),r={mediaElement:t,amplify:i=>{r.gain.gain.value=i;},getAmpLevel:()=>r.gain.gain.value,context:n,source:n.createMediaElementSource(t),gain:n.createGain()};return r.source.connect(r.gain),r.gain.connect(n.destination),r.amplify(e),r}function K(t){let{overflowX:e,overflowY:n}=getComputedStyle(t);return {vertical:(n==="scroll"||n==="auto")&&t.scrollHeight>t.clientHeight,horizontal:(e==="scroll"||e==="auto")&&t.scrollWidth>t.clientWidth}}function B(t,e){return (Array.isArray(e)||e instanceof NodeList)&&(e=e.length),`${t}${e===1?"":"s"}`}function U(t){return new Promise(e=>{setTimeout(()=>e(),t);})}function X(t,e=300){let n;return function(...r){clearTimeout(n),n=setTimeout(()=>t.apply(this,r),e);}}function Y(n){return c(this,arguments,function*(t,e={}){let{timeout:r=1e4}=e,i=new AbortController,o=setTimeout(()=>i.abort(),r),a=yield fetch(t,b(f({},e),{signal:i.signal}));return clearTimeout(o),a})}var d=new Map;function ee(t,e){let n=[];d.has(t)&&(n=d.get(t)),n.push(e),d.set(t,n),E(t,n);}function te(t){return d.delete(t)}function E(t,e){let n=[];if(e.forEach((r,i)=>{try{let o=r.all?document.querySelectorAll(t):document.querySelector(t);(o!==null&&o instanceof NodeList&&o.length>0||o!==null)&&(r.listener(o),r.continuous||n.push(i));}catch(o){console.error(`Couldn't call listener for selector '${t}'`,o);}}),n.length>0){let r=e.filter((i,o)=>!n.includes(o));r.length===0?d.delete(t):d.set(t,r);}}function ne(t={}){new MutationObserver(()=>{for(let[n,r]of d.entries())E(n,r);}).observe(document.body,f({subtree:!0,childList:!0},t));}function re(){return d}
|
|
2
2
|
|
|
3
|
-
export { y as ConfigManager,
|
|
3
|
+
export { y as ConfigManager, W as addGlobalStyle, j as addParent, q as amplifyMedia, B as autoPlural, D as clamp, X as debounce, Y as fetchAdvanced, re as getSelectorMap, S as getUnsafeWindow, ne as initOnSelector, _ as insertAfter, C as interceptEvent, J as interceptWindowEvent, K as isScrollable, L as mapRange, ee as onSelector, H as openInNewTab, U as pauseFor, F as preloadImages, g as randRange, V as randomItem, T as randomItemIndex, k as randomizeArray, te as removeOnSelector, $ as takeRandomItem };
|
package/dist/lib/dom.d.ts
CHANGED
|
@@ -6,12 +6,12 @@ export declare function getUnsafeWindow(): Window;
|
|
|
6
6
|
* Inserts `afterElement` as a sibling just after the provided `beforeElement`
|
|
7
7
|
* @returns Returns the `afterElement`
|
|
8
8
|
*/
|
|
9
|
-
export declare function insertAfter(beforeElement:
|
|
9
|
+
export declare function insertAfter(beforeElement: Element, afterElement: Element): Element;
|
|
10
10
|
/**
|
|
11
11
|
* Adds a parent container around the provided element
|
|
12
12
|
* @returns Returns the new parent element
|
|
13
13
|
*/
|
|
14
|
-
export declare function addParent(element:
|
|
14
|
+
export declare function addParent(element: Element, newParent: Element): Element;
|
|
15
15
|
/**
|
|
16
16
|
* Adds global CSS style in the form of a `<style>` element in the document's `<head>`
|
|
17
17
|
* This needs to be run after the `DOMContentLoaded` event has fired on the document object (or instantly if `@run-at document-end` is used).
|
|
@@ -67,3 +67,8 @@ export declare function amplifyMedia<TElem extends HTMLMediaElement>(mediaElemen
|
|
|
67
67
|
source: MediaElementAudioSourceNode;
|
|
68
68
|
gain: GainNode;
|
|
69
69
|
};
|
|
70
|
+
/** Checks if an element is scrollable in the horizontal and vertical directions */
|
|
71
|
+
export declare function isScrollable(element: Element): {
|
|
72
|
+
vertical: boolean;
|
|
73
|
+
horizontal: boolean;
|
|
74
|
+
};
|
package/dist/lib/onSelector.d.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/** Options for the `onSelector()` function */
|
|
2
2
|
export type OnSelectorOpts<TElem extends Element = HTMLElement> = SelectorOptsOne<TElem> | SelectorOptsAll<TElem>;
|
|
3
|
-
type SelectorOptsOne<TElem extends Element> =
|
|
3
|
+
type SelectorOptsOne<TElem extends Element> = SelectorOptsCommon & {
|
|
4
4
|
/** Whether to use `querySelectorAll()` instead - default is false */
|
|
5
5
|
all?: false;
|
|
6
6
|
/** Gets called whenever the selector was found in the DOM */
|
|
7
7
|
listener: (element: TElem) => void;
|
|
8
8
|
};
|
|
9
|
-
type SelectorOptsAll<TElem extends Element> =
|
|
9
|
+
type SelectorOptsAll<TElem extends Element> = SelectorOptsCommon & {
|
|
10
10
|
/** Whether to use `querySelectorAll()` instead - default is false */
|
|
11
11
|
all: true;
|
|
12
12
|
/** Gets called whenever the selector was found in the DOM */
|
|
13
13
|
listener: (elements: NodeListOf<TElem>) => void;
|
|
14
14
|
};
|
|
15
|
-
type
|
|
15
|
+
type SelectorOptsCommon = {
|
|
16
16
|
/** Whether to call the listener continuously instead of once - default is false */
|
|
17
17
|
continuous?: boolean;
|
|
18
18
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sv443-network/userutils",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, modify the DOM more easily and more
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "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
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
7
|
"types": "dist/lib/index.d.ts",
|
|
@@ -48,7 +48,8 @@
|
|
|
48
48
|
"files": [
|
|
49
49
|
"/dist/index.js",
|
|
50
50
|
"/dist/index.mjs",
|
|
51
|
-
"/dist/
|
|
51
|
+
"/dist/index.global.js",
|
|
52
|
+
"/dist/lib/**.d.ts",
|
|
52
53
|
"/package.json",
|
|
53
54
|
"/README.md",
|
|
54
55
|
"/CHANGELOG.md",
|