@sv443-network/userutils 7.0.1 → 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 +18 -0
- package/README.md +416 -70
- package/dist/index.global.js +685 -177
- package/dist/index.js +656 -203
- package/dist/lib/Dialog.d.ts +104 -0
- package/dist/lib/NanoEmitter.d.ts +20 -0
- 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 +3 -0
- package/dist/lib/misc.d.ts +7 -31
- package/dist/lib/types.d.ts +10 -0
- package/package.json +9 -2
- package/dist/index.mjs +0 -844
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
|
|
@@ -44,6 +45,8 @@ View the documentation of previous major releases:
|
|
|
44
45
|
- [**Misc:**](#misc)
|
|
45
46
|
- [`DataStore`](#datastore) - class that manages a hybrid sync & async persistent JSON database, including data migration
|
|
46
47
|
- [`DataStoreSerializer`](#datastoreserializer) - class for importing & exporting data of multiple DataStore instances, including compression, checksumming and running migrations
|
|
48
|
+
- [`Dialog`](#dialog) - class for creating custom modal dialogs with a promise-based API and a generic, default style
|
|
49
|
+
- [`NanoEmitter`](#nanoemitter) - tiny event emitter class with a focus on performance and simplicity (based on [nanoevents](https://npmjs.com/package/nanoevents))
|
|
47
50
|
- [`autoPlural()`](#autoplural) - automatically pluralize a string
|
|
48
51
|
- [`pauseFor()`](#pausefor) - pause the execution of a function for a given amount of time
|
|
49
52
|
- [`debounce()`](#debounce) - call a function only once in a series of calls, after or before a given timeout
|
|
@@ -60,9 +63,14 @@ View the documentation of previous major releases:
|
|
|
60
63
|
- [`randomizeArray()`](#randomizearray) - returns a copy of the array with its items in a random order
|
|
61
64
|
- [**Translation:**](#translation)
|
|
62
65
|
- [`tr()`](#tr) - simple translation of a string to another language
|
|
63
|
-
- [tr.addLanguage()](#traddlanguage) - add a language and its translations
|
|
64
|
-
- [tr.setLanguage()](#trsetlanguage) - set the currently active language for translations
|
|
65
|
-
- [tr.getLanguage()](#trgetlanguage) - returns the currently active language
|
|
66
|
+
- [`tr.addLanguage()`](#traddlanguage) - add a language and its translations
|
|
67
|
+
- [`tr.setLanguage()`](#trsetlanguage) - set the currently active language for translations
|
|
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
|
|
66
74
|
- [**Utility types for TypeScript:**](#utility-types)
|
|
67
75
|
- [`Stringifiable`](#stringifiable) - any value that is a string or can be converted to one (implicitly or explicitly)
|
|
68
76
|
- [`NonEmptyArray`](#nonemptyarray) - any array that should have at least one item
|
|
@@ -71,12 +79,15 @@ View the documentation of previous major releases:
|
|
|
71
79
|
|
|
72
80
|
<br><br>
|
|
73
81
|
|
|
74
|
-
<!-- #
|
|
82
|
+
<!-- #region Installation -->
|
|
75
83
|
## Installation:
|
|
76
|
-
|
|
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:
|
|
77
87
|
```
|
|
78
88
|
npm i @sv443-network/userutils
|
|
79
89
|
```
|
|
90
|
+
<sup>For other package managers, check out the respective install command on the [JavaScript Registry](https://jsr.io/@sv443-network/userutils)</sup>
|
|
80
91
|
Then, import it in your script as usual:
|
|
81
92
|
```ts
|
|
82
93
|
import { addGlobalStyle } from "@sv443-network/userutils";
|
|
@@ -85,18 +96,17 @@ View the documentation of previous major releases:
|
|
|
85
96
|
|
|
86
97
|
import * as UserUtils from "@sv443-network/userutils";
|
|
87
98
|
```
|
|
88
|
-
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.
|
|
89
99
|
|
|
90
100
|
<br>
|
|
91
101
|
|
|
92
|
-
- 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:
|
|
93
103
|
```
|
|
94
104
|
// @require https://greasyfork.org/scripts/472956-userutils/code/UserUtils.js
|
|
95
105
|
```
|
|
96
106
|
```
|
|
97
107
|
// @require https://openuserjs.org/src/libs/Sv443/UserUtils.js
|
|
98
108
|
```
|
|
99
|
-
(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))
|
|
100
110
|
|
|
101
111
|
Then, access the functions on the global variable `UserUtils`:
|
|
102
112
|
```ts
|
|
@@ -107,10 +117,16 @@ View the documentation of previous major releases:
|
|
|
107
117
|
const { clamp } = UserUtils;
|
|
108
118
|
console.log(clamp(1, 5, 10)); // 5
|
|
109
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
|
+
```
|
|
110
126
|
|
|
111
127
|
<br><br>
|
|
112
128
|
|
|
113
|
-
<!-- #
|
|
129
|
+
<!-- #region Preamble -->
|
|
114
130
|
## Preamble:
|
|
115
131
|
This library is written in TypeScript and contains builtin TypeScript declarations.
|
|
116
132
|
|
|
@@ -123,19 +139,19 @@ Their documentation will contain a section marked by a warning emoji (⚠️) th
|
|
|
123
139
|
|
|
124
140
|
<br><br>
|
|
125
141
|
|
|
126
|
-
<!-- #
|
|
142
|
+
<!-- #region License -->
|
|
127
143
|
## License:
|
|
128
144
|
This library is licensed under the MIT License.
|
|
129
145
|
See the [license file](./LICENSE.txt) for details.
|
|
130
146
|
|
|
131
147
|
<br><br>
|
|
132
148
|
|
|
133
|
-
<!-- #
|
|
149
|
+
<!-- #region Features -->
|
|
134
150
|
## Features:
|
|
135
151
|
|
|
136
152
|
<br>
|
|
137
153
|
|
|
138
|
-
<!-- #
|
|
154
|
+
<!-- #region DOM -->
|
|
139
155
|
## DOM:
|
|
140
156
|
|
|
141
157
|
### SelectorObserver
|
|
@@ -435,10 +451,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
435
451
|
fooObserver.enable();
|
|
436
452
|
});
|
|
437
453
|
```
|
|
438
|
-
|
|
439
454
|
</details>
|
|
440
455
|
|
|
441
|
-
|
|
442
456
|
<br>
|
|
443
457
|
|
|
444
458
|
### getUnsafeWindow()
|
|
@@ -466,7 +480,6 @@ const mouseEvent = new MouseEvent("mousemove", {
|
|
|
466
480
|
|
|
467
481
|
document.body.dispatchEvent(mouseEvent);
|
|
468
482
|
```
|
|
469
|
-
|
|
470
483
|
</details>
|
|
471
484
|
|
|
472
485
|
<br>
|
|
@@ -494,7 +507,6 @@ newParent.href = "https://example.org/";
|
|
|
494
507
|
|
|
495
508
|
addParent(element, newParent);
|
|
496
509
|
```
|
|
497
|
-
|
|
498
510
|
</details>
|
|
499
511
|
|
|
500
512
|
<br>
|
|
@@ -522,7 +534,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
522
534
|
`);
|
|
523
535
|
});
|
|
524
536
|
```
|
|
525
|
-
|
|
526
537
|
</details>
|
|
527
538
|
|
|
528
539
|
<br>
|
|
@@ -554,7 +565,6 @@ preloadImages([
|
|
|
554
565
|
console.error("Couldn't preload all images. Results:", results);
|
|
555
566
|
});
|
|
556
567
|
```
|
|
557
|
-
|
|
558
568
|
</details>
|
|
559
569
|
|
|
560
570
|
<br>
|
|
@@ -581,7 +591,6 @@ document.querySelector("#my-button").addEventListener("click", () => {
|
|
|
581
591
|
openInNewTab("https://example.org/", true);
|
|
582
592
|
});
|
|
583
593
|
```
|
|
584
|
-
|
|
585
594
|
</details>
|
|
586
595
|
|
|
587
596
|
<br>
|
|
@@ -616,7 +625,6 @@ interceptEvent(document.body, "click", (event) => {
|
|
|
616
625
|
return false; // allow all other click events through
|
|
617
626
|
});
|
|
618
627
|
```
|
|
619
|
-
|
|
620
628
|
</details>
|
|
621
629
|
|
|
622
630
|
<br>
|
|
@@ -646,7 +654,6 @@ import { interceptWindowEvent } from "@sv443-network/userutils";
|
|
|
646
654
|
// as no predicate is specified, all events will be discarded by default
|
|
647
655
|
interceptWindowEvent("beforeunload");
|
|
648
656
|
```
|
|
649
|
-
|
|
650
657
|
</details>
|
|
651
658
|
|
|
652
659
|
<br>
|
|
@@ -671,7 +678,6 @@ const { horizontal, vertical } = isScrollable(element);
|
|
|
671
678
|
console.log("Element has a horizontal scroll bar:", horizontal);
|
|
672
679
|
console.log("Element has a vertical scroll bar:", vertical);
|
|
673
680
|
```
|
|
674
|
-
|
|
675
681
|
</details>
|
|
676
682
|
|
|
677
683
|
<br>
|
|
@@ -724,7 +730,6 @@ observeElementProp(myInput, "value", (oldValue, newValue) => {
|
|
|
724
730
|
console.log("Value changed from", oldValue, "to", newValue);
|
|
725
731
|
});
|
|
726
732
|
```
|
|
727
|
-
|
|
728
733
|
</details>
|
|
729
734
|
|
|
730
735
|
<br>
|
|
@@ -847,12 +852,42 @@ const allBelowExcl = getSiblingsFrame(refElement, Infinity, "bottom", false);
|
|
|
847
852
|
// <div>5</div> │ frame
|
|
848
853
|
// <div>6</div> ◄──┘
|
|
849
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
|
|
850
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
|
+
```
|
|
851
886
|
</details>
|
|
852
887
|
|
|
853
888
|
<br><br>
|
|
854
889
|
|
|
855
|
-
<!-- #
|
|
890
|
+
<!-- #region Math -->
|
|
856
891
|
## Math:
|
|
857
892
|
|
|
858
893
|
### clamp()
|
|
@@ -877,7 +912,6 @@ clamp(99999, 0, 10); // 10
|
|
|
877
912
|
clamp(-99999, -Infinity, 0); // -99999
|
|
878
913
|
clamp(99999, 0, Infinity); // 99999
|
|
879
914
|
```
|
|
880
|
-
|
|
881
915
|
</details>
|
|
882
916
|
|
|
883
917
|
<br>
|
|
@@ -908,7 +942,6 @@ mapRange(5, 0, 10, 0, 50); // 25
|
|
|
908
942
|
// for example, if 4 files of a total of 13 were downloaded:
|
|
909
943
|
mapRange(4, 0, 13, 0, 100); // 30.76923076923077
|
|
910
944
|
```
|
|
911
|
-
|
|
912
945
|
</details>
|
|
913
946
|
|
|
914
947
|
<br>
|
|
@@ -932,12 +965,11 @@ randRange(0, 10); // 4
|
|
|
932
965
|
randRange(10, 20); // 17
|
|
933
966
|
randRange(10); // 7
|
|
934
967
|
```
|
|
935
|
-
|
|
936
968
|
</details>
|
|
937
969
|
|
|
938
970
|
<br><br>
|
|
939
971
|
|
|
940
|
-
<!-- #
|
|
972
|
+
<!-- #region Misc -->
|
|
941
973
|
## Misc:
|
|
942
974
|
|
|
943
975
|
### DataStore
|
|
@@ -1100,7 +1132,6 @@ async function init() {
|
|
|
1100
1132
|
|
|
1101
1133
|
init();
|
|
1102
1134
|
```
|
|
1103
|
-
|
|
1104
1135
|
</details>
|
|
1105
1136
|
|
|
1106
1137
|
<br>
|
|
@@ -1108,7 +1139,7 @@ init();
|
|
|
1108
1139
|
### DataStoreSerializer
|
|
1109
1140
|
Usage:
|
|
1110
1141
|
```ts
|
|
1111
|
-
new DataStoreSerializer(stores: DataStore[], options
|
|
1142
|
+
new DataStoreSerializer(stores: DataStore[], options?: DataStoreSerializerOptions)
|
|
1112
1143
|
```
|
|
1113
1144
|
|
|
1114
1145
|
A class that manages serializing and deserializing (exporting and importing) one to infinite DataStore instances.
|
|
@@ -1201,8 +1232,13 @@ const serializer = new DataStoreSerializer([fooStore, barStore], {
|
|
|
1201
1232
|
});
|
|
1202
1233
|
|
|
1203
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:
|
|
1204
1240
|
const serializedData = await serializer.serialize();
|
|
1205
|
-
// create file and download it:
|
|
1241
|
+
// create a file and download it:
|
|
1206
1242
|
const blob = new Blob([serializedData], { type: "application/json" });
|
|
1207
1243
|
const url = URL.createObjectURL(blob);
|
|
1208
1244
|
const a = document.createElement("a");
|
|
@@ -1211,7 +1247,7 @@ async function exportMyDataPls() {
|
|
|
1211
1247
|
a.click();
|
|
1212
1248
|
a.remove();
|
|
1213
1249
|
|
|
1214
|
-
//
|
|
1250
|
+
// `serialize()` exports a stringified object that looks similar to this:
|
|
1215
1251
|
// [
|
|
1216
1252
|
// {
|
|
1217
1253
|
// "id": "foo-data",
|
|
@@ -1246,7 +1282,241 @@ async function importMyDataPls() {
|
|
|
1246
1282
|
```
|
|
1247
1283
|
</details>
|
|
1248
1284
|
|
|
1249
|
-
<br
|
|
1285
|
+
<br>
|
|
1286
|
+
|
|
1287
|
+
### Dialog
|
|
1288
|
+
Usage:
|
|
1289
|
+
```ts
|
|
1290
|
+
new Dialog(options: DialogOptions)
|
|
1291
|
+
```
|
|
1292
|
+
|
|
1293
|
+
A class that creates a customizable modal dialog with a title (optional), body and footer (optional).
|
|
1294
|
+
There are tons of options for customization, like changing the close behavior, translating strings and more.
|
|
1295
|
+
|
|
1296
|
+
The options object has the following properties:
|
|
1297
|
+
| Property | Description |
|
|
1298
|
+
| :-- | :-- |
|
|
1299
|
+
| `id: string` | A unique internal identification string for this instance. If two Dialogs share the same ID, they will overwrite each other. |
|
|
1300
|
+
| `width: number` | The target and maximum width of the dialog in pixels. |
|
|
1301
|
+
| `height: number` | The target and maximum height of the dialog in pixels. |
|
|
1302
|
+
| `renderBody: () => HTMLElement \| Promise<HTMLElement>` | Called to render the body of the dialog. |
|
|
1303
|
+
| `renderHeader?: () => HTMLElement \| Promise<HTMLElement>` | (Optional) Called to render the header of the dialog. Leave undefined for a blank header. |
|
|
1304
|
+
| `renderFooter?: () => HTMLElement \| Promise<HTMLElement>` | (Optional) Called to render the footer of the dialog. Leave undefined for no footer. |
|
|
1305
|
+
| `closeOnBgClick?: boolean` | (Optional) Whether the dialog should close when the background is clicked. Defaults to `true`. |
|
|
1306
|
+
| `closeOnEscPress?: boolean` | (Optional) Whether the dialog should close when the escape key is pressed. Defaults to `true`. |
|
|
1307
|
+
| `destroyOnClose?: boolean` | (Optional) Whether the dialog should be destroyed when it's closed. Defaults to `false`. |
|
|
1308
|
+
| `unmountOnClose?: boolean` | (Optional) Whether the dialog should be unmounted when it's closed. Defaults to `true`. Superseded by `destroyOnClose`. |
|
|
1309
|
+
| `removeListenersOnDestroy?: boolean` | (Optional) Whether all listeners should be removed when the dialog is destroyed. Defaults to `true`. |
|
|
1310
|
+
| `small?: boolean` | (Optional) Whether the dialog should have a smaller overall appearance. Defaults to `false`. |
|
|
1311
|
+
| `verticalAlign?: "top" \| "center" \| "bottom"` | (Optional) Where to align or anchor the dialog vertically. Defaults to `"center"`. |
|
|
1312
|
+
| `strings?: Partial<typeof defaultStrings>` | (Optional) Strings used in the dialog (used for translations). Defaults to the default English strings (importable with the name `defaultStrings`). |
|
|
1313
|
+
| `dialogCss?: string` | (Optional) CSS to apply to the dialog. Defaults to the default (importable with the name `defaultDialogCss`). |
|
|
1314
|
+
|
|
1315
|
+
Methods:
|
|
1316
|
+
`open(): Promise<void>`
|
|
1317
|
+
Opens the dialog.
|
|
1318
|
+
|
|
1319
|
+
`close(): void`
|
|
1320
|
+
Closes the dialog.
|
|
1321
|
+
|
|
1322
|
+
`mount(): Promise<void>`
|
|
1323
|
+
Mounts the dialog to the DOM by calling the render functions provided in the options object.
|
|
1324
|
+
Can be done before opening the dialog to avoid a delay.
|
|
1325
|
+
|
|
1326
|
+
`unmount(): void`
|
|
1327
|
+
Unmounts the dialog from the DOM.
|
|
1328
|
+
|
|
1329
|
+
`remount(): Promise<void>`
|
|
1330
|
+
Unmounts and mounts the dialog again.
|
|
1331
|
+
The render functions in the options object will be called again.
|
|
1332
|
+
May cause a flickering effect due to the rendering delay.
|
|
1333
|
+
|
|
1334
|
+
`isOpen(): boolean`
|
|
1335
|
+
Returns `true` if the dialog is open, else `false`.
|
|
1336
|
+
|
|
1337
|
+
`isMounted(): boolean`
|
|
1338
|
+
Returns `true` if the dialog is mounted, else `false`.
|
|
1339
|
+
|
|
1340
|
+
`destroy(): void`
|
|
1341
|
+
Destroys the dialog.
|
|
1342
|
+
Removes all listeners and unmounts the dialog by default.
|
|
1343
|
+
|
|
1344
|
+
`static getCurrentDialogId(): string`
|
|
1345
|
+
Static method that returns the ID of the currently open dialog.
|
|
1346
|
+
Needs to be called without creating an instance of the class.
|
|
1347
|
+
|
|
1348
|
+
`static getOpenDialogs(): string[]`
|
|
1349
|
+
Static method that returns an array of the IDs of all open dialogs.
|
|
1350
|
+
Needs to be called without creating an instance of the class.
|
|
1351
|
+
|
|
1352
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
1353
|
+
|
|
1354
|
+
```ts
|
|
1355
|
+
import { Dialog } from "@sv443-network/userutils";
|
|
1356
|
+
|
|
1357
|
+
const fooDialog = new Dialog({
|
|
1358
|
+
id: "foo-dialog",
|
|
1359
|
+
width: 400,
|
|
1360
|
+
height: 300,
|
|
1361
|
+
renderHeader() {
|
|
1362
|
+
const header = document.createElement("div");
|
|
1363
|
+
header.textContent = "This is the header";
|
|
1364
|
+
return header;
|
|
1365
|
+
},
|
|
1366
|
+
renderBody() {
|
|
1367
|
+
const body = document.createElement("div");
|
|
1368
|
+
body.textContent = "This is the body";
|
|
1369
|
+
return body;
|
|
1370
|
+
},
|
|
1371
|
+
renderFooter() {
|
|
1372
|
+
const footer = document.createElement("div");
|
|
1373
|
+
footer.textContent = "This is the footer";
|
|
1374
|
+
return footer;
|
|
1375
|
+
},
|
|
1376
|
+
closeOnBgClick: true,
|
|
1377
|
+
closeOnEscPress: true,
|
|
1378
|
+
destroyOnClose: false,
|
|
1379
|
+
unmountOnClose: true,
|
|
1380
|
+
removeListenersOnDestroy: true,
|
|
1381
|
+
small: false,
|
|
1382
|
+
verticalAlign: "center",
|
|
1383
|
+
strings: {
|
|
1384
|
+
closeDialogTooltip: "Click to close",
|
|
1385
|
+
},
|
|
1386
|
+
dialogCss: getMyCustomDialogCss(),
|
|
1387
|
+
});
|
|
1388
|
+
|
|
1389
|
+
fooDialog.on("close", () => {
|
|
1390
|
+
console.log("Dialog closed");
|
|
1391
|
+
});
|
|
1392
|
+
|
|
1393
|
+
fooDialog.open();
|
|
1394
|
+
```
|
|
1395
|
+
</details>
|
|
1396
|
+
|
|
1397
|
+
<br>
|
|
1398
|
+
|
|
1399
|
+
### NanoEmitter
|
|
1400
|
+
Usage:
|
|
1401
|
+
```ts
|
|
1402
|
+
new NanoEmitter<TEventMap = EventsMap>(options?: NanoEmitterOptions): NanoEmitter<TEventMap>
|
|
1403
|
+
```
|
|
1404
|
+
|
|
1405
|
+
A class that provides a minimalistic event emitter with a tiny footprint powered by [nanoevents.](https://npmjs.com/package/nanoevents)
|
|
1406
|
+
The `TEventMap` generic is used to define the events that can be emitted and listened to.
|
|
1407
|
+
|
|
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.
|
|
1410
|
+
|
|
1411
|
+
The options object has the following properties:
|
|
1412
|
+
| Property | Description |
|
|
1413
|
+
| :-- | :-- |
|
|
1414
|
+
| `publicEmit?: boolean` | (Optional) If set to true, allows emitting events through the public method `emit()` (`false` by default). |
|
|
1415
|
+
|
|
1416
|
+
Methods:
|
|
1417
|
+
`on<K extends keyof TEventMap>(event: K, listener: TEventMap[K]): void`
|
|
1418
|
+
Registers a listener function for the given event.
|
|
1419
|
+
May be called multiple times for the same event.
|
|
1420
|
+
|
|
1421
|
+
`once<K extends keyof TEventMap>(event: K, listener: TEventMap[K]): void`
|
|
1422
|
+
Registers a listener function for the given event that will only be called once.
|
|
1423
|
+
|
|
1424
|
+
`emit<K extends keyof TEventMap>(event: K, ...args: Parameters<TEventMap[K]>): boolean`
|
|
1425
|
+
Emits an event with the given arguments from outside the class instance if `publicEmit` is set to `true`.
|
|
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.
|
|
1428
|
+
|
|
1429
|
+
`unsubscribeAll(): void`
|
|
1430
|
+
Removes all listeners from all events.
|
|
1431
|
+
|
|
1432
|
+
<br>
|
|
1433
|
+
|
|
1434
|
+
<details><summary><b>Object oriented example - click to view</b></summary>
|
|
1435
|
+
|
|
1436
|
+
```ts
|
|
1437
|
+
import { NanoEmitter } from "@sv443-network/userutils";
|
|
1438
|
+
|
|
1439
|
+
// map of events for strong typing - the functions always return void
|
|
1440
|
+
interface MyEventMap {
|
|
1441
|
+
foo: (bar: string) => void;
|
|
1442
|
+
baz: (qux: number) => void;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
class MyClass extends NanoEmitter<MyEventMap> {
|
|
1446
|
+
constructor() {
|
|
1447
|
+
super({
|
|
1448
|
+
// allow emitting events from outside the class
|
|
1449
|
+
publicEmit: true,
|
|
1450
|
+
});
|
|
1451
|
+
|
|
1452
|
+
this.once("baz", (qux) => {
|
|
1453
|
+
console.log("baz event (inside, once):", qux);
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
public doStuff() {
|
|
1458
|
+
this.emit("foo", "hello");
|
|
1459
|
+
this.emit("baz", 42);
|
|
1460
|
+
this.emit("foo", "world");
|
|
1461
|
+
this.emit("baz", 69);
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
const myInstance = new MyClass();
|
|
1466
|
+
myInstance.doStuff();
|
|
1467
|
+
|
|
1468
|
+
myInstance.on("foo", (bar) => {
|
|
1469
|
+
console.log("foo event (outside):", bar);
|
|
1470
|
+
});
|
|
1471
|
+
|
|
1472
|
+
myInstance.emit("baz", "hello from the outside");
|
|
1473
|
+
|
|
1474
|
+
myInstance.unsubscribeAll();
|
|
1475
|
+
```
|
|
1476
|
+
</details>
|
|
1477
|
+
|
|
1478
|
+
<br>
|
|
1479
|
+
|
|
1480
|
+
<details><summary><b>Functional example - click to view</b></summary>
|
|
1481
|
+
|
|
1482
|
+
```ts
|
|
1483
|
+
import { NanoEmitter } from "@sv443-network/userutils";
|
|
1484
|
+
|
|
1485
|
+
// map of events for strong typing - the functions always return void
|
|
1486
|
+
interface MyEventMap {
|
|
1487
|
+
foo: (bar: string) => void;
|
|
1488
|
+
baz: (qux: number) => void;
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
const myEmitter = new NanoEmitter<MyEventMap>({
|
|
1492
|
+
// allow emitting events from outside the class
|
|
1493
|
+
publicEmit: true,
|
|
1494
|
+
});
|
|
1495
|
+
|
|
1496
|
+
myEmitter.on("foo", (bar) => {
|
|
1497
|
+
console.log("foo event:", bar);
|
|
1498
|
+
});
|
|
1499
|
+
|
|
1500
|
+
myEmitter.once("baz", (qux) => {
|
|
1501
|
+
console.log("baz event (once):", qux);
|
|
1502
|
+
});
|
|
1503
|
+
|
|
1504
|
+
function doStuff() {
|
|
1505
|
+
myEmitter.emit("foo", "hello");
|
|
1506
|
+
myEmitter.emit("baz", 42);
|
|
1507
|
+
myEmitter.emit("foo", "world");
|
|
1508
|
+
myEmitter.emit("baz", 69);
|
|
1509
|
+
|
|
1510
|
+
myEmitter.emit("foo", "hello from the outside");
|
|
1511
|
+
|
|
1512
|
+
myEmitter.unsubscribeAll();
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
doStuff();
|
|
1516
|
+
```
|
|
1517
|
+
</details>
|
|
1518
|
+
|
|
1519
|
+
<br>
|
|
1250
1520
|
|
|
1251
1521
|
### autoPlural()
|
|
1252
1522
|
Usage:
|
|
@@ -1272,7 +1542,6 @@ autoPlural("apple", [1, 2]); // "apples"
|
|
|
1272
1542
|
const items = [1, 2, 3, 4, "foo", "bar"];
|
|
1273
1543
|
console.log(`Found ${items.length} ${autoPlural("item", items)}`); // "Found 6 items"
|
|
1274
1544
|
```
|
|
1275
|
-
|
|
1276
1545
|
</details>
|
|
1277
1546
|
|
|
1278
1547
|
<br>
|
|
@@ -1296,7 +1565,6 @@ async function run() {
|
|
|
1296
1565
|
console.log("World");
|
|
1297
1566
|
}
|
|
1298
1567
|
```
|
|
1299
|
-
|
|
1300
1568
|
</details>
|
|
1301
1569
|
|
|
1302
1570
|
<br>
|
|
@@ -1342,7 +1610,6 @@ const myFunc = debounce((event) => {
|
|
|
1342
1610
|
|
|
1343
1611
|
document.body.addEventListener("scroll", myFunc);
|
|
1344
1612
|
```
|
|
1345
|
-
|
|
1346
1613
|
</details>
|
|
1347
1614
|
|
|
1348
1615
|
<br>
|
|
@@ -1377,7 +1644,6 @@ fetchAdvanced("https://jokeapi.dev/joke/Any?safe-mode", {
|
|
|
1377
1644
|
console.error("Fetch error:", err);
|
|
1378
1645
|
});
|
|
1379
1646
|
```
|
|
1380
|
-
|
|
1381
1647
|
</details>
|
|
1382
1648
|
|
|
1383
1649
|
<br>
|
|
@@ -1405,7 +1671,6 @@ insertValues("Testing %1", { toString: () => "foo" }); // "Testing foo"
|
|
|
1405
1671
|
const values = ["foo", "bar", "baz"];
|
|
1406
1672
|
insertValues("Testing %1, %2, %3 and %4", ...values); // "Testing foo, bar and baz and %4"
|
|
1407
1673
|
```
|
|
1408
|
-
|
|
1409
1674
|
</details>
|
|
1410
1675
|
|
|
1411
1676
|
<br>
|
|
@@ -1446,7 +1711,6 @@ const barDeflate = await compress("Hello, World!".repeat(20), "deflate");
|
|
|
1446
1711
|
console.log(fooDeflate); // "eJzzSM3JyddRCM8vyklRBAAfngRq"
|
|
1447
1712
|
console.log(barDeflate); // "eJzzSM3JyddRCM8vyklR9BiZHAAIEVg1"
|
|
1448
1713
|
```
|
|
1449
|
-
|
|
1450
1714
|
</details>
|
|
1451
1715
|
|
|
1452
1716
|
<br>
|
|
@@ -1477,7 +1741,6 @@ const decompressed = await decompress(compressed, "gzip");
|
|
|
1477
1741
|
|
|
1478
1742
|
console.log(decompressed); // "Hello, World!"
|
|
1479
1743
|
```
|
|
1480
|
-
|
|
1481
1744
|
</details>
|
|
1482
1745
|
|
|
1483
1746
|
<br>
|
|
@@ -1544,12 +1807,11 @@ randomId(10, 2); // "1010001101" (length 10, radix 2)
|
|
|
1544
1807
|
randomId(10, 10); // "0183428506" (length 10, radix 10)
|
|
1545
1808
|
randomId(10, 36); // "z46jfpa37r" (length 10, radix 36)
|
|
1546
1809
|
```
|
|
1547
|
-
|
|
1548
1810
|
</details>
|
|
1549
1811
|
|
|
1550
1812
|
<br><br>
|
|
1551
1813
|
|
|
1552
|
-
<!-- #
|
|
1814
|
+
<!-- #region Arrays -->
|
|
1553
1815
|
## Arrays:
|
|
1554
1816
|
|
|
1555
1817
|
### randomItem()
|
|
@@ -1569,7 +1831,6 @@ import { randomItem } from "@sv443-network/userutils";
|
|
|
1569
1831
|
randomItem(["foo", "bar", "baz"]); // "bar"
|
|
1570
1832
|
randomItem([ ]); // undefined
|
|
1571
1833
|
```
|
|
1572
|
-
|
|
1573
1834
|
</details>
|
|
1574
1835
|
|
|
1575
1836
|
<br>
|
|
@@ -1596,7 +1857,6 @@ const [item, index] = randomItemIndex(["foo", "bar", "baz"]); // ["bar", 1]
|
|
|
1596
1857
|
// or if you only want the index:
|
|
1597
1858
|
const [, index] = randomItemIndex(["foo", "bar", "baz"]); // 1
|
|
1598
1859
|
```
|
|
1599
|
-
|
|
1600
1860
|
</details>
|
|
1601
1861
|
|
|
1602
1862
|
<br>
|
|
@@ -1619,7 +1879,6 @@ const arr = ["foo", "bar", "baz"];
|
|
|
1619
1879
|
takeRandomItem(arr); // "bar"
|
|
1620
1880
|
console.log(arr); // ["foo", "baz"]
|
|
1621
1881
|
```
|
|
1622
|
-
|
|
1623
1882
|
</details>
|
|
1624
1883
|
|
|
1625
1884
|
<br>
|
|
@@ -1645,12 +1904,11 @@ console.log(randomizeArray(foo)); // [4, 5, 2, 1, 6, 3]
|
|
|
1645
1904
|
|
|
1646
1905
|
console.log(foo); // [1, 2, 3, 4, 5, 6] - original array is not mutated
|
|
1647
1906
|
```
|
|
1648
|
-
|
|
1649
1907
|
</details>
|
|
1650
1908
|
|
|
1651
1909
|
<br><br>
|
|
1652
1910
|
|
|
1653
|
-
<!-- #
|
|
1911
|
+
<!-- #region Translation -->
|
|
1654
1912
|
## Translation:
|
|
1655
1913
|
This is a very lightweight translation function that can be used to translate simple strings.
|
|
1656
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.
|
|
@@ -1698,7 +1956,6 @@ tr.setLanguage("de");
|
|
|
1698
1956
|
console.log(tr("welcome")); // "Willkommen"
|
|
1699
1957
|
console.log(tr("welcome_name", "John")); // "Willkommen, John"
|
|
1700
1958
|
```
|
|
1701
|
-
|
|
1702
1959
|
</details>
|
|
1703
1960
|
|
|
1704
1961
|
<br>
|
|
@@ -1770,34 +2027,33 @@ tr.addLanguage("de-AT", {
|
|
|
1770
2027
|
// example for custom pluralization:
|
|
1771
2028
|
|
|
1772
2029
|
tr.addLanguage("en", {
|
|
1773
|
-
"
|
|
1774
|
-
"
|
|
1775
|
-
"
|
|
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",
|
|
1776
2033
|
});
|
|
1777
2034
|
|
|
1778
|
-
/** Returns the custom pluralization identifier for the given number of items (or size of Array/NodeList) */
|
|
1779
|
-
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 }) {
|
|
1780
2037
|
if(typeof num !== "number")
|
|
1781
2038
|
num = num.length;
|
|
1782
2039
|
|
|
1783
2040
|
if(num === 0)
|
|
1784
|
-
return
|
|
2041
|
+
return `${key}-0`;
|
|
1785
2042
|
else if(num === 1)
|
|
1786
|
-
return
|
|
2043
|
+
return `${key}-1`;
|
|
1787
2044
|
else
|
|
1788
|
-
return
|
|
2045
|
+
return `${key}-n`;
|
|
1789
2046
|
};
|
|
1790
2047
|
|
|
1791
2048
|
const items = [];
|
|
1792
|
-
tr(
|
|
2049
|
+
console.log(tr(pl("cart_items_added", items), items.length)); // "No items were added to the cart"
|
|
1793
2050
|
|
|
1794
2051
|
items.push("foo");
|
|
1795
|
-
tr(
|
|
2052
|
+
console.log(tr(pl("cart_items_added", items), items.length)); // "Added 1 item to the cart"
|
|
1796
2053
|
|
|
1797
2054
|
items.push("bar");
|
|
1798
|
-
tr(
|
|
2055
|
+
console.log(tr(pl("cart_items_added", items), items.length)); // "Added 2 items to the cart"
|
|
1799
2056
|
```
|
|
1800
|
-
|
|
1801
2057
|
</details>
|
|
1802
2058
|
|
|
1803
2059
|
<br>
|
|
@@ -1826,7 +2082,101 @@ If no language has been set yet, it will return undefined.
|
|
|
1826
2082
|
|
|
1827
2083
|
<br><br>
|
|
1828
2084
|
|
|
1829
|
-
|
|
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 -->
|
|
1830
2180
|
## Utility types:
|
|
1831
2181
|
UserUtils also offers some utility types that can be used in TypeScript projects.
|
|
1832
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.
|
|
@@ -1862,7 +2212,6 @@ logSomething(fooObject); // "Log: hello world"
|
|
|
1862
2212
|
|
|
1863
2213
|
logSomething(barObject); // Type error
|
|
1864
2214
|
```
|
|
1865
|
-
|
|
1866
2215
|
</details>
|
|
1867
2216
|
|
|
1868
2217
|
<br>
|
|
@@ -1893,7 +2242,6 @@ function somethingElse(array: NonEmptyArray) {
|
|
|
1893
2242
|
|
|
1894
2243
|
logFirstItem(["04abc", "69"]); // 4
|
|
1895
2244
|
```
|
|
1896
|
-
|
|
1897
2245
|
</details>
|
|
1898
2246
|
|
|
1899
2247
|
<br>
|
|
@@ -1918,7 +2266,6 @@ function convertToNumber<T extends string>(str: NonEmptyString<T>) {
|
|
|
1918
2266
|
convertToNumber("04abc"); // "4"
|
|
1919
2267
|
convertToNumber(""); // type error: Argument of type 'string' is not assignable to parameter of type 'never'
|
|
1920
2268
|
```
|
|
1921
|
-
|
|
1922
2269
|
</details>
|
|
1923
2270
|
|
|
1924
2271
|
<br>
|
|
@@ -1946,12 +2293,11 @@ foo("a"); // included in autocomplete, no type error
|
|
|
1946
2293
|
foo(""); // *not* included in autocomplete, still no type error
|
|
1947
2294
|
foo(1); // type error: Argument of type '1' is not assignable to parameter of type 'LooseUnion<"a" | "b" | "c">'
|
|
1948
2295
|
```
|
|
1949
|
-
|
|
1950
2296
|
</details>
|
|
1951
2297
|
|
|
1952
2298
|
<br><br><br><br>
|
|
1953
2299
|
|
|
1954
|
-
<!-- #
|
|
2300
|
+
<!-- #region Footer -->
|
|
1955
2301
|
<div style="text-align: center;" align="center">
|
|
1956
2302
|
|
|
1957
2303
|
Made with ❤️ by [Sv443](https://github.com/Sv443)
|