@sv443-network/userutils 2.0.1 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/README.md +511 -218
- package/dist/index.global.js +198 -114
- package/dist/index.js +197 -113
- package/dist/index.mjs +194 -109
- package/dist/lib/{config.d.ts → ConfigManager.d.ts} +8 -5
- package/dist/lib/SelectorObserver.d.ts +84 -0
- package/dist/lib/array.d.ts +6 -4
- package/dist/lib/dom.d.ts +8 -53
- package/dist/lib/index.d.ts +2 -2
- package/dist/lib/math.d.ts +12 -5
- package/dist/lib/misc.d.ts +26 -5
- package/dist/lib/translation.d.ts +2 -2
- package/package.json +10 -4
- package/dist/lib/onSelector.d.ts +0 -40
package/README.md
CHANGED
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
<div style="text-align: center;" align="center">
|
|
2
2
|
|
|
3
|
+
<!-- #MARKER Description -->
|
|
3
4
|
## UserUtils
|
|
4
5
|
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
6
|
|
|
6
|
-
Contains builtin TypeScript declarations.
|
|
7
|
+
Contains builtin TypeScript declarations. Fully web compatible and supports ESM and CJS imports and global declaration.
|
|
7
8
|
If you like using this library, please consider [supporting the development ❤️](https://github.com/sponsors/Sv443)
|
|
8
9
|
|
|
10
|
+
<br>
|
|
11
|
+
<sub>
|
|
12
|
+
|
|
13
|
+
View the documentation of previous major releases: [3.0.0](https://github.com/Sv443-Network/UserUtils/blob/v3.0.0/README.md), [2.0.1](https://github.com/Sv443-Network/UserUtils/blob/v2.0.1/README.md), [1.2.0](https://github.com/Sv443-Network/UserUtils/blob/v1.2.0/README.md), [0.5.3](https://github.com/Sv443-Network/UserUtils/blob/v0.5.3/README.md)
|
|
14
|
+
|
|
15
|
+
</sub>
|
|
9
16
|
</div>
|
|
10
17
|
<br>
|
|
11
18
|
|
|
19
|
+
<!-- #MARKER Table of Contents -->
|
|
12
20
|
## Table of Contents:
|
|
13
21
|
- [**Installation**](#installation)
|
|
14
|
-
- [**Preamble**](#preamble)
|
|
22
|
+
- [**Preamble** (info about the documentation)](#preamble)
|
|
15
23
|
- [**License**](#license)
|
|
16
24
|
- [**Features**](#features)
|
|
17
25
|
- [**DOM:**](#dom)
|
|
18
|
-
- [
|
|
19
|
-
- [initOnSelector()](#initonselector) - needs to be called once to be able to use `onSelector()`
|
|
20
|
-
- [getSelectorMap()](#getselectormap) - returns all currently registered selectors, listeners and options
|
|
26
|
+
- [SelectorObserver](#selectorobserver) - class that manages listeners that are called when selectors are found in the DOM
|
|
21
27
|
- [getUnsafeWindow()](#getunsafewindow) - get the unsafeWindow object or fall back to the regular window object
|
|
22
28
|
- [insertAfter()](#insertafter) - insert an element as a sibling after another element
|
|
23
29
|
- [addParent()](#addparent) - add a parent element around another element
|
|
@@ -26,19 +32,21 @@ If you like using this library, please consider [supporting the development ❤
|
|
|
26
32
|
- [openInNewTab()](#openinnewtab) - open a link in a new tab
|
|
27
33
|
- [interceptEvent()](#interceptevent) - conditionally intercepts events registered by `addEventListener()` on any given EventTarget object
|
|
28
34
|
- [interceptWindowEvent()](#interceptwindowevent) - conditionally intercepts events registered by `addEventListener()` on the window object
|
|
29
|
-
- [amplifyMedia()](#amplifymedia) - amplify an audio or video element's volume past the maximum of 100%
|
|
30
35
|
- [isScrollable()](#isscrollable) - check if an element has a horizontal or vertical scroll bar
|
|
31
36
|
- [**Math:**](#math)
|
|
32
37
|
- [clamp()](#clamp) - constrain a number between a min and max value
|
|
33
38
|
- [mapRange()](#maprange) - map a number from one range to the same spot in another range
|
|
34
39
|
- [randRange()](#randrange) - generate a random number between a min and max boundary
|
|
40
|
+
- [randomId()](#randomid) - generate a random ID of a given length and radix
|
|
35
41
|
- [**Misc:**](#misc)
|
|
36
|
-
- [ConfigManager
|
|
42
|
+
- [ConfigManager](#configmanager) - class that manages persistent userscript configurations, including data migration
|
|
37
43
|
- [autoPlural()](#autoplural) - automatically pluralize a string
|
|
38
44
|
- [pauseFor()](#pausefor) - pause the execution of a function for a given amount of time
|
|
39
45
|
- [debounce()](#debounce) - call a function only once, after a given amount of time
|
|
40
46
|
- [fetchAdvanced()](#fetchadvanced) - wrapper around the fetch API with a timeout option
|
|
41
47
|
- [insertValues()](#insertvalues) - insert values into a string at specified placeholders
|
|
48
|
+
- [compress()](#compress) - compress a string with Gzip or Deflate
|
|
49
|
+
- [decompress()](#decompress) - decompress a previously compressed string
|
|
42
50
|
- [**Arrays:**](#arrays)
|
|
43
51
|
- [randomItem()](#randomitem) - returns a random item from an array
|
|
44
52
|
- [randomItemIndex()](#randomitemindex) - returns a tuple of a random item and its index from an array
|
|
@@ -51,9 +59,13 @@ If you like using this library, please consider [supporting the development ❤
|
|
|
51
59
|
- [tr.getLanguage()](#trgetlanguage) - returns the currently active language
|
|
52
60
|
- [**Utility types for TypeScript:**](#utility-types)
|
|
53
61
|
- [Stringifiable](#stringifiable) - any value that is a string or can be converted to one (implicitly or explicitly)
|
|
62
|
+
- [NonEmptyArray](#nonemptyarray) - any array that should have at least one item
|
|
63
|
+
- [NonEmptyString](#nonemptystring) - any string that should have at least one character
|
|
64
|
+
- [LooseUnion](#looseunion) - a union that gives autocomplete in the IDE but also allows any other value of the same type
|
|
54
65
|
|
|
55
66
|
<br><br>
|
|
56
67
|
|
|
68
|
+
<!-- #MARKER Installation -->
|
|
57
69
|
## Installation:
|
|
58
70
|
- If you are using a bundler like webpack, you can install this package using npm:
|
|
59
71
|
```
|
|
@@ -75,7 +87,11 @@ If you like using this library, please consider [supporting the development ❤
|
|
|
75
87
|
```
|
|
76
88
|
// @require https://greasyfork.org/scripts/472956-userutils/code/UserUtils.js
|
|
77
89
|
```
|
|
78
|
-
|
|
90
|
+
```
|
|
91
|
+
// @require https://openuserjs.org/src/libs/Sv443/UserUtils.js
|
|
92
|
+
```
|
|
93
|
+
(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))
|
|
94
|
+
|
|
79
95
|
Then, access the functions on the global variable `UserUtils`:
|
|
80
96
|
```ts
|
|
81
97
|
UserUtils.addGlobalStyle("body { background-color: red; }");
|
|
@@ -88,164 +104,316 @@ If you like using this library, please consider [supporting the development ❤
|
|
|
88
104
|
|
|
89
105
|
<br><br>
|
|
90
106
|
|
|
107
|
+
<!-- #MARKER Preamble -->
|
|
91
108
|
## Preamble:
|
|
92
109
|
This library is written in TypeScript and contains builtin TypeScript declarations.
|
|
93
110
|
|
|
94
111
|
Each feature has example code that can be expanded by clicking on the text "Example - click to view".
|
|
95
|
-
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).
|
|
96
|
-
If the usage section contains multiple
|
|
112
|
+
The usages and examples are written in TypeScript and use ESM import syntax, but the library can also be used in plain JavaScript after removing the type annotations (and changing the imports if you are using CommonJS or the global declaration).
|
|
113
|
+
If the usage section contains multiple usages of the function, each occurrence represents an overload and you can choose which one you want to use.
|
|
97
114
|
|
|
98
115
|
Some features require the `@run-at` or `@grant` directives to be tweaked in the userscript header or have other requirements.
|
|
99
116
|
Their documentation will contain a section marked by a warning emoji (⚠️) that will go into more detail.
|
|
100
117
|
|
|
101
118
|
<br><br>
|
|
102
119
|
|
|
120
|
+
<!-- #MARKER License -->
|
|
103
121
|
## License:
|
|
104
122
|
This library is licensed under the MIT License.
|
|
105
123
|
See the [license file](./LICENSE.txt) for details.
|
|
106
124
|
|
|
107
125
|
<br><br>
|
|
108
126
|
|
|
127
|
+
<!-- #MARKER Features -->
|
|
109
128
|
## Features:
|
|
110
129
|
|
|
111
130
|
<br>
|
|
112
131
|
|
|
132
|
+
<!-- #SECTION DOM -->
|
|
113
133
|
## DOM:
|
|
114
134
|
|
|
115
|
-
###
|
|
135
|
+
### SelectorObserver
|
|
116
136
|
Usage:
|
|
117
137
|
```ts
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
all?: boolean,
|
|
121
|
-
continuous?: boolean,
|
|
122
|
-
}): void
|
|
138
|
+
new SelectorObserver(baseElement: Element, options?: SelectorObserverOptions)
|
|
139
|
+
new SelectorObserver(baseElementSelector: string, options?: SelectorObserverOptions)
|
|
123
140
|
```
|
|
141
|
+
|
|
142
|
+
A class that manages listeners that are called when elements at given selectors are found in the DOM.
|
|
143
|
+
This is useful for userscripts that need to wait for elements to be added to the DOM at an indeterminate point in time before they can be interacted with.
|
|
124
144
|
|
|
125
|
-
|
|
126
|
-
If
|
|
145
|
+
The constructor takes a `baseElement`, which is a parent of the elements you want to observe.
|
|
146
|
+
If a selector string is passed instead, it will be used to find the element.
|
|
147
|
+
If you want to observe the entire document, you can pass `document.body`
|
|
148
|
+
|
|
149
|
+
The `options` parameter is optional and will be passed to the MutationObserver that is used internally.
|
|
150
|
+
The default options are `{ childList: true, subtree: true }` - you may see the [MutationObserver.observe() documentation](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe#options) for more information and a list of options.
|
|
151
|
+
For example, if you want to trigger the listeners when certain attributes change, pass `{ attributes: true, attributeFilter: ["class", "data-my-attribute"] }`
|
|
152
|
+
Additionally, there are the following extra options:
|
|
153
|
+
- `defaultDebounce` - if set to a number, this debounce will be applied to every listener that doesn't have a custom debounce set (defaults to 0)
|
|
127
154
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
155
|
+
⚠️ Make sure to call `enable()` to actually start observing. This will need to be done after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired) **and** as soon as the `baseElement` or `baseElementSelector` is available.
|
|
156
|
+
|
|
157
|
+
<br>
|
|
158
|
+
|
|
159
|
+
#### Methods:
|
|
160
|
+
`addListener<TElement = HTMLElement>(selector: string, options: SelectorListenerOptions): void`
|
|
161
|
+
Adds a listener (specified in `options.listener`) for the given selector that will be called once the selector exists in the DOM. It will be passed the element(s) that match the selector as the only argument.
|
|
162
|
+
The listener will be called immediately if the selector already exists in the DOM.
|
|
163
|
+
|
|
164
|
+
> `options.listener` is the only required property of the `options` object.
|
|
165
|
+
> It is a function that will be called once the selector exists in the DOM.
|
|
166
|
+
> It will be passed the found element or NodeList of elements, depending on if `options.all` is set to true or false.
|
|
131
167
|
|
|
132
|
-
If `
|
|
168
|
+
> If `options.all` is set to true, querySelectorAll() will be used instead and the listener will be passed a `NodeList` of matching elements.
|
|
169
|
+
> This will also include elements that were already found in a previous listener call.
|
|
170
|
+
> If set to false (default), querySelector() will be used and only the first matching element will be returned.
|
|
133
171
|
|
|
134
|
-
|
|
135
|
-
|
|
172
|
+
> If `options.continuous` is set to true, the listener will not be deregistered after it was called once (defaults to false).
|
|
173
|
+
>
|
|
174
|
+
> ⚠️ You should keep usage of this option to a minimum, as it will cause the listener to be called every time the selector is *checked for and found* and this can stack up quite quickly.
|
|
175
|
+
> ⚠️ You should try to only use this option on SelectorObserver instances that are scoped really low in the DOM tree to prevent as many selector checks as possible from being triggered.
|
|
176
|
+
> ⚠️ I also recommend always setting a debounce time (see constructor or below) if you use this option.
|
|
136
177
|
|
|
137
|
-
|
|
138
|
-
|
|
178
|
+
> If `options.debounce` is set to a number above 0, the listener will be debounced by that amount of milliseconds (defaults to 0).
|
|
179
|
+
> E.g. if the debounce time is set to 200 and the selector is found twice within 100ms, only the last call of the listener will be executed.
|
|
139
180
|
|
|
140
|
-
|
|
181
|
+
> When using TypeScript, the generic `TElement` can be used to specify the type of the element(s) that the listener will return.
|
|
182
|
+
> It will default to HTMLElement if left undefined.
|
|
141
183
|
|
|
142
|
-
<
|
|
143
|
-
|
|
144
|
-
```ts
|
|
145
|
-
import { initOnSelector, onSelector } from "@sv443-network/userutils";
|
|
146
|
-
|
|
147
|
-
document.addEventListener("DOMContentLoaded", initOnSelector);
|
|
184
|
+
<br>
|
|
148
185
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
continuous: true,
|
|
156
|
-
});
|
|
186
|
+
`enable(immediatelyCheckSelectors?: boolean): boolean`
|
|
187
|
+
Enables the observation of the child elements for the first time or if it was disabled before.
|
|
188
|
+
`immediatelyCheckSelectors` is set to true by default, which means all previously registered selectors will be checked. Set to false to only check them on the first detected mutation.
|
|
189
|
+
Returns true if the observation was enabled, false if it was already enabled or the passed `baseElementSelector` couldn't be found.
|
|
190
|
+
|
|
191
|
+
<br>
|
|
157
192
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
});
|
|
164
|
-
```
|
|
193
|
+
`disable(): void`
|
|
194
|
+
Disables the observation of the child elements.
|
|
195
|
+
If selectors are currently being checked, the current selector will be finished before disabling.
|
|
196
|
+
|
|
197
|
+
<br>
|
|
165
198
|
|
|
166
|
-
|
|
199
|
+
`isEnabled(): boolean`
|
|
200
|
+
Returns whether the observation of the child elements is currently enabled.
|
|
201
|
+
|
|
202
|
+
<br>
|
|
167
203
|
|
|
204
|
+
`clearListeners(): void`
|
|
205
|
+
Removes all listeners for all selectors.
|
|
206
|
+
|
|
168
207
|
<br>
|
|
169
208
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
```ts
|
|
173
|
-
initOnSelector(options?: MutationObserverInit): void
|
|
174
|
-
```
|
|
209
|
+
`removeAllListeners(selector: string): boolean`
|
|
210
|
+
Removes all listeners for the given selector.
|
|
175
211
|
|
|
176
|
-
|
|
177
|
-
By default, this only checks if elements are added or removed (at any depth).
|
|
212
|
+
<br>
|
|
178
213
|
|
|
179
|
-
|
|
214
|
+
`removeListener(selector: string, options: SelectorListenerOptions): boolean`
|
|
215
|
+
Removes a specific listener for the given selector and options.
|
|
180
216
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
> Set `options.characterData` to `true` to also check for character data changes on every single descendant of the `<body>` (defaults to false).
|
|
186
|
-
>
|
|
187
|
-
> ⚠️ Using these extra options can have a performance impact on larger sites or sites with a constantly changing DOM.
|
|
217
|
+
<br>
|
|
218
|
+
|
|
219
|
+
`getAllListeners(): Map<string, SelectorListenerOptions[]>`
|
|
220
|
+
Returns a Map of all selectors and their listeners.
|
|
188
221
|
|
|
189
|
-
<
|
|
222
|
+
<br>
|
|
223
|
+
|
|
224
|
+
`getListeners(selector: string): SelectorListenerOptions[] | undefined`
|
|
225
|
+
Returns all listeners for the given selector or undefined if there are none.
|
|
226
|
+
|
|
227
|
+
<br>
|
|
228
|
+
|
|
229
|
+
<details><summary><b>Examples - click to view</b></summary>
|
|
230
|
+
|
|
231
|
+
#### Basic usage:
|
|
190
232
|
|
|
191
233
|
```ts
|
|
192
|
-
import {
|
|
234
|
+
import { SelectorObserver } from "@sv443-network/userutils";
|
|
235
|
+
|
|
236
|
+
// adding a single-shot listener before the element exists:
|
|
237
|
+
const fooObserver = new SelectorObserver("body");
|
|
238
|
+
|
|
239
|
+
fooObserver.addListener("#my-element", {
|
|
240
|
+
listener: (element) => {
|
|
241
|
+
console.log("Element found:", element);
|
|
242
|
+
},
|
|
243
|
+
});
|
|
193
244
|
|
|
194
245
|
document.addEventListener("DOMContentLoaded", () => {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
246
|
+
// starting observation after the <body> element is available:
|
|
247
|
+
fooObserver.enable();
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
// adding custom observer options:
|
|
251
|
+
|
|
252
|
+
const barObserver = new SelectorObserver(document.body, {
|
|
253
|
+
// only check if the following attributes change:
|
|
254
|
+
attributeFilter: ["class", "style", "data-whatever"],
|
|
255
|
+
// debounce all listeners by 100ms unless specified otherwise:
|
|
256
|
+
defaultDebounce: 100,
|
|
198
257
|
});
|
|
258
|
+
|
|
259
|
+
barObserver.addListener("#my-element", {
|
|
260
|
+
listener: (element) => {
|
|
261
|
+
console.log("Element's attributes changed:", element);
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
barObserver.addListener("#my-other-element", {
|
|
266
|
+
// set the debounce higher than provided by the defaultDebounce property:
|
|
267
|
+
debounce: 250,
|
|
268
|
+
listener: (element) => {
|
|
269
|
+
console.log("Other element's attributes changed:", element);
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
barObserver.enable();
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
// using custom listener options:
|
|
277
|
+
|
|
278
|
+
const bazObserver = new SelectorObserver(document.body);
|
|
279
|
+
|
|
280
|
+
// for TypeScript, specify that input elements are returned by the listener:
|
|
281
|
+
bazObserver.addListener<HTMLInputElement>("input", {
|
|
282
|
+
all: true, // use querySelectorAll() instead of querySelector()
|
|
283
|
+
continuous: true, // don't remove the listener after it was called once
|
|
284
|
+
debounce: 50, // debounce the listener by 50ms
|
|
285
|
+
listener: (elements) => {
|
|
286
|
+
// type of `elements` is NodeListOf<HTMLInputElement>
|
|
287
|
+
console.log("Input elements found:", elements);
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
bazObserver.enable();
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
// use a different element as the base:
|
|
295
|
+
|
|
296
|
+
const myElement = document.querySelector("#my-element");
|
|
297
|
+
if(myElement) {
|
|
298
|
+
const quxObserver = new SelectorObserver(myElement);
|
|
299
|
+
|
|
300
|
+
quxObserver.addListener("#my-child-element", {
|
|
301
|
+
listener: (element) => {
|
|
302
|
+
console.log("Child element found:", element);
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
quxObserver.enable();
|
|
307
|
+
}
|
|
199
308
|
});
|
|
200
309
|
```
|
|
201
310
|
|
|
202
|
-
</details>
|
|
203
|
-
|
|
204
311
|
<br>
|
|
205
312
|
|
|
206
|
-
|
|
207
|
-
|
|
313
|
+
#### Get and remove listeners:
|
|
314
|
+
|
|
208
315
|
```ts
|
|
209
|
-
|
|
316
|
+
import { SelectorObserver } from "@sv443-network/userutils";
|
|
317
|
+
|
|
318
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
319
|
+
const observer = new SelectorObserver(document.body);
|
|
320
|
+
|
|
321
|
+
observer.addListener("#my-element-foo", {
|
|
322
|
+
continuous: true,
|
|
323
|
+
listener: (element) => {
|
|
324
|
+
console.log("Element found:", element);
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
observer.addListener("#my-element-bar", {
|
|
329
|
+
listener: (element) => {
|
|
330
|
+
console.log("Element found again:", element);
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
observer.enable();
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
// get all listeners:
|
|
338
|
+
|
|
339
|
+
console.log(observer.getAllListeners());
|
|
340
|
+
// Map(2) {
|
|
341
|
+
// '#my-element-foo' => [ { listener: [Function: listener] } ],
|
|
342
|
+
// '#my-element-bar' => [ { listener: [Function: listener] } ]
|
|
343
|
+
// }
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
// get listeners for a specific selector:
|
|
347
|
+
|
|
348
|
+
console.log(observer.getListeners("#my-element-foo"));
|
|
349
|
+
// [ { listener: [Function: listener], continuous: true } ]
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
// remove all listeners for a specific selector:
|
|
353
|
+
|
|
354
|
+
observer.removeAllListeners("#my-element-foo");
|
|
355
|
+
console.log(observer.getAllListeners());
|
|
356
|
+
// Map(1) {
|
|
357
|
+
// '#my-element-bar' => [ { listener: [Function: listener] } ]
|
|
358
|
+
// }
|
|
359
|
+
});
|
|
210
360
|
```
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
<details><summary><b>Example - click to view</b></summary>
|
|
361
|
+
|
|
362
|
+
<br>
|
|
363
|
+
|
|
364
|
+
#### Chaining:
|
|
216
365
|
|
|
217
366
|
```ts
|
|
218
|
-
import {
|
|
367
|
+
import { SelectorObserver } from "@sv443-network/userutils";
|
|
368
|
+
import type { SelectorObserverOptions } from "@sv443-network/userutils";
|
|
219
369
|
|
|
220
|
-
|
|
370
|
+
// apply a default debounce to all SelectorObserver instances:
|
|
371
|
+
const defaultOptions: SelectorObserverOptions = {
|
|
372
|
+
defaultDebounce: 100,
|
|
373
|
+
};
|
|
221
374
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
375
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
376
|
+
// initialize generic observer that in turn initializes "sub-observers":
|
|
377
|
+
const fooObserver = new SelectorObserver(document.body, {
|
|
378
|
+
...defaultOptions,
|
|
379
|
+
// define any other specific options here
|
|
380
|
+
});
|
|
227
381
|
|
|
228
|
-
|
|
229
|
-
listener: (elements) => void 0,
|
|
230
|
-
});
|
|
382
|
+
const myElementSelector = "#my-element";
|
|
231
383
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
//
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
//
|
|
244
|
-
|
|
384
|
+
// this relatively expensive listener (as it is in the full <body> scope) will only fire once:
|
|
385
|
+
fooObserver.addListener(myElementSelector, {
|
|
386
|
+
listener: (element) => {
|
|
387
|
+
// only enable barObserver once its baseElement exists:
|
|
388
|
+
barObserver.enable();
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// barObserver is created at the same time as fooObserver, but only enabled once #my-element exists
|
|
393
|
+
const barObserver = new SelectorObserver(element, {
|
|
394
|
+
...defaultOptions,
|
|
395
|
+
// define any other specific options here
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// this selector will be checked for immediately after `enable()` is called
|
|
399
|
+
// and on each subsequent mutation because `continuous` is set to true.
|
|
400
|
+
// however it is much less expensive as it is scoped to a lower element which will receive less DOM updates
|
|
401
|
+
barObserver.addListener(".my-child-element", {
|
|
402
|
+
all: true,
|
|
403
|
+
continuous: true,
|
|
404
|
+
listener: (elements) => {
|
|
405
|
+
console.log("Child elements found:", elements);
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// immediately enable fooObserver as the <body> is available as soon as "DOMContentLoaded" fires:
|
|
410
|
+
fooObserver.enable();
|
|
411
|
+
});
|
|
245
412
|
```
|
|
246
413
|
|
|
247
414
|
</details>
|
|
248
415
|
|
|
416
|
+
|
|
249
417
|
<br>
|
|
250
418
|
|
|
251
419
|
### getUnsafeWindow()
|
|
@@ -383,6 +551,9 @@ preloadImages([
|
|
|
383
551
|
], true)
|
|
384
552
|
.then((results) => {
|
|
385
553
|
console.log("Images preloaded. Results:", results);
|
|
554
|
+
})
|
|
555
|
+
.catch((results) => {
|
|
556
|
+
console.error("Couldn't preload all images. Results:", results);
|
|
386
557
|
});
|
|
387
558
|
```
|
|
388
559
|
|
|
@@ -422,11 +593,12 @@ Usage:
|
|
|
422
593
|
interceptEvent(
|
|
423
594
|
eventObject: EventTarget,
|
|
424
595
|
eventName: string,
|
|
425
|
-
predicate
|
|
596
|
+
predicate?: (event: Event) => boolean
|
|
426
597
|
): void
|
|
427
598
|
```
|
|
428
599
|
|
|
429
600
|
Intercepts all events dispatched on the `eventObject` and prevents the listeners from being called as long as the predicate function returns a truthy value.
|
|
601
|
+
If no predicate is specified, all events will be discarded.
|
|
430
602
|
Calling this function will set the `Error.stackTraceLimit` to 1000 (if it's not already higher) to ensure the stack trace is preserved.
|
|
431
603
|
|
|
432
604
|
⚠️ 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.
|
|
@@ -437,8 +609,12 @@ Calling this function will set the `Error.stackTraceLimit` to 1000 (if it's not
|
|
|
437
609
|
import { interceptEvent } from "@sv443-network/userutils";
|
|
438
610
|
|
|
439
611
|
interceptEvent(document.body, "click", (event) => {
|
|
440
|
-
|
|
441
|
-
|
|
612
|
+
// prevent all click events on <a> elements within the entire <body>
|
|
613
|
+
if(event.target instanceof HTMLAnchorElement) {
|
|
614
|
+
console.log("Intercepting click event:", event);
|
|
615
|
+
return true;
|
|
616
|
+
}
|
|
617
|
+
return false; // allow all other click events through
|
|
442
618
|
});
|
|
443
619
|
```
|
|
444
620
|
|
|
@@ -451,11 +627,12 @@ Usage:
|
|
|
451
627
|
```ts
|
|
452
628
|
interceptWindowEvent(
|
|
453
629
|
eventName: string,
|
|
454
|
-
predicate
|
|
630
|
+
predicate?: (event: Event) => boolean
|
|
455
631
|
): void
|
|
456
632
|
```
|
|
457
633
|
|
|
458
634
|
Intercepts all events dispatched on the `window` object and prevents the listeners from being called as long as the predicate function returns a truthy value.
|
|
635
|
+
If no predicate is specified, all events will be discarded.
|
|
459
636
|
This is essentially the same as [`interceptEvent()`](#interceptevent), but automatically uses the `unsafeWindow` (or falls back to regular `window`).
|
|
460
637
|
|
|
461
638
|
⚠️ 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.
|
|
@@ -466,111 +643,9 @@ This is essentially the same as [`interceptEvent()`](#interceptevent), but autom
|
|
|
466
643
|
```ts
|
|
467
644
|
import { interceptWindowEvent } from "@sv443-network/userutils";
|
|
468
645
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
</details>
|
|
475
|
-
|
|
476
|
-
<br>
|
|
477
|
-
|
|
478
|
-
### amplifyMedia()
|
|
479
|
-
Usage:
|
|
480
|
-
```ts
|
|
481
|
-
amplifyMedia(mediaElement: HTMLMediaElement, initialMultiplier?: number): AmplifyMediaResult
|
|
482
|
-
```
|
|
483
|
-
|
|
484
|
-
Amplifies the gain of a media element (like `<audio>` or `<video>`) by a given multiplier (defaults to 1.0).
|
|
485
|
-
This is how you can increase the volume of a media element beyond the default maximum volume of 1.0 or 100%.
|
|
486
|
-
Make sure to limit the multiplier to a reasonable value ([clamp()](#clamp) is good for this), as it may cause bleeding eardrums.
|
|
487
|
-
|
|
488
|
-
This is the processing workflow applied to the media element:
|
|
489
|
-
`MediaElement (source)` => `DynamicsCompressorNode (limiter)` => `GainNode` => `AudioDestination (output)`
|
|
490
|
-
|
|
491
|
-
A limiter (compression) is applied to the audio to prevent clipping.
|
|
492
|
-
Its properties can be changed by calling the returned function `setLimiterOptions()`
|
|
493
|
-
The limiter options set by default are `{ threshold: -12, knee: 30, ratio: 12, attack: 0.003, release: 0.25 }`
|
|
494
|
-
|
|
495
|
-
⚠️ This function has to be run in response to a user interaction event, else the browser will reject it because of the strict autoplay policy.
|
|
496
|
-
⚠️ Make sure to call the returned function `enable()` after calling this function to actually enable the amplification.
|
|
497
|
-
|
|
498
|
-
The returned object of the type `AmplifyMediaResult` has the following properties:
|
|
499
|
-
| Property | Description |
|
|
500
|
-
| :-- | :-- |
|
|
501
|
-
| `setGain()` | Used to change the gain multiplier |
|
|
502
|
-
| `getGain()` | Returns the current gain multiplier |
|
|
503
|
-
| `enable()` | Call to enable the amplification for the first time or if it was disabled before |
|
|
504
|
-
| `disable()` | Call to disable amplification |
|
|
505
|
-
| `setLimiterOptions()` | Used for changing the [options of the DynamicsCompressorNode](https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode/DynamicsCompressorNode#options) - the default is `{ threshold: -12, knee: 30, ratio: 12, attack: 0.003, release: 0.25 }` |
|
|
506
|
-
| `context` | The AudioContext instance |
|
|
507
|
-
| `source` | The MediaElementSourceNode instance |
|
|
508
|
-
| `gainNode` | The GainNode instance used for actually boosting the gain |
|
|
509
|
-
| `limiterNode` | The DynamicsCompressorNode instance used for limiting clipping and distortion |
|
|
510
|
-
|
|
511
|
-
<details><summary><b>Example - click to view</b></summary>
|
|
512
|
-
|
|
513
|
-
```ts
|
|
514
|
-
import { amplifyMedia, clamp } from "@sv443-network/userutils";
|
|
515
|
-
import type { AmplifyMediaResult } from "@sv443-network/userutils";
|
|
516
|
-
|
|
517
|
-
const audioElement = document.querySelector<HTMLAudioElement>("audio");
|
|
518
|
-
|
|
519
|
-
let ampResult: AmplifyMediaResult | undefined;
|
|
520
|
-
|
|
521
|
-
function setGain(newValue: number) {
|
|
522
|
-
if(!ampResult)
|
|
523
|
-
return;
|
|
524
|
-
// constrain the value to between 0 and 3 for safety
|
|
525
|
-
ampResult.setGain(clamp(newValue, 0, 3));
|
|
526
|
-
console.log("Gain set to", ampResult.getGain());
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
const amplifyButton = document.querySelector<HTMLButtonElement>("button#amplify");
|
|
531
|
-
|
|
532
|
-
// amplifyMedia() needs to be called in response to a user interaction event:
|
|
533
|
-
amplifyButton.addEventListener("click", () => {
|
|
534
|
-
// only needs to be initialized once, afterwards the returned object
|
|
535
|
-
// can be used to change settings and enable/disable the amplification
|
|
536
|
-
if(!ampResult) {
|
|
537
|
-
// initialize amplification and set gain to 2x
|
|
538
|
-
ampResult = amplifyMedia(audioElement, 2);
|
|
539
|
-
// enable the amplification
|
|
540
|
-
ampResult.enable();
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
setGain(2.5); // set gain to 2.5x
|
|
544
|
-
|
|
545
|
-
console.log(ampResult.getGain()); // 2.5
|
|
546
|
-
});
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
const disableButton = document.querySelector<HTMLButtonElement>("button#disable");
|
|
550
|
-
|
|
551
|
-
disableButton.addEventListener("click", () => {
|
|
552
|
-
if(ampResult) {
|
|
553
|
-
// disable the amplification
|
|
554
|
-
ampResult.disable();
|
|
555
|
-
}
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
const limiterButton = document.querySelector<HTMLButtonElement>("button#limiter");
|
|
560
|
-
|
|
561
|
-
limiterButton.addEventListener("click", () => {
|
|
562
|
-
if(ampResult) {
|
|
563
|
-
// change the limiter options to a more aggressive setting
|
|
564
|
-
// see https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode/DynamicsCompressorNode#options
|
|
565
|
-
ampResult.setLimiterOptions({
|
|
566
|
-
threshold: -10,
|
|
567
|
-
knee: 20,
|
|
568
|
-
ratio: 20,
|
|
569
|
-
attack: 0.001,
|
|
570
|
-
release: 0.1,
|
|
571
|
-
});
|
|
572
|
-
}
|
|
573
|
-
});
|
|
646
|
+
// prevent the pesky "Are you sure you want to leave this page?" popup
|
|
647
|
+
// as no predicate is specified, all events will be discarded by default
|
|
648
|
+
interceptWindowEvent("beforeunload");
|
|
574
649
|
```
|
|
575
650
|
|
|
576
651
|
</details>
|
|
@@ -602,6 +677,7 @@ console.log("Element has a vertical scroll bar:", vertical);
|
|
|
602
677
|
|
|
603
678
|
<br><br>
|
|
604
679
|
|
|
680
|
+
<!-- #SECTION Math -->
|
|
605
681
|
## Math:
|
|
606
682
|
|
|
607
683
|
### clamp()
|
|
@@ -636,10 +712,10 @@ Usage:
|
|
|
636
712
|
```ts
|
|
637
713
|
mapRange(
|
|
638
714
|
value: number,
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
715
|
+
range1min: number,
|
|
716
|
+
range1max: number,
|
|
717
|
+
range2min: number,
|
|
718
|
+
range2max: number
|
|
643
719
|
): number
|
|
644
720
|
```
|
|
645
721
|
|
|
@@ -684,11 +760,39 @@ randRange(10); // 7
|
|
|
684
760
|
|
|
685
761
|
</details>
|
|
686
762
|
|
|
763
|
+
<br>
|
|
764
|
+
|
|
765
|
+
### randomId()
|
|
766
|
+
Usage:
|
|
767
|
+
```ts
|
|
768
|
+
randomId(length?: number, radix?: number): string
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
Generates a cryptographically strong random ID of a given length and [radix (base).](https://en.wikipedia.org/wiki/Radix)
|
|
772
|
+
The default length is 16 and the default radix is 16 (hexadecimal).
|
|
773
|
+
You may change the radix to get digits from different numerical systems.
|
|
774
|
+
Use 2 for binary, 8 for octal, 10 for decimal, 16 for hexadecimal and 36 for alphanumeric.
|
|
775
|
+
|
|
776
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
777
|
+
|
|
778
|
+
```ts
|
|
779
|
+
import { randomId } from "@sv443-network/userutils";
|
|
780
|
+
|
|
781
|
+
randomId(); // "1bda419a73629d4f" (length 16, radix 16)
|
|
782
|
+
randomId(10); // "f86cd354a4" (length 10, radix 16)
|
|
783
|
+
randomId(10, 2); // "1010001101" (length 10, radix 2)
|
|
784
|
+
randomId(10, 10); // "0183428506" (length 10, radix 10)
|
|
785
|
+
randomId(10, 36); // "z46jfpa37r" (length 10, radix 36)
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
</details>
|
|
789
|
+
|
|
687
790
|
<br><br>
|
|
688
791
|
|
|
792
|
+
<!-- #SECTION Misc -->
|
|
689
793
|
## Misc:
|
|
690
794
|
|
|
691
|
-
### ConfigManager
|
|
795
|
+
### ConfigManager
|
|
692
796
|
Usage:
|
|
693
797
|
```ts
|
|
694
798
|
new ConfigManager(options: ConfigManagerOptions)
|
|
@@ -711,7 +815,7 @@ The options object has the following properties:
|
|
|
711
815
|
|
|
712
816
|
<br>
|
|
713
817
|
|
|
714
|
-
|
|
818
|
+
#### Methods:
|
|
715
819
|
`loadData(): Promise<TData>`
|
|
716
820
|
Asynchronously loads the configuration data from persistent storage and returns it.
|
|
717
821
|
If no data was saved in persistent storage before, the value of `options.defaultConfig` will be returned and written to persistent storage.
|
|
@@ -927,8 +1031,109 @@ fetchAdvanced("https://jokeapi.dev/joke/Any?safe-mode", {
|
|
|
927
1031
|
|
|
928
1032
|
</details>
|
|
929
1033
|
|
|
1034
|
+
<br>
|
|
1035
|
+
|
|
1036
|
+
### insertValues()
|
|
1037
|
+
Usage:
|
|
1038
|
+
```ts
|
|
1039
|
+
insertValues(input: string, ...values: Stringifiable[]): string
|
|
1040
|
+
```
|
|
1041
|
+
|
|
1042
|
+
Inserts values into a string in the format `%n`, where `n` is the number of the value, starting at 1.
|
|
1043
|
+
The values will be stringified using `toString()` (see [Stringifiable](#stringifiable)) before being inserted into the input string.
|
|
1044
|
+
If not enough values are passed, the remaining placeholders will be left untouched.
|
|
1045
|
+
|
|
1046
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
1047
|
+
|
|
1048
|
+
```ts
|
|
1049
|
+
import { insertValues } from "@sv443-network/userutils";
|
|
1050
|
+
|
|
1051
|
+
insertValues("Hello, %1!", "World"); // "Hello, World!"
|
|
1052
|
+
insertValues("Hello, %1! My name is %2.", "World", "John"); // "Hello, World! My name is John."
|
|
1053
|
+
insertValues("Testing %1", { toString: () => "foo" }); // "Testing foo"
|
|
1054
|
+
|
|
1055
|
+
// using an array for the values and not passing enough arguments:
|
|
1056
|
+
const values = ["foo", "bar", "baz"];
|
|
1057
|
+
insertValues("Testing %1, %2, %3 and %4", ...values); // "Testing foo, bar and baz and %4"
|
|
1058
|
+
```
|
|
1059
|
+
|
|
1060
|
+
</details>
|
|
1061
|
+
|
|
1062
|
+
<br>
|
|
1063
|
+
|
|
1064
|
+
### compress()
|
|
1065
|
+
Usage:
|
|
1066
|
+
```ts
|
|
1067
|
+
compress(input: string | ArrayBuffer, compressionFormat: CompressionFormat, outputType?: "base64"): Promise<string>
|
|
1068
|
+
compress(input: string | ArrayBuffer, compressionFormat: CompressionFormat, outputType: "arrayBuffer"): Promise<ArrayBuffer>
|
|
1069
|
+
```
|
|
1070
|
+
|
|
1071
|
+
Compresses a string or ArrayBuffer using the specified compression format. Most browsers should support at least `gzip` and `deflate`
|
|
1072
|
+
The `outputType` dictates which format the output will be in. It will default to `base64` if left undefined.
|
|
1073
|
+
|
|
1074
|
+
⚠️ You need to provide the `@grant unsafeWindow` directive if you are using the `base64` output type or you will get a TypeError.
|
|
1075
|
+
⚠️ Not all browsers might support compression. Please check [on this page](https://developer.mozilla.org/en-US/docs/Web/API/CompressionStream#browser_compatibility) for compatibility and supported compression formats.
|
|
1076
|
+
|
|
1077
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
1078
|
+
|
|
1079
|
+
```ts
|
|
1080
|
+
import { compress } from "@sv443-network/userutils";
|
|
1081
|
+
|
|
1082
|
+
// using gzip:
|
|
1083
|
+
|
|
1084
|
+
const fooGz = await compress("Hello, World!", "gzip");
|
|
1085
|
+
const barGz = await compress("Hello, World!".repeat(20), "gzip");
|
|
1086
|
+
|
|
1087
|
+
// not as efficient with short strings but can save quite a lot of space with larger strings:
|
|
1088
|
+
console.log(fooGz); // "H4sIAAAAAAAAE/NIzcnJ11EIzy/KSVEEANDDSuwNAAAA"
|
|
1089
|
+
console.log(barGz); // "H4sIAAAAAAAAE/NIzcnJ11EIzy/KSVH0GJkcAKOPcmYEAQAA"
|
|
1090
|
+
|
|
1091
|
+
// depending on the type of data you might want to use a different compression format like deflate:
|
|
1092
|
+
|
|
1093
|
+
const fooDeflate = await compress("Hello, World!", "deflate");
|
|
1094
|
+
const barDeflate = await compress("Hello, World!".repeat(20), "deflate");
|
|
1095
|
+
|
|
1096
|
+
// again, it's not as efficient initially but gets better with longer inputs:
|
|
1097
|
+
console.log(fooDeflate); // "eJzzSM3JyddRCM8vyklRBAAfngRq"
|
|
1098
|
+
console.log(barDeflate); // "eJzzSM3JyddRCM8vyklR9BiZHAAIEVg1"
|
|
1099
|
+
```
|
|
1100
|
+
|
|
1101
|
+
</details>
|
|
1102
|
+
|
|
1103
|
+
<br>
|
|
1104
|
+
|
|
1105
|
+
### decompress()
|
|
1106
|
+
Usage:
|
|
1107
|
+
```ts
|
|
1108
|
+
decompress(input: string | ArrayBuffer, compressionFormat: CompressionFormat, outputType?: "string"): Promise<string>
|
|
1109
|
+
decompress(input: string | ArrayBuffer, compressionFormat: CompressionFormat, outputType: "arrayBuffer"): Promise<ArrayBuffer>
|
|
1110
|
+
```
|
|
1111
|
+
|
|
1112
|
+
Decompresses a string or ArrayBuffer that has been previously [compressed](#compress) using the specified compression format. Most browsers should support at least `gzip` and `deflate`
|
|
1113
|
+
The `outputType` dictates which format the output will be in. It will default to `string` if left undefined.
|
|
1114
|
+
|
|
1115
|
+
⚠️ You need to provide the `@grant unsafeWindow` directive if you are using the `string` output type or you will get a TypeError.
|
|
1116
|
+
⚠️ Not all browsers might support decompression. Please check [on this page](https://developer.mozilla.org/en-US/docs/Web/API/DecompressionStream#browser_compatibility) for compatibility and supported compression formats.
|
|
1117
|
+
|
|
1118
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
1119
|
+
|
|
1120
|
+
```ts
|
|
1121
|
+
import { compress, decompress } from "@sv443-network/userutils";
|
|
1122
|
+
|
|
1123
|
+
const compressed = await compress("Hello, World!".repeat(20), "gzip");
|
|
1124
|
+
|
|
1125
|
+
console.log(compressed); // "H4sIAAAAAAAAE/NIzcnJ11EIzy/KSVH0GJkcAKOPcmYEAQAA"
|
|
1126
|
+
|
|
1127
|
+
const decompressed = await decompress(compressed, "gzip");
|
|
1128
|
+
|
|
1129
|
+
console.log(decompressed); // "Hello, World!"
|
|
1130
|
+
```
|
|
1131
|
+
|
|
1132
|
+
</details>
|
|
1133
|
+
|
|
930
1134
|
<br><br>
|
|
931
1135
|
|
|
1136
|
+
<!-- #SECTION Arrays -->
|
|
932
1137
|
## Arrays:
|
|
933
1138
|
|
|
934
1139
|
### randomItem()
|
|
@@ -1029,6 +1234,7 @@ console.log(foo); // [1, 2, 3, 4, 5, 6] - original array is not mutated
|
|
|
1029
1234
|
|
|
1030
1235
|
<br><br>
|
|
1031
1236
|
|
|
1237
|
+
<!-- #SECTION Translation -->
|
|
1032
1238
|
## Translation:
|
|
1033
1239
|
This is a very lightweight translation function that can be used to translate simple strings.
|
|
1034
1240
|
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.
|
|
@@ -1204,6 +1410,7 @@ If no language has been set yet, it will return undefined.
|
|
|
1204
1410
|
|
|
1205
1411
|
<br><br>
|
|
1206
1412
|
|
|
1413
|
+
<!-- #SECTION Utility types -->
|
|
1207
1414
|
## Utility types:
|
|
1208
1415
|
UserUtils also offers some utility types that can be used in TypeScript projects.
|
|
1209
1416
|
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.
|
|
@@ -1236,13 +1443,99 @@ logSomething(true); // "Log: true"
|
|
|
1236
1443
|
logSomething({}); // "Log: [object Object]"
|
|
1237
1444
|
logSomething(Symbol(1)); // "Log: Symbol(1)"
|
|
1238
1445
|
logSomething(fooObject); // "Log: hello world"
|
|
1239
|
-
|
|
1446
|
+
|
|
1447
|
+
logSomething(barObject); // Type Error
|
|
1448
|
+
```
|
|
1449
|
+
|
|
1450
|
+
</details>
|
|
1451
|
+
|
|
1452
|
+
<br>
|
|
1453
|
+
|
|
1454
|
+
## NonEmptyArray
|
|
1455
|
+
Usage:
|
|
1456
|
+
```ts
|
|
1457
|
+
NonEmptyArray<TItem = unknown>
|
|
1458
|
+
```
|
|
1459
|
+
|
|
1460
|
+
This type describes an array that has at least one item.
|
|
1461
|
+
Use the generic parameter to specify the type of the items in the array.
|
|
1462
|
+
|
|
1463
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
1464
|
+
|
|
1465
|
+
```ts
|
|
1466
|
+
import type { NonEmptyArray } from "@sv443-network/userutils";
|
|
1467
|
+
|
|
1468
|
+
function logFirstItem(array: NonEmptyArray<string>) {
|
|
1469
|
+
console.log(parseInt(array[0]));
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
function somethingElse(array: NonEmptyArray) {
|
|
1473
|
+
// array is typed as NonEmptyArray<unknown> when not passing a
|
|
1474
|
+
// generic parameter, so this throws a TS error:
|
|
1475
|
+
console.log(parseInt(array[0])); // Argument of type 'unknown' is not assignable to parameter of type 'string'
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
logFirstItem(["04abc", "69"]); // 4
|
|
1479
|
+
```
|
|
1480
|
+
|
|
1481
|
+
</details>
|
|
1482
|
+
|
|
1483
|
+
<br>
|
|
1484
|
+
|
|
1485
|
+
## NonEmptyString
|
|
1486
|
+
Usage:
|
|
1487
|
+
```ts
|
|
1488
|
+
NonEmptyString<TString extends string>
|
|
1489
|
+
```
|
|
1490
|
+
|
|
1491
|
+
This type describes a string that has at least one character.
|
|
1492
|
+
|
|
1493
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
1494
|
+
|
|
1495
|
+
```ts
|
|
1496
|
+
import type { NonEmptyString } from "@sv443-network/userutils";
|
|
1497
|
+
|
|
1498
|
+
function convertToNumber<T extends string>(str: NonEmptyString<T>) {
|
|
1499
|
+
console.log(parseInt(str));
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
convertToNumber("04abc"); // "4"
|
|
1503
|
+
convertToNumber(""); // type error: Argument of type 'string' is not assignable to parameter of type 'never'
|
|
1504
|
+
```
|
|
1505
|
+
|
|
1506
|
+
</details>
|
|
1507
|
+
|
|
1508
|
+
<br>
|
|
1509
|
+
|
|
1510
|
+
## LooseUnion
|
|
1511
|
+
Usage:
|
|
1512
|
+
```ts
|
|
1513
|
+
LooseUnion<TUnion extends string | number | object>
|
|
1514
|
+
```
|
|
1515
|
+
|
|
1516
|
+
A type that offers autocomplete in the IDE for the passed union but also allows any value of the same type to be passed.
|
|
1517
|
+
Supports unions of strings, numbers and objects.
|
|
1518
|
+
|
|
1519
|
+
<details><summary><b>Example - click to view</b></summary>
|
|
1520
|
+
|
|
1521
|
+
```ts
|
|
1522
|
+
function foo(bar: LooseUnion<"a" | "b" | "c">) {
|
|
1523
|
+
console.log(bar);
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
// when typing the following, autocomplete suggests "a", "b" and "c"
|
|
1527
|
+
// foo("
|
|
1528
|
+
|
|
1529
|
+
foo("a"); // included in autocomplete, no type error
|
|
1530
|
+
foo(""); // *not* included in autocomplete, still no type error
|
|
1531
|
+
foo(1); // type error: Argument of type '1' is not assignable to parameter of type 'LooseUnion<"a" | "b" | "c">'
|
|
1240
1532
|
```
|
|
1241
1533
|
|
|
1242
1534
|
</details>
|
|
1243
1535
|
|
|
1244
1536
|
<br><br><br><br>
|
|
1245
1537
|
|
|
1538
|
+
<!-- #MARKER Footer -->
|
|
1246
1539
|
<div style="text-align: center;" align="center">
|
|
1247
1540
|
|
|
1248
1541
|
Made with ❤️ by [Sv443](https://github.com/Sv443)
|