@sv443-network/userutils 7.1.0 → 7.2.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 +11 -0
- package/README.md +185 -74
- package/dist/index.global.js +204 -190
- package/dist/index.js +203 -190
- package/dist/lib/Dialog.d.ts +6 -33
- package/dist/lib/NanoEmitter.d.ts +2 -2
- package/dist/lib/colors.d.ts +18 -0
- package/dist/lib/crypto.d.ts +24 -0
- package/dist/lib/dom.d.ts +8 -0
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/misc.d.ts +7 -31
- package/dist/lib/types.d.ts +10 -0
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<div style="text-align: center;" align="center">
|
|
2
2
|
|
|
3
|
-
<!-- #
|
|
3
|
+
<!-- #region Description -->
|
|
4
4
|
## UserUtils
|
|
5
5
|
Lightweight library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, create persistent & synchronous data stores, modify the DOM more easily and more.
|
|
6
6
|
|
|
7
|
-
Contains builtin TypeScript declarations.
|
|
7
|
+
Contains builtin TypeScript declarations. Supports ESM and CJS imports via a bundler and UMD / global declaration via `@require`.
|
|
8
8
|
If you like using this library, please consider [supporting the development ❤️](https://github.com/sponsors/Sv443)
|
|
9
9
|
|
|
10
10
|
<br>
|
|
@@ -19,7 +19,7 @@ View the documentation of previous major releases:
|
|
|
19
19
|
</div>
|
|
20
20
|
<br>
|
|
21
21
|
|
|
22
|
-
<!-- #
|
|
22
|
+
<!-- #region Table of Contents -->
|
|
23
23
|
## Table of Contents:
|
|
24
24
|
- [**Installation**](#installation)
|
|
25
25
|
- [**Preamble** (info about the documentation)](#preamble)
|
|
@@ -37,6 +37,7 @@ View the documentation of previous major releases:
|
|
|
37
37
|
- [`isScrollable()`](#isscrollable) - check if an element has a horizontal or vertical scroll bar
|
|
38
38
|
- [`observeElementProp()`](#observeelementprop) - observe changes to an element's property that can't be observed with MutationObserver
|
|
39
39
|
- [`getSiblingsFrame()`](#getsiblingsframe) - returns a frame of an element's siblings, with a given alignment and size
|
|
40
|
+
- [`setInnerHtmlUnsafe()`](#setinnerhtmlunsafe) - set the innerHTML of an element using a [Trusted Types policy](https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API) without sanitizing or escaping it
|
|
40
41
|
- [**Math:**](#math)
|
|
41
42
|
- [`clamp()`](#clamp) - constrain a number between a min and max value
|
|
42
43
|
- [`mapRange()`](#maprange) - map a number from one range to the same spot in another range
|
|
@@ -65,6 +66,11 @@ View the documentation of previous major releases:
|
|
|
65
66
|
- [`tr.addLanguage()`](#traddlanguage) - add a language and its translations
|
|
66
67
|
- [`tr.setLanguage()`](#trsetlanguage) - set the currently active language for translations
|
|
67
68
|
- [`tr.getLanguage()`](#trgetlanguage) - returns the currently active language
|
|
69
|
+
- [**Colors:**](#colors)
|
|
70
|
+
- [`hexToRgb()`](#hextorgb) - convert a hex color string to an RGB number tuple
|
|
71
|
+
- [`rgbToHex()`](#rgbtohex) - convert RGB numbers to a hex color string
|
|
72
|
+
- [`lightenColor()`](#lightencolor) - lighten a CSS color string (hex, rgb or rgba) by a given percentage
|
|
73
|
+
- [`darkenColor()`](#darkencolor) - darken a CSS color string (hex, rgb or rgba) by a given percentage
|
|
68
74
|
- [**Utility types for TypeScript:**](#utility-types)
|
|
69
75
|
- [`Stringifiable`](#stringifiable) - any value that is a string or can be converted to one (implicitly or explicitly)
|
|
70
76
|
- [`NonEmptyArray`](#nonemptyarray) - any array that should have at least one item
|
|
@@ -73,12 +79,15 @@ View the documentation of previous major releases:
|
|
|
73
79
|
|
|
74
80
|
<br><br>
|
|
75
81
|
|
|
76
|
-
<!-- #
|
|
82
|
+
<!-- #region Installation -->
|
|
77
83
|
## Installation:
|
|
78
|
-
|
|
84
|
+
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.
|
|
85
|
+
|
|
86
|
+
- If you are using a bundler (like webpack, rollup, vite, etc.), you can install this package using npm:
|
|
79
87
|
```
|
|
80
88
|
npm i @sv443-network/userutils
|
|
81
89
|
```
|
|
90
|
+
<sup>For other package managers, check out the respective install command on the [JavaScript Registry](https://jsr.io/@sv443-network/userutils)</sup>
|
|
82
91
|
Then, import it in your script as usual:
|
|
83
92
|
```ts
|
|
84
93
|
import { addGlobalStyle } from "@sv443-network/userutils";
|
|
@@ -87,18 +96,17 @@ View the documentation of previous major releases:
|
|
|
87
96
|
|
|
88
97
|
import * as UserUtils from "@sv443-network/userutils";
|
|
89
98
|
```
|
|
90
|
-
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.
|
|
91
99
|
|
|
92
100
|
<br>
|
|
93
101
|
|
|
94
|
-
- If you are not using a bundler, you can include the latest release by adding one of these directives to the userscript header, depending on your preferred CDN:
|
|
102
|
+
- If you are not using a bundler or want to reduce the size of your userscript, you can include the latest release by adding one of these directives to the userscript header, depending on your preferred CDN:
|
|
95
103
|
```
|
|
96
104
|
// @require https://greasyfork.org/scripts/472956-userutils/code/UserUtils.js
|
|
97
105
|
```
|
|
98
106
|
```
|
|
99
107
|
// @require https://openuserjs.org/src/libs/Sv443/UserUtils.js
|
|
100
108
|
```
|
|
101
|
-
(in order for your userscript not to break on a major library update, use the versioned URL at the top of the [GreasyFork page](https://greasyfork.org/scripts/472956-userutils))
|
|
109
|
+
(in order for your userscript not to break on a major library update, instead use the versioned URL at the top of the [GreasyFork page](https://greasyfork.org/scripts/472956-userutils))
|
|
102
110
|
|
|
103
111
|
Then, access the functions on the global variable `UserUtils`:
|
|
104
112
|
```ts
|
|
@@ -109,10 +117,16 @@ View the documentation of previous major releases:
|
|
|
109
117
|
const { clamp } = UserUtils;
|
|
110
118
|
console.log(clamp(1, 5, 10)); // 5
|
|
111
119
|
```
|
|
120
|
+
If you're using TypeScript and it complains about the missing global variable `UserUtils`, install the library using the package manager of your choice and add the following inside a `.d.ts` file somewhere in your project:
|
|
121
|
+
```ts
|
|
122
|
+
declare global {
|
|
123
|
+
const UserUtils: typeof import("@sv443-network/userutils");
|
|
124
|
+
}
|
|
125
|
+
```
|
|
112
126
|
|
|
113
127
|
<br><br>
|
|
114
128
|
|
|
115
|
-
<!-- #
|
|
129
|
+
<!-- #region Preamble -->
|
|
116
130
|
## Preamble:
|
|
117
131
|
This library is written in TypeScript and contains builtin TypeScript declarations.
|
|
118
132
|
|
|
@@ -125,19 +139,19 @@ Their documentation will contain a section marked by a warning emoji (⚠️) th
|
|
|
125
139
|
|
|
126
140
|
<br><br>
|
|
127
141
|
|
|
128
|
-
<!-- #
|
|
142
|
+
<!-- #region License -->
|
|
129
143
|
## License:
|
|
130
144
|
This library is licensed under the MIT License.
|
|
131
145
|
See the [license file](./LICENSE.txt) for details.
|
|
132
146
|
|
|
133
147
|
<br><br>
|
|
134
148
|
|
|
135
|
-
<!-- #
|
|
149
|
+
<!-- #region Features -->
|
|
136
150
|
## Features:
|
|
137
151
|
|
|
138
152
|
<br>
|
|
139
153
|
|
|
140
|
-
<!-- #
|
|
154
|
+
<!-- #region DOM -->
|
|
141
155
|
## DOM:
|
|
142
156
|
|
|
143
157
|
### SelectorObserver
|
|
@@ -437,10 +451,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
437
451
|
fooObserver.enable();
|
|
438
452
|
});
|
|
439
453
|
```
|
|
440
|
-
|
|
441
454
|
</details>
|
|
442
455
|
|
|
443
|
-
|
|
444
456
|
<br>
|
|
445
457
|
|
|
446
458
|
### getUnsafeWindow()
|
|
@@ -468,7 +480,6 @@ const mouseEvent = new MouseEvent("mousemove", {
|
|
|
468
480
|
|
|
469
481
|
document.body.dispatchEvent(mouseEvent);
|
|
470
482
|
```
|
|
471
|
-
|
|
472
483
|
</details>
|
|
473
484
|
|
|
474
485
|
<br>
|
|
@@ -496,7 +507,6 @@ newParent.href = "https://example.org/";
|
|
|
496
507
|
|
|
497
508
|
addParent(element, newParent);
|
|
498
509
|
```
|
|
499
|
-
|
|
500
510
|
</details>
|
|
501
511
|
|
|
502
512
|
<br>
|
|
@@ -524,7 +534,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
524
534
|
`);
|
|
525
535
|
});
|
|
526
536
|
```
|
|
527
|
-
|
|
528
537
|
</details>
|
|
529
538
|
|
|
530
539
|
<br>
|
|
@@ -556,7 +565,6 @@ preloadImages([
|
|
|
556
565
|
console.error("Couldn't preload all images. Results:", results);
|
|
557
566
|
});
|
|
558
567
|
```
|
|
559
|
-
|
|
560
568
|
</details>
|
|
561
569
|
|
|
562
570
|
<br>
|
|
@@ -583,7 +591,6 @@ document.querySelector("#my-button").addEventListener("click", () => {
|
|
|
583
591
|
openInNewTab("https://example.org/", true);
|
|
584
592
|
});
|
|
585
593
|
```
|
|
586
|
-
|
|
587
594
|
</details>
|
|
588
595
|
|
|
589
596
|
<br>
|
|
@@ -618,7 +625,6 @@ interceptEvent(document.body, "click", (event) => {
|
|
|
618
625
|
return false; // allow all other click events through
|
|
619
626
|
});
|
|
620
627
|
```
|
|
621
|
-
|
|
622
628
|
</details>
|
|
623
629
|
|
|
624
630
|
<br>
|
|
@@ -648,7 +654,6 @@ import { interceptWindowEvent } from "@sv443-network/userutils";
|
|
|
648
654
|
// as no predicate is specified, all events will be discarded by default
|
|
649
655
|
interceptWindowEvent("beforeunload");
|
|
650
656
|
```
|
|
651
|
-
|
|
652
657
|
</details>
|
|
653
658
|
|
|
654
659
|
<br>
|
|
@@ -673,7 +678,6 @@ const { horizontal, vertical } = isScrollable(element);
|
|
|
673
678
|
console.log("Element has a horizontal scroll bar:", horizontal);
|
|
674
679
|
console.log("Element has a vertical scroll bar:", vertical);
|
|
675
680
|
```
|
|
676
|
-
|
|
677
681
|
</details>
|
|
678
682
|
|
|
679
683
|
<br>
|
|
@@ -726,7 +730,6 @@ observeElementProp(myInput, "value", (oldValue, newValue) => {
|
|
|
726
730
|
console.log("Value changed from", oldValue, "to", newValue);
|
|
727
731
|
});
|
|
728
732
|
```
|
|
729
|
-
|
|
730
733
|
</details>
|
|
731
734
|
|
|
732
735
|
<br>
|
|
@@ -849,12 +852,42 @@ const allBelowExcl = getSiblingsFrame(refElement, Infinity, "bottom", false);
|
|
|
849
852
|
// <div>5</div> │ frame
|
|
850
853
|
// <div>6</div> ◄──┘
|
|
851
854
|
```
|
|
855
|
+
</details>
|
|
856
|
+
|
|
857
|
+
<br>
|
|
858
|
+
|
|
859
|
+
### setInnerHtmlUnsafe()
|
|
860
|
+
Usage:
|
|
861
|
+
```ts
|
|
862
|
+
setInnerHtmlUnsafe(element: Element, html: string): Element
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
Sets the innerHTML property of the provided element without any sanitation or validation.
|
|
866
|
+
Makes use of the [Trusted Types API](https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API) to trick the browser into thinking the HTML is safe.
|
|
867
|
+
Use this function if the page makes use of the CSP directive `require-trusted-types-for 'script'` and throws a "This document requires 'TrustedHTML' assignment" error on Chromium-based browsers.
|
|
868
|
+
If the browser doesn't support Trusted Types, this function will fall back to regular innerHTML assignment.
|
|
869
|
+
|
|
870
|
+
⚠️ This function does not perform any sanitization, it only tricks the browser into thinking the HTML is safe and should thus be used with utmost caution, as it can easily cause XSS vulnerabilities!
|
|
871
|
+
A much better way of doing this is by using the [DOMPurify](https://github.com/cure53/DOMPurify#what-about-dompurify-and-trusted-types) library to create your own Trusted Types policy that *actually* sanitizes the HTML and prevents (most) XSS attack vectors.
|
|
872
|
+
You can also find more info [here.](https://web.dev/articles/trusted-types#library)
|
|
873
|
+
|
|
874
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
875
|
+
|
|
876
|
+
```ts
|
|
877
|
+
import { setInnerHtmlUnsafe } from "@sv443-network/userutils";
|
|
878
|
+
|
|
879
|
+
const myElement = document.querySelector("#my-element");
|
|
880
|
+
setInnerHtmlUnsafe(myElement, "<img src='https://picsum.photos/100/100' />"); // hardcoded value, so no XSS risk
|
|
852
881
|
|
|
882
|
+
const myXssElement = document.querySelector("#my-xss-element");
|
|
883
|
+
const userModifiableVariable = `<img onerror="alert('XSS!')" src="invalid" />`; // let's pretend this came from user input
|
|
884
|
+
setInnerHtmlUnsafe(myXssElement, userModifiableVariable); // <- uses a user-modifiable variable, so big XSS risk!
|
|
885
|
+
```
|
|
853
886
|
</details>
|
|
854
887
|
|
|
855
888
|
<br><br>
|
|
856
889
|
|
|
857
|
-
<!-- #
|
|
890
|
+
<!-- #region Math -->
|
|
858
891
|
## Math:
|
|
859
892
|
|
|
860
893
|
### clamp()
|
|
@@ -879,7 +912,6 @@ clamp(99999, 0, 10); // 10
|
|
|
879
912
|
clamp(-99999, -Infinity, 0); // -99999
|
|
880
913
|
clamp(99999, 0, Infinity); // 99999
|
|
881
914
|
```
|
|
882
|
-
|
|
883
915
|
</details>
|
|
884
916
|
|
|
885
917
|
<br>
|
|
@@ -910,7 +942,6 @@ mapRange(5, 0, 10, 0, 50); // 25
|
|
|
910
942
|
// for example, if 4 files of a total of 13 were downloaded:
|
|
911
943
|
mapRange(4, 0, 13, 0, 100); // 30.76923076923077
|
|
912
944
|
```
|
|
913
|
-
|
|
914
945
|
</details>
|
|
915
946
|
|
|
916
947
|
<br>
|
|
@@ -934,12 +965,11 @@ randRange(0, 10); // 4
|
|
|
934
965
|
randRange(10, 20); // 17
|
|
935
966
|
randRange(10); // 7
|
|
936
967
|
```
|
|
937
|
-
|
|
938
968
|
</details>
|
|
939
969
|
|
|
940
970
|
<br><br>
|
|
941
971
|
|
|
942
|
-
<!-- #
|
|
972
|
+
<!-- #region Misc -->
|
|
943
973
|
## Misc:
|
|
944
974
|
|
|
945
975
|
### DataStore
|
|
@@ -1102,7 +1132,6 @@ async function init() {
|
|
|
1102
1132
|
|
|
1103
1133
|
init();
|
|
1104
1134
|
```
|
|
1105
|
-
|
|
1106
1135
|
</details>
|
|
1107
1136
|
|
|
1108
1137
|
<br>
|
|
@@ -1110,7 +1139,7 @@ init();
|
|
|
1110
1139
|
### DataStoreSerializer
|
|
1111
1140
|
Usage:
|
|
1112
1141
|
```ts
|
|
1113
|
-
new DataStoreSerializer(stores: DataStore[], options
|
|
1142
|
+
new DataStoreSerializer(stores: DataStore[], options?: DataStoreSerializerOptions)
|
|
1114
1143
|
```
|
|
1115
1144
|
|
|
1116
1145
|
A class that manages serializing and deserializing (exporting and importing) one to infinite DataStore instances.
|
|
@@ -1203,8 +1232,13 @@ const serializer = new DataStoreSerializer([fooStore, barStore], {
|
|
|
1203
1232
|
});
|
|
1204
1233
|
|
|
1205
1234
|
async function exportMyDataPls() {
|
|
1235
|
+
// first, make sure the persistent data of the stores is loaded into their caches:
|
|
1236
|
+
await fooStore.loadData();
|
|
1237
|
+
await barStore.loadData();
|
|
1238
|
+
|
|
1239
|
+
// now serialize the data:
|
|
1206
1240
|
const serializedData = await serializer.serialize();
|
|
1207
|
-
// create file and download it:
|
|
1241
|
+
// create a file and download it:
|
|
1208
1242
|
const blob = new Blob([serializedData], { type: "application/json" });
|
|
1209
1243
|
const url = URL.createObjectURL(blob);
|
|
1210
1244
|
const a = document.createElement("a");
|
|
@@ -1213,7 +1247,7 @@ async function exportMyDataPls() {
|
|
|
1213
1247
|
a.click();
|
|
1214
1248
|
a.remove();
|
|
1215
1249
|
|
|
1216
|
-
//
|
|
1250
|
+
// `serialize()` exports a stringified object that looks similar to this:
|
|
1217
1251
|
// [
|
|
1218
1252
|
// {
|
|
1219
1253
|
// "id": "foo-data",
|
|
@@ -1358,7 +1392,6 @@ fooDialog.on("close", () => {
|
|
|
1358
1392
|
|
|
1359
1393
|
fooDialog.open();
|
|
1360
1394
|
```
|
|
1361
|
-
|
|
1362
1395
|
</details>
|
|
1363
1396
|
|
|
1364
1397
|
<br>
|
|
@@ -1372,13 +1405,13 @@ new NanoEmitter<TEventMap = EventsMap>(options?: NanoEmitterOptions): NanoEmitte
|
|
|
1372
1405
|
A class that provides a minimalistic event emitter with a tiny footprint powered by [nanoevents.](https://npmjs.com/package/nanoevents)
|
|
1373
1406
|
The `TEventMap` generic is used to define the events that can be emitted and listened to.
|
|
1374
1407
|
|
|
1375
|
-
The intention behind this class is to extend it in your own classes to provide a simple event system.
|
|
1376
|
-
|
|
1408
|
+
The main intention behind this class is to extend it in your own classes to provide a simple event system directly built into the class.
|
|
1409
|
+
However in a functional environment you can also just create instances for use as standalone event emitters throughout your project.
|
|
1377
1410
|
|
|
1378
1411
|
The options object has the following properties:
|
|
1379
1412
|
| Property | Description |
|
|
1380
1413
|
| :-- | :-- |
|
|
1381
|
-
| `publicEmit?: boolean` | (Optional)
|
|
1414
|
+
| `publicEmit?: boolean` | (Optional) If set to true, allows emitting events through the public method `emit()` (`false` by default). |
|
|
1382
1415
|
|
|
1383
1416
|
Methods:
|
|
1384
1417
|
`on<K extends keyof TEventMap>(event: K, listener: TEventMap[K]): void`
|
|
@@ -1390,8 +1423,8 @@ Registers a listener function for the given event that will only be called once.
|
|
|
1390
1423
|
|
|
1391
1424
|
`emit<K extends keyof TEventMap>(event: K, ...args: Parameters<TEventMap[K]>): boolean`
|
|
1392
1425
|
Emits an event with the given arguments from outside the class instance if `publicEmit` is set to `true`.
|
|
1393
|
-
If `publicEmit` is set to `true`, this method
|
|
1394
|
-
If
|
|
1426
|
+
If `publicEmit` is set to `true`, this method will return `true` if the event was emitted.
|
|
1427
|
+
If it is set to `false`, it will always return `false` and you will need to use `this.events.emit()` from inside the class instead.
|
|
1395
1428
|
|
|
1396
1429
|
`unsubscribeAll(): void`
|
|
1397
1430
|
Removes all listeners from all events.
|
|
@@ -1403,6 +1436,7 @@ Removes all listeners from all events.
|
|
|
1403
1436
|
```ts
|
|
1404
1437
|
import { NanoEmitter } from "@sv443-network/userutils";
|
|
1405
1438
|
|
|
1439
|
+
// map of events for strong typing - the functions always return void
|
|
1406
1440
|
interface MyEventMap {
|
|
1407
1441
|
foo: (bar: string) => void;
|
|
1408
1442
|
baz: (qux: number) => void;
|
|
@@ -1414,8 +1448,9 @@ class MyClass extends NanoEmitter<MyEventMap> {
|
|
|
1414
1448
|
// allow emitting events from outside the class
|
|
1415
1449
|
publicEmit: true,
|
|
1416
1450
|
});
|
|
1451
|
+
|
|
1417
1452
|
this.once("baz", (qux) => {
|
|
1418
|
-
console.log("baz event (inside):", qux);
|
|
1453
|
+
console.log("baz event (inside, once):", qux);
|
|
1419
1454
|
});
|
|
1420
1455
|
}
|
|
1421
1456
|
|
|
@@ -1438,7 +1473,6 @@ myInstance.emit("baz", "hello from the outside");
|
|
|
1438
1473
|
|
|
1439
1474
|
myInstance.unsubscribeAll();
|
|
1440
1475
|
```
|
|
1441
|
-
|
|
1442
1476
|
</details>
|
|
1443
1477
|
|
|
1444
1478
|
<br>
|
|
@@ -1448,6 +1482,7 @@ myInstance.unsubscribeAll();
|
|
|
1448
1482
|
```ts
|
|
1449
1483
|
import { NanoEmitter } from "@sv443-network/userutils";
|
|
1450
1484
|
|
|
1485
|
+
// map of events for strong typing - the functions always return void
|
|
1451
1486
|
interface MyEventMap {
|
|
1452
1487
|
foo: (bar: string) => void;
|
|
1453
1488
|
baz: (qux: number) => void;
|
|
@@ -1463,7 +1498,7 @@ myEmitter.on("foo", (bar) => {
|
|
|
1463
1498
|
});
|
|
1464
1499
|
|
|
1465
1500
|
myEmitter.once("baz", (qux) => {
|
|
1466
|
-
console.log("baz event:", qux);
|
|
1501
|
+
console.log("baz event (once):", qux);
|
|
1467
1502
|
});
|
|
1468
1503
|
|
|
1469
1504
|
function doStuff() {
|
|
@@ -1507,7 +1542,6 @@ autoPlural("apple", [1, 2]); // "apples"
|
|
|
1507
1542
|
const items = [1, 2, 3, 4, "foo", "bar"];
|
|
1508
1543
|
console.log(`Found ${items.length} ${autoPlural("item", items)}`); // "Found 6 items"
|
|
1509
1544
|
```
|
|
1510
|
-
|
|
1511
1545
|
</details>
|
|
1512
1546
|
|
|
1513
1547
|
<br>
|
|
@@ -1531,7 +1565,6 @@ async function run() {
|
|
|
1531
1565
|
console.log("World");
|
|
1532
1566
|
}
|
|
1533
1567
|
```
|
|
1534
|
-
|
|
1535
1568
|
</details>
|
|
1536
1569
|
|
|
1537
1570
|
<br>
|
|
@@ -1577,7 +1610,6 @@ const myFunc = debounce((event) => {
|
|
|
1577
1610
|
|
|
1578
1611
|
document.body.addEventListener("scroll", myFunc);
|
|
1579
1612
|
```
|
|
1580
|
-
|
|
1581
1613
|
</details>
|
|
1582
1614
|
|
|
1583
1615
|
<br>
|
|
@@ -1612,7 +1644,6 @@ fetchAdvanced("https://jokeapi.dev/joke/Any?safe-mode", {
|
|
|
1612
1644
|
console.error("Fetch error:", err);
|
|
1613
1645
|
});
|
|
1614
1646
|
```
|
|
1615
|
-
|
|
1616
1647
|
</details>
|
|
1617
1648
|
|
|
1618
1649
|
<br>
|
|
@@ -1640,7 +1671,6 @@ insertValues("Testing %1", { toString: () => "foo" }); // "Testing foo"
|
|
|
1640
1671
|
const values = ["foo", "bar", "baz"];
|
|
1641
1672
|
insertValues("Testing %1, %2, %3 and %4", ...values); // "Testing foo, bar and baz and %4"
|
|
1642
1673
|
```
|
|
1643
|
-
|
|
1644
1674
|
</details>
|
|
1645
1675
|
|
|
1646
1676
|
<br>
|
|
@@ -1681,7 +1711,6 @@ const barDeflate = await compress("Hello, World!".repeat(20), "deflate");
|
|
|
1681
1711
|
console.log(fooDeflate); // "eJzzSM3JyddRCM8vyklRBAAfngRq"
|
|
1682
1712
|
console.log(barDeflate); // "eJzzSM3JyddRCM8vyklR9BiZHAAIEVg1"
|
|
1683
1713
|
```
|
|
1684
|
-
|
|
1685
1714
|
</details>
|
|
1686
1715
|
|
|
1687
1716
|
<br>
|
|
@@ -1712,7 +1741,6 @@ const decompressed = await decompress(compressed, "gzip");
|
|
|
1712
1741
|
|
|
1713
1742
|
console.log(decompressed); // "Hello, World!"
|
|
1714
1743
|
```
|
|
1715
|
-
|
|
1716
1744
|
</details>
|
|
1717
1745
|
|
|
1718
1746
|
<br>
|
|
@@ -1779,12 +1807,11 @@ randomId(10, 2); // "1010001101" (length 10, radix 2)
|
|
|
1779
1807
|
randomId(10, 10); // "0183428506" (length 10, radix 10)
|
|
1780
1808
|
randomId(10, 36); // "z46jfpa37r" (length 10, radix 36)
|
|
1781
1809
|
```
|
|
1782
|
-
|
|
1783
1810
|
</details>
|
|
1784
1811
|
|
|
1785
1812
|
<br><br>
|
|
1786
1813
|
|
|
1787
|
-
<!-- #
|
|
1814
|
+
<!-- #region Arrays -->
|
|
1788
1815
|
## Arrays:
|
|
1789
1816
|
|
|
1790
1817
|
### randomItem()
|
|
@@ -1804,7 +1831,6 @@ import { randomItem } from "@sv443-network/userutils";
|
|
|
1804
1831
|
randomItem(["foo", "bar", "baz"]); // "bar"
|
|
1805
1832
|
randomItem([ ]); // undefined
|
|
1806
1833
|
```
|
|
1807
|
-
|
|
1808
1834
|
</details>
|
|
1809
1835
|
|
|
1810
1836
|
<br>
|
|
@@ -1831,7 +1857,6 @@ const [item, index] = randomItemIndex(["foo", "bar", "baz"]); // ["bar", 1]
|
|
|
1831
1857
|
// or if you only want the index:
|
|
1832
1858
|
const [, index] = randomItemIndex(["foo", "bar", "baz"]); // 1
|
|
1833
1859
|
```
|
|
1834
|
-
|
|
1835
1860
|
</details>
|
|
1836
1861
|
|
|
1837
1862
|
<br>
|
|
@@ -1854,7 +1879,6 @@ const arr = ["foo", "bar", "baz"];
|
|
|
1854
1879
|
takeRandomItem(arr); // "bar"
|
|
1855
1880
|
console.log(arr); // ["foo", "baz"]
|
|
1856
1881
|
```
|
|
1857
|
-
|
|
1858
1882
|
</details>
|
|
1859
1883
|
|
|
1860
1884
|
<br>
|
|
@@ -1880,12 +1904,11 @@ console.log(randomizeArray(foo)); // [4, 5, 2, 1, 6, 3]
|
|
|
1880
1904
|
|
|
1881
1905
|
console.log(foo); // [1, 2, 3, 4, 5, 6] - original array is not mutated
|
|
1882
1906
|
```
|
|
1883
|
-
|
|
1884
1907
|
</details>
|
|
1885
1908
|
|
|
1886
1909
|
<br><br>
|
|
1887
1910
|
|
|
1888
|
-
<!-- #
|
|
1911
|
+
<!-- #region Translation -->
|
|
1889
1912
|
## Translation:
|
|
1890
1913
|
This is a very lightweight translation function that can be used to translate simple strings.
|
|
1891
1914
|
Pluralization is not supported but can be achieved manually by adding variations to the translations, identified by a different suffix. See the example section of [`tr.addLanguage()`](#traddlanguage) for an example on how this might be done.
|
|
@@ -1933,7 +1956,6 @@ tr.setLanguage("de");
|
|
|
1933
1956
|
console.log(tr("welcome")); // "Willkommen"
|
|
1934
1957
|
console.log(tr("welcome_name", "John")); // "Willkommen, John"
|
|
1935
1958
|
```
|
|
1936
|
-
|
|
1937
1959
|
</details>
|
|
1938
1960
|
|
|
1939
1961
|
<br>
|
|
@@ -2005,34 +2027,33 @@ tr.addLanguage("de-AT", {
|
|
|
2005
2027
|
// example for custom pluralization:
|
|
2006
2028
|
|
|
2007
2029
|
tr.addLanguage("en", {
|
|
2008
|
-
"
|
|
2009
|
-
"
|
|
2010
|
-
"
|
|
2030
|
+
"cart_items_added-0": "No items were added to the cart",
|
|
2031
|
+
"cart_items_added-1": "Added %1 item to the cart",
|
|
2032
|
+
"cart_items_added-n": "Added %1 items to the cart",
|
|
2011
2033
|
});
|
|
2012
2034
|
|
|
2013
|
-
/** Returns the custom pluralization identifier for the given number of items (or size of Array/NodeList) */
|
|
2014
|
-
function pl(num: number | unknown
|
|
2035
|
+
/** Returns the translation key with a custom pluralization identifier added to it for the given number of items (or size of Array/NodeList or anything else with a `length` property) */
|
|
2036
|
+
function pl(key: string, num: number | Array<unknown> | NodeList | { length: number }) {
|
|
2015
2037
|
if(typeof num !== "number")
|
|
2016
2038
|
num = num.length;
|
|
2017
2039
|
|
|
2018
2040
|
if(num === 0)
|
|
2019
|
-
return
|
|
2041
|
+
return `${key}-0`;
|
|
2020
2042
|
else if(num === 1)
|
|
2021
|
-
return
|
|
2043
|
+
return `${key}-1`;
|
|
2022
2044
|
else
|
|
2023
|
-
return
|
|
2045
|
+
return `${key}-n`;
|
|
2024
2046
|
};
|
|
2025
2047
|
|
|
2026
2048
|
const items = [];
|
|
2027
|
-
tr(
|
|
2049
|
+
console.log(tr(pl("cart_items_added", items), items.length)); // "No items were added to the cart"
|
|
2028
2050
|
|
|
2029
2051
|
items.push("foo");
|
|
2030
|
-
tr(
|
|
2052
|
+
console.log(tr(pl("cart_items_added", items), items.length)); // "Added 1 item to the cart"
|
|
2031
2053
|
|
|
2032
2054
|
items.push("bar");
|
|
2033
|
-
tr(
|
|
2055
|
+
console.log(tr(pl("cart_items_added", items), items.length)); // "Added 2 items to the cart"
|
|
2034
2056
|
```
|
|
2035
|
-
|
|
2036
2057
|
</details>
|
|
2037
2058
|
|
|
2038
2059
|
<br>
|
|
@@ -2061,7 +2082,101 @@ If no language has been set yet, it will return undefined.
|
|
|
2061
2082
|
|
|
2062
2083
|
<br><br>
|
|
2063
2084
|
|
|
2064
|
-
|
|
2085
|
+
## Colors:
|
|
2086
|
+
The color functions are used to manipulate and convert colors in various formats.
|
|
2087
|
+
|
|
2088
|
+
### hexToRgb()
|
|
2089
|
+
Usage:
|
|
2090
|
+
```ts
|
|
2091
|
+
hexToRgb(hex: string): [red: number, green: number, blue: number]
|
|
2092
|
+
```
|
|
2093
|
+
|
|
2094
|
+
Converts a hex color string to an RGB color tuple array.
|
|
2095
|
+
Accepts the formats `#RRGGBB` and `#RGB`, with or without the hash symbol.
|
|
2096
|
+
|
|
2097
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
2098
|
+
|
|
2099
|
+
```ts
|
|
2100
|
+
import { hexToRgb } from "@sv443-network/userutils";
|
|
2101
|
+
|
|
2102
|
+
hexToRgb("#ff0000"); // [255, 0, 0]
|
|
2103
|
+
hexToRgb("0032ef"); // [0, 50, 239]
|
|
2104
|
+
hexToRgb("#0f0"); // [0, 255, 0]
|
|
2105
|
+
```
|
|
2106
|
+
</details>
|
|
2107
|
+
|
|
2108
|
+
<br>
|
|
2109
|
+
|
|
2110
|
+
### rgbToHex()
|
|
2111
|
+
Usage:
|
|
2112
|
+
```ts
|
|
2113
|
+
rgbToHex(red: number, green: number, blue: number, withHash?: boolean, upperCase?: boolean): string
|
|
2114
|
+
```
|
|
2115
|
+
|
|
2116
|
+
Converts RGB color values to a hex color string.
|
|
2117
|
+
The `withHash` parameter determines if the hash symbol should be included in the output (true by default).
|
|
2118
|
+
The `upperCase` parameter determines if the output should be in uppercase (false by default).
|
|
2119
|
+
|
|
2120
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
2121
|
+
|
|
2122
|
+
```ts
|
|
2123
|
+
import { rgbToHex } from "@sv443-network/userutils";
|
|
2124
|
+
|
|
2125
|
+
rgbToHex(255, 0, 0); // "#ff0000"
|
|
2126
|
+
rgbToHex(255, 0, 0, false); // "ff0000"
|
|
2127
|
+
rgbToHex(255, 0, 0, true, true); // "#FF0000"
|
|
2128
|
+
```
|
|
2129
|
+
</details>
|
|
2130
|
+
|
|
2131
|
+
<br>
|
|
2132
|
+
|
|
2133
|
+
### lightenColor()
|
|
2134
|
+
Usage:
|
|
2135
|
+
```ts
|
|
2136
|
+
lightenColor(color: string, percent: number): string
|
|
2137
|
+
```
|
|
2138
|
+
|
|
2139
|
+
Lightens a CSS color value (in hex, RGB or RGBA format) by a given percentage.
|
|
2140
|
+
Will not exceed the maximum range (00-FF or 0-255).
|
|
2141
|
+
Throws an error if the color format is invalid or not supported.
|
|
2142
|
+
|
|
2143
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
2144
|
+
|
|
2145
|
+
```ts
|
|
2146
|
+
import { lightenColor } from "@sv443-network/userutils";
|
|
2147
|
+
|
|
2148
|
+
lightenColor("#ff0000", 20); // "#ff3333"
|
|
2149
|
+
lightenColor("rgb(0, 255, 0)", 50); // "rgb(128, 255, 128)"
|
|
2150
|
+
lightenColor("rgba(0, 255, 0, 0.5)", 50); // "rgba(128, 255, 128, 0.5)"
|
|
2151
|
+
```
|
|
2152
|
+
</details>
|
|
2153
|
+
|
|
2154
|
+
<br>
|
|
2155
|
+
|
|
2156
|
+
### darkenColor()
|
|
2157
|
+
Usage:
|
|
2158
|
+
```ts
|
|
2159
|
+
darkenColor(color: string, percent: number): string
|
|
2160
|
+
```
|
|
2161
|
+
|
|
2162
|
+
Darkens a CSS color value (in hex, RGB or RGBA format) by a given percentage.
|
|
2163
|
+
Will not exceed the maximum range (00-FF or 0-255).
|
|
2164
|
+
Throws an error if the color format is invalid or not supported.
|
|
2165
|
+
|
|
2166
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
2167
|
+
|
|
2168
|
+
```ts
|
|
2169
|
+
import { darkenColor } from "@sv443-network/userutils";
|
|
2170
|
+
|
|
2171
|
+
darkenColor("#ff0000", 20); // "#cc0000"
|
|
2172
|
+
darkenColor("rgb(0, 255, 0)", 50); // "rgb(0, 128, 0)"
|
|
2173
|
+
darkenColor("rgba(0, 255, 0, 0.5)", 50); // "rgba(0, 128, 0, 0.5)"
|
|
2174
|
+
```
|
|
2175
|
+
</details>
|
|
2176
|
+
|
|
2177
|
+
<br><br>
|
|
2178
|
+
|
|
2179
|
+
<!-- #region Utility types -->
|
|
2065
2180
|
## Utility types:
|
|
2066
2181
|
UserUtils also offers some utility types that can be used in TypeScript projects.
|
|
2067
2182
|
They don't alter the runtime behavior of the code, but they can be used to make the code more readable and to prevent errors.
|
|
@@ -2097,7 +2212,6 @@ logSomething(fooObject); // "Log: hello world"
|
|
|
2097
2212
|
|
|
2098
2213
|
logSomething(barObject); // Type error
|
|
2099
2214
|
```
|
|
2100
|
-
|
|
2101
2215
|
</details>
|
|
2102
2216
|
|
|
2103
2217
|
<br>
|
|
@@ -2128,7 +2242,6 @@ function somethingElse(array: NonEmptyArray) {
|
|
|
2128
2242
|
|
|
2129
2243
|
logFirstItem(["04abc", "69"]); // 4
|
|
2130
2244
|
```
|
|
2131
|
-
|
|
2132
2245
|
</details>
|
|
2133
2246
|
|
|
2134
2247
|
<br>
|
|
@@ -2153,7 +2266,6 @@ function convertToNumber<T extends string>(str: NonEmptyString<T>) {
|
|
|
2153
2266
|
convertToNumber("04abc"); // "4"
|
|
2154
2267
|
convertToNumber(""); // type error: Argument of type 'string' is not assignable to parameter of type 'never'
|
|
2155
2268
|
```
|
|
2156
|
-
|
|
2157
2269
|
</details>
|
|
2158
2270
|
|
|
2159
2271
|
<br>
|
|
@@ -2181,12 +2293,11 @@ foo("a"); // included in autocomplete, no type error
|
|
|
2181
2293
|
foo(""); // *not* included in autocomplete, still no type error
|
|
2182
2294
|
foo(1); // type error: Argument of type '1' is not assignable to parameter of type 'LooseUnion<"a" | "b" | "c">'
|
|
2183
2295
|
```
|
|
2184
|
-
|
|
2185
2296
|
</details>
|
|
2186
2297
|
|
|
2187
2298
|
<br><br><br><br>
|
|
2188
2299
|
|
|
2189
|
-
<!-- #
|
|
2300
|
+
<!-- #region Footer -->
|
|
2190
2301
|
<div style="text-align: center;" align="center">
|
|
2191
2302
|
|
|
2192
2303
|
Made with ❤️ by [Sv443](https://github.com/Sv443)
|