@sv443-network/userutils 9.0.4 → 9.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/dist/index.js CHANGED
@@ -97,13 +97,21 @@ function randRange(...args) {
97
97
  } else
98
98
  return Math.floor(Math.random() * (max - min + 1)) + min;
99
99
  }
100
- function digitCount(num) {
100
+ function digitCount(num, withDecimals = true) {
101
101
  num = Number(!["string", "number"].includes(typeof num) ? String(num) : num);
102
102
  if (typeof num === "number" && isNaN(num))
103
103
  return NaN;
104
- return num === 0 ? 1 : Math.floor(
105
- Math.log10(Math.abs(Number(num))) + 1
106
- );
104
+ const [intPart, decPart] = num.toString().split(".");
105
+ const intDigits = intPart === "0" ? 1 : Math.floor(Math.log10(Math.abs(Number(intPart))) + 1);
106
+ const decDigits = withDecimals && decPart ? decPart.length : 0;
107
+ return intDigits + decDigits;
108
+ }
109
+ function roundFixed(num, fractionDigits) {
110
+ const scale = 10 ** fractionDigits;
111
+ return Math.round(num * scale) / scale;
112
+ }
113
+ function bitSetHas(bitSet, checkVal) {
114
+ return (bitSet & checkVal) === checkVal;
107
115
  }
108
116
 
109
117
  // lib/array.ts
@@ -128,7 +136,7 @@ function randomizeArray(array) {
128
136
  if (array.length === 0)
129
137
  return retArray;
130
138
  for (let i = retArray.length - 1; i > 0; i--) {
131
- const j = Math.floor(randRange(0, 1e4) / 1e4 * (i + 1));
139
+ const j = Math.floor(Math.random() * (i + 1));
132
140
  [retArray[i], retArray[j]] = [retArray[j], retArray[i]];
133
141
  }
134
142
  return retArray;
@@ -171,10 +179,10 @@ function darkenColor(color, percent, upperCase = false) {
171
179
  else if (color.startsWith("rgb")) {
172
180
  const rgbValues = (_a = color.match(/\d+(\.\d+)?/g)) == null ? undefined : _a.map(Number);
173
181
  if (!rgbValues)
174
- throw new Error("Invalid RGB/RGBA color format");
182
+ throw new TypeError("Invalid RGB/RGBA color format");
175
183
  [r, g, b, a] = rgbValues;
176
184
  } else
177
- throw new Error("Unsupported color format");
185
+ throw new TypeError("Unsupported color format");
178
186
  [r, g, b] = darkenRgb(r, g, b, percent);
179
187
  if (isHexCol)
180
188
  return rgbToHex(r, g, b, a, color.startsWith("#"), upperCase);
@@ -183,10 +191,39 @@ function darkenColor(color, percent, upperCase = false) {
183
191
  else if (color.startsWith("rgb"))
184
192
  return `rgb(${r}, ${g}, ${b})`;
185
193
  else
186
- throw new Error("Unsupported color format");
194
+ throw new TypeError("Unsupported color format");
187
195
  }
188
196
 
197
+ // lib/errors.ts
198
+ var UUError = class extends Error {
199
+ constructor(message, options) {
200
+ super(message, options);
201
+ __publicField(this, "date");
202
+ this.date = /* @__PURE__ */ new Date();
203
+ }
204
+ };
205
+ var ChecksumMismatchError = class extends UUError {
206
+ constructor(message, options) {
207
+ super(message, options);
208
+ this.name = "ChecksumMismatchError";
209
+ }
210
+ };
211
+ var MigrationError = class extends UUError {
212
+ constructor(message, options) {
213
+ super(message, options);
214
+ this.name = "MigrationError";
215
+ }
216
+ };
217
+ var PlatformError = class extends UUError {
218
+ constructor(message, options) {
219
+ super(message, options);
220
+ this.name = "PlatformError";
221
+ }
222
+ };
223
+
189
224
  // lib/dom.ts
225
+ var domReady = false;
226
+ document.addEventListener("DOMContentLoaded", () => domReady = true);
190
227
  function getUnsafeWindow() {
191
228
  try {
192
229
  return unsafeWindow;
@@ -242,8 +279,8 @@ function openInNewTab(href, background, additionalProps) {
242
279
  }
243
280
  function interceptEvent(eventObject, eventName, predicate = () => true) {
244
281
  var _a;
245
- if ((eventObject === window || eventObject === getUnsafeWindow()) && ((_a = GM == null ? undefined : GM.info) == null ? undefined : _a.scriptHandler) && GM.info.scriptHandler === "FireMonkey")
246
- throw new Error("Intercepting window events is not supported on FireMonkey due to the isolated context the userscript runs in.");
282
+ if (((_a = GM == null ? undefined : GM.info) == null ? undefined : _a.scriptHandler) && GM.info.scriptHandler === "FireMonkey" && (eventObject === window || eventObject === getUnsafeWindow()))
283
+ throw new PlatformError("Intercepting window events is not supported on FireMonkey due to the isolated context the userscript runs in.");
247
284
  Error.stackTraceLimit = Math.max(Error.stackTraceLimit, 100);
248
285
  if (isNaN(Error.stackTraceLimit))
249
286
  Error.stackTraceLimit = 100;
@@ -325,6 +362,36 @@ function setInnerHtmlUnsafe(element, html) {
325
362
  element.innerHTML = (_c = (_b = ttPolicy == null ? undefined : ttPolicy.createHTML) == null ? undefined : _b.call(ttPolicy, html)) != null ? _c : html;
326
363
  return element;
327
364
  }
365
+ function probeElementStyle(probeStyle, element, hideOffscreen = true, parentElement = document.body) {
366
+ const el = element ? typeof element === "function" ? element() : element : document.createElement("span");
367
+ if (hideOffscreen) {
368
+ el.style.position = "absolute";
369
+ el.style.left = "-9999px";
370
+ el.style.top = "-9999px";
371
+ el.style.zIndex = "-9999";
372
+ }
373
+ el.classList.add("_uu_probe_element");
374
+ parentElement.appendChild(el);
375
+ const style = window.getComputedStyle(el);
376
+ const result = probeStyle(style, el);
377
+ setTimeout(() => el.remove(), 1);
378
+ return result;
379
+ }
380
+ function isDomLoaded() {
381
+ return domReady;
382
+ }
383
+ function onDomLoad(cb) {
384
+ return new Promise((res) => {
385
+ if (domReady) {
386
+ cb == null ? undefined : cb();
387
+ res();
388
+ } else
389
+ document.addEventListener("DOMContentLoaded", () => {
390
+ cb == null ? undefined : cb();
391
+ res();
392
+ });
393
+ });
394
+ }
328
395
 
329
396
  // lib/crypto.ts
330
397
  function compress(input, compressionFormat, outputType = "string") {
@@ -372,6 +439,8 @@ function computeHash(input, algorithm = "SHA-256") {
372
439
  });
373
440
  }
374
441
  function randomId(length = 16, radix = 16, enhancedEntropy = false, randomCase = true) {
442
+ if (radix < 2 || radix > 36)
443
+ throw new RangeError("The radix argument must be between 2 and 36");
375
444
  let arr = [];
376
445
  const caseArr = randomCase ? [0, 1] : [0];
377
446
  if (enhancedEntropy) {
@@ -545,8 +614,7 @@ var DataStore = class {
545
614
  lastFmtVer = oldFmtVer = ver;
546
615
  } catch (err) {
547
616
  if (!resetOnError)
548
- throw new Error(`Error while running migration function for format version '${fmtVer}'`);
549
- console.error(`Error while running migration function for format version '${fmtVer}' - resetting to the default value.`, err);
617
+ throw new MigrationError(`Error while running migration function for format version '${fmtVer}'`, { cause: err });
550
618
  yield this.saveDefaultData();
551
619
  return this.getData();
552
620
  }
@@ -607,7 +675,6 @@ var DataStore = class {
607
675
  return JSON.parse(decRes != null ? decRes : data);
608
676
  });
609
677
  }
610
- //#region misc
611
678
  /** Copies a JSON-compatible object and loses all its internal references in the process */
612
679
  deepCopy(obj) {
613
680
  return JSON.parse(JSON.stringify(obj));
@@ -659,7 +726,7 @@ var DataStore = class {
659
726
  };
660
727
 
661
728
  // lib/DataStoreSerializer.ts
662
- var DataStoreSerializer = class {
729
+ var DataStoreSerializer = class _DataStoreSerializer {
663
730
  constructor(stores, options = {}) {
664
731
  __publicField(this, "stores");
665
732
  __publicField(this, "options");
@@ -677,27 +744,25 @@ var DataStoreSerializer = class {
677
744
  return computeHash(input, "SHA-256");
678
745
  });
679
746
  }
680
- /** Serializes a DataStore instance */
681
- serializeStore(storeInst) {
682
- return __async(this, null, function* () {
683
- const data = storeInst.encodingEnabled() ? yield storeInst.encodeData(JSON.stringify(storeInst.getData())) : JSON.stringify(storeInst.getData());
684
- const checksum = this.options.addChecksum ? yield this.calcChecksum(data) : undefined;
685
- return {
686
- id: storeInst.id,
687
- data,
688
- formatVersion: storeInst.formatVersion,
689
- encoded: storeInst.encodingEnabled(),
690
- checksum
691
- };
692
- });
693
- }
694
- /** Serializes the data stores into a string */
695
- serialize() {
747
+ /**
748
+ * Serializes the data stores into a string.
749
+ * @param useEncoding Whether to encode the data using each DataStore's `encodeData()` method
750
+ * @param stringified Whether to return the result as a string or as an array of `SerializedDataStore` objects
751
+ */
752
+ serialize(useEncoding = true, stringified = true) {
696
753
  return __async(this, null, function* () {
697
754
  const serData = [];
698
- for (const store of this.stores)
699
- serData.push(yield this.serializeStore(store));
700
- return JSON.stringify(serData);
755
+ for (const storeInst of this.stores) {
756
+ const data = useEncoding && storeInst.encodingEnabled() ? yield storeInst.encodeData(JSON.stringify(storeInst.getData())) : JSON.stringify(storeInst.getData());
757
+ serData.push({
758
+ id: storeInst.id,
759
+ data,
760
+ formatVersion: storeInst.formatVersion,
761
+ encoded: useEncoding && storeInst.encodingEnabled(),
762
+ checksum: this.options.addChecksum ? yield this.calcChecksum(data) : undefined
763
+ });
764
+ }
765
+ return stringified ? JSON.stringify(serData) : serData;
701
766
  });
702
767
  }
703
768
  /**
@@ -706,7 +771,9 @@ var DataStoreSerializer = class {
706
771
  */
707
772
  deserialize(serializedData) {
708
773
  return __async(this, null, function* () {
709
- const deserStores = JSON.parse(serializedData);
774
+ const deserStores = typeof serializedData === "string" ? JSON.parse(serializedData) : serializedData;
775
+ if (!Array.isArray(deserStores) || !deserStores.every(_DataStoreSerializer.isSerializedDataStore))
776
+ throw new TypeError("Invalid serialized data format! Expected an array of SerializedDataStore objects.");
710
777
  for (const storeData of deserStores) {
711
778
  const storeInst = this.stores.find((s) => s.id === storeData.id);
712
779
  if (!storeInst)
@@ -714,7 +781,7 @@ var DataStoreSerializer = class {
714
781
  if (this.options.ensureIntegrity && typeof storeData.checksum === "string") {
715
782
  const checksum = yield this.calcChecksum(storeData.data);
716
783
  if (checksum !== storeData.checksum)
717
- throw new Error(`Checksum mismatch for DataStore with ID "${storeData.id}"!
784
+ throw new ChecksumMismatchError(`Checksum mismatch for DataStore with ID "${storeData.id}"!
718
785
  Expected: ${storeData.checksum}
719
786
  Has: ${checksum}`);
720
787
  }
@@ -758,6 +825,10 @@ Has: ${checksum}`);
758
825
  return Promise.allSettled(this.stores.map((store) => store.deleteData()));
759
826
  });
760
827
  }
828
+ /** Checks if a given value is a SerializedDataStore object */
829
+ static isSerializedDataStore(obj) {
830
+ return typeof obj === "object" && obj !== null && "id" in obj && "data" in obj && "formatVersion" in obj && "encoded" in obj;
831
+ }
761
832
  };
762
833
  var NanoEmitter = class {
763
834
  constructor(options = {}) {
@@ -897,7 +968,7 @@ var Debouncer = class extends NanoEmitter {
897
968
  }, this.timeout);
898
969
  break;
899
970
  default:
900
- throw new Error(`Invalid debouncer type: ${this.type}`);
971
+ throw new TypeError(`Invalid debouncer type: ${this.type}`);
901
972
  }
902
973
  }
903
974
  };
@@ -1329,10 +1400,23 @@ var Dialog = class _Dialog extends NanoEmitter {
1329
1400
  };
1330
1401
 
1331
1402
  // lib/misc.ts
1332
- function autoPlural(word, num) {
1333
- if (Array.isArray(num) || num instanceof NodeList)
1334
- num = num.length;
1335
- return `${word}${num === 1 ? "" : "s"}`;
1403
+ function autoPlural(term, num, pluralType = "auto") {
1404
+ let n = num;
1405
+ if (typeof n !== "number")
1406
+ n = getListLength(n, false);
1407
+ if (!["-s", "-ies"].includes(pluralType))
1408
+ pluralType = "auto";
1409
+ if (isNaN(n))
1410
+ n = 2;
1411
+ const pType = pluralType === "auto" ? String(term).endsWith("y") ? "-ies" : "-s" : pluralType;
1412
+ switch (pType) {
1413
+ case "-s":
1414
+ return `${term}${n === 1 ? "" : "s"}`;
1415
+ case "-ies":
1416
+ return `${String(term).slice(0, -1)}${n === 1 ? "y" : "ies"}`;
1417
+ default:
1418
+ return String(term);
1419
+ }
1336
1420
  }
1337
1421
  function insertValues(input, ...values) {
1338
1422
  return input.replace(/%\d/gm, (match) => {
@@ -1341,29 +1425,33 @@ function insertValues(input, ...values) {
1341
1425
  return (_b = (_a = values[argIndex]) != null ? _a : match) == null ? undefined : _b.toString();
1342
1426
  });
1343
1427
  }
1344
- function pauseFor(time) {
1345
- return new Promise((res) => {
1346
- setTimeout(() => res(), time);
1428
+ function pauseFor(time, signal, rejectOnAbort = false) {
1429
+ return new Promise((res, rej) => {
1430
+ const timeout = setTimeout(() => res(), time);
1431
+ signal == null ? undefined : signal.addEventListener("abort", () => {
1432
+ clearTimeout(timeout);
1433
+ rejectOnAbort ? rej(new Error("The pause was aborted")) : res();
1434
+ });
1347
1435
  });
1348
1436
  }
1349
1437
  function fetchAdvanced(_0) {
1350
1438
  return __async(this, arguments, function* (input, options = {}) {
1351
- var _a;
1352
1439
  const { timeout = 1e4 } = options;
1353
- const { signal, abort } = new AbortController();
1354
- (_a = options.signal) == null ? undefined : _a.addEventListener("abort", abort);
1355
- let signalOpts = {}, id = undefined;
1440
+ const ctl = new AbortController();
1441
+ const _a = options, { signal } = _a, restOpts = __objRest(_a, ["signal"]);
1442
+ signal == null ? undefined : signal.addEventListener("abort", () => ctl.abort());
1443
+ let sigOpts = {}, id = undefined;
1356
1444
  if (timeout >= 0) {
1357
- id = setTimeout(() => abort(), timeout);
1358
- signalOpts = { signal };
1445
+ id = setTimeout(() => ctl.abort(), timeout);
1446
+ sigOpts = { signal: ctl.signal };
1359
1447
  }
1360
1448
  try {
1361
- const res = yield fetch(input, __spreadValues(__spreadValues({}, options), signalOpts));
1362
- id && clearTimeout(id);
1449
+ const res = yield fetch(input, __spreadValues(__spreadValues({}, restOpts), sigOpts));
1450
+ typeof id !== "undefined" && clearTimeout(id);
1363
1451
  return res;
1364
1452
  } catch (err) {
1365
- id && clearTimeout(id);
1366
- throw err;
1453
+ typeof id !== "undefined" && clearTimeout(id);
1454
+ throw new Error("Error while calling fetch", { cause: err });
1367
1455
  }
1368
1456
  });
1369
1457
  }
@@ -1379,10 +1467,11 @@ function consumeStringGen(strGen) {
1379
1467
  );
1380
1468
  });
1381
1469
  }
1470
+ function getListLength(obj, zeroOnInvalid = true) {
1471
+ return "length" in obj ? obj.length : "size" in obj ? obj.size : "count" in obj ? obj.count : zeroOnInvalid ? 0 : NaN;
1472
+ }
1382
1473
 
1383
1474
  // lib/SelectorObserver.ts
1384
- var domLoaded = false;
1385
- document.addEventListener("DOMContentLoaded", () => domLoaded = true);
1386
1475
  var SelectorObserver = class {
1387
1476
  constructor(baseElement, options = {}) {
1388
1477
  __publicField(this, "enabled", false);
@@ -1423,7 +1512,7 @@ var SelectorObserver = class {
1423
1512
  }
1424
1513
  /** Call to check all selectors in the {@linkcode listenerMap} using {@linkcode checkSelector()} */
1425
1514
  checkAllSelectors() {
1426
- if (!this.enabled || !domLoaded)
1515
+ if (!this.enabled || !isDomLoaded())
1427
1516
  return;
1428
1517
  for (const [selector, listeners] of this.listenerMap.entries())
1429
1518
  this.checkSelector(selector, listeners);
@@ -1567,9 +1656,9 @@ function translate(language, key, ...trArgs) {
1567
1656
  if (typeof language !== "string" || language.length === 0 || typeof trObj !== "object" || trObj === null)
1568
1657
  return fallbackLang ? translate(fallbackLang, key, ...trArgs) : key;
1569
1658
  const transformTrVal = (trKey, trValue) => {
1570
- const tfs = valTransforms.filter(({ regex }) => new RegExp(regex).test(trValue));
1659
+ const tfs = valTransforms.filter(({ regex }) => new RegExp(regex).test(String(trValue)));
1571
1660
  if (tfs.length === 0)
1572
- return trValue;
1661
+ return String(trValue);
1573
1662
  let retStr = String(trValue);
1574
1663
  for (const tf of tfs) {
1575
1664
  const re = new RegExp(tf.regex);
@@ -1718,4 +1807,4 @@ var tr = {
1718
1807
  }
1719
1808
  };
1720
1809
 
1721
- export { DataStore, DataStoreSerializer, Debouncer, Dialog, NanoEmitter, SelectorObserver, addGlobalStyle, addParent, autoPlural, clamp, compress, computeHash, consumeGen, consumeStringGen, currentDialogId, darkenColor, debounce, decompress, defaultDialogCss, defaultStrings, digitCount, fetchAdvanced, getSiblingsFrame, getUnsafeWindow, hexToRgb, insertValues, interceptEvent, interceptWindowEvent, isScrollable, lightenColor, mapRange, observeElementProp, openDialogs, openInNewTab, pauseFor, preloadImages, randRange, randomId, randomItem, randomItemIndex, randomizeArray, rgbToHex, setInnerHtmlUnsafe, takeRandomItem, tr };
1810
+ export { ChecksumMismatchError, DataStore, DataStoreSerializer, Debouncer, Dialog, MigrationError, NanoEmitter, PlatformError, SelectorObserver, UUError, addGlobalStyle, addParent, autoPlural, bitSetHas, clamp, compress, computeHash, consumeGen, consumeStringGen, currentDialogId, darkenColor, debounce, decompress, defaultDialogCss, defaultStrings, digitCount, fetchAdvanced, getListLength, getSiblingsFrame, getUnsafeWindow, hexToRgb, insertValues, interceptEvent, interceptWindowEvent, isDomLoaded, isScrollable, lightenColor, mapRange, observeElementProp, onDomLoad, openDialogs, openInNewTab, pauseFor, preloadImages, probeElementStyle, randRange, randomId, randomItem, randomItemIndex, randomizeArray, rgbToHex, roundFixed, setInnerHtmlUnsafe, takeRandomItem, tr };
@@ -2,7 +2,7 @@
2
2
  * @module lib/DataStoreSerializer
3
3
  * This module contains the DataStoreSerializer class, which allows you to import and export serialized DataStore data - [see the documentation for more info](https://github.com/Sv443-Network/UserUtils/blob/main/docs.md#datastoreserializer)
4
4
  */
5
- import { type DataStore } from "./index.js";
5
+ import type { DataStore } from "./DataStore.js";
6
6
  export type DataStoreSerializerOptions = {
7
7
  /** Whether to add a checksum to the exported data */
8
8
  addChecksum?: boolean;
@@ -43,15 +43,23 @@ export declare class DataStoreSerializer {
43
43
  constructor(stores: DataStore[], options?: DataStoreSerializerOptions);
44
44
  /** Calculates the checksum of a string */
45
45
  protected calcChecksum(input: string): Promise<string>;
46
- /** Serializes a DataStore instance */
47
- protected serializeStore(storeInst: DataStore): Promise<SerializedDataStore>;
48
- /** Serializes the data stores into a string */
49
- serialize(): Promise<string>;
46
+ /**
47
+ * Serializes the data stores into a string.
48
+ * @param useEncoding Whether to encode the data using each DataStore's `encodeData()` method
49
+ * @param stringified Whether to return the result as a string or as an array of `SerializedDataStore` objects
50
+ */
51
+ serialize(useEncoding: boolean, stringified: true): Promise<string>;
52
+ /**
53
+ * Serializes the data stores into a string.
54
+ * @param useEncoding Whether to encode the data using each DataStore's `encodeData()` method
55
+ * @param stringified Whether to return the result as a string or as an array of `SerializedDataStore` objects
56
+ */
57
+ serialize(useEncoding: boolean, stringified: false): Promise<SerializedDataStore[]>;
50
58
  /**
51
59
  * Deserializes the data exported via {@linkcode serialize()} and imports it into the DataStore instances.
52
60
  * Also triggers the migration process if the data format has changed.
53
61
  */
54
- deserialize(serializedData: string): Promise<void>;
62
+ deserialize(serializedData: string | SerializedDataStore[]): Promise<void>;
55
63
  /**
56
64
  * Loads the persistent data of the DataStore instances into the in-memory cache.
57
65
  * Also triggers the migration process if the data format has changed.
@@ -65,4 +73,6 @@ export declare class DataStoreSerializer {
65
73
  * Leaves the in-memory data untouched.
66
74
  */
67
75
  deleteStoresData(): Promise<PromiseSettledResult<void>[]>;
76
+ /** Checks if a given value is a SerializedDataStore object */
77
+ static isSerializedDataStore(obj: unknown): obj is SerializedDataStore;
68
78
  }
package/dist/lib/dom.d.ts CHANGED
@@ -76,3 +76,22 @@ export declare function getSiblingsFrame<TSibling extends Element = HTMLElement>
76
76
  * - ⚠️ This function does not perform any sanitization and should thus be used with utmost caution, as it can easily lead to XSS vulnerabilities!
77
77
  */
78
78
  export declare function setInnerHtmlUnsafe<TElement extends Element = HTMLElement>(element: TElement, html: string): TElement;
79
+ /**
80
+ * Creates an invisible temporary element to probe its rendered style.
81
+ * Has to be run after the `DOMContentLoaded` event has fired on the document object.
82
+ * @param probeStyle Function to probe the element's style. First argument is the element's style object from [`window.getComputedStyle()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle), second argument is the element itself
83
+ * @param element The element to probe, or a function that creates and returns the element - should not be added to the DOM prior to calling this function! - all probe elements will have the class `_uu_probe_element` added to them
84
+ * @param hideOffscreen Whether to hide the element offscreen, enabled by default - disable if you want to probe the position style properties of the element
85
+ * @param parentElement The parent element to append the probe element to, defaults to `document.body`
86
+ * @returns The value returned by the `probeElement` function
87
+ */
88
+ export declare function probeElementStyle<TValue, TElem extends HTMLElement = HTMLSpanElement>(probeStyle: (style: CSSStyleDeclaration, element: TElem) => TValue, element?: TElem | (() => TElem), hideOffscreen?: boolean, parentElement?: HTMLElement): TValue;
89
+ /** Returns whether or not the DOM has finished loading */
90
+ export declare function isDomLoaded(): boolean;
91
+ /**
92
+ * Executes a callback and/or resolves the returned Promise when the DOM has finished loading.
93
+ * Immediately executes/resolves if the DOM is already loaded.
94
+ * @param cb Callback to execute when the DOM has finished loading
95
+ * @returns Returns a Promise that resolves when the DOM has finished loading
96
+ */
97
+ export declare function onDomLoad(cb?: () => void): Promise<void>;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @module lib/errors
3
+ * Contains custom error classes
4
+ */
5
+ /** Base class for all UserUtils errors - adds a `date` prop set to the error throw time */
6
+ export declare class UUError extends Error {
7
+ readonly date: Date;
8
+ constructor(message: string, options?: ErrorOptions);
9
+ }
10
+ /** Error while validating checksum */
11
+ export declare class ChecksumMismatchError extends UUError {
12
+ constructor(message: string, options?: ErrorOptions);
13
+ }
14
+ /** Error while migrating data */
15
+ export declare class MigrationError extends UUError {
16
+ constructor(message: string, options?: ErrorOptions);
17
+ }
18
+ /** Error due to the platform, like using a feature that's not supported by the browser */
19
+ export declare class PlatformError extends UUError {
20
+ constructor(message: string, options?: ErrorOptions);
21
+ }
@@ -10,6 +10,7 @@ export * from "./DataStoreSerializer.js";
10
10
  export * from "./Debouncer.js";
11
11
  export * from "./Dialog.js";
12
12
  export * from "./dom.js";
13
+ export * from "./errors.js";
13
14
  export * from "./math.js";
14
15
  export * from "./misc.js";
15
16
  export * from "./NanoEmitter.js";
@@ -27,5 +27,55 @@ export declare function randRange(min: number, max: number, enhancedEntropy?: bo
27
27
  * Set {@linkcode enhancedEntropy} to true to use `crypto.getRandomValues()` for better cryptographic randomness (this also makes it take longer to generate)
28
28
  */
29
29
  export declare function randRange(max: number, enhancedEntropy?: boolean): number;
30
- /** Calculates the amount of digits in the given number - the given number or string will be passed to the `Number()` constructor. Returns NaN if the number is invalid. */
31
- export declare function digitCount(num: number | Stringifiable): number;
30
+ /**
31
+ * Calculates the amount of digits in the given number - the given number or string will be passed to the `Number()` constructor.
32
+ * Returns NaN if the number is invalid.
33
+ * @param num The number to count the digits of
34
+ * @param withDecimals Whether to count the decimal places as well (defaults to true)
35
+ * @example ```ts
36
+ * digitCount(); // NaN
37
+ * digitCount(0); // 1
38
+ * digitCount(123); // 3
39
+ * digitCount(123.456); // 6
40
+ * digitCount(Infinity); // Infinity
41
+ * ```
42
+ */
43
+ export declare function digitCount(num: number | Stringifiable, withDecimals?: boolean): number;
44
+ /**
45
+ * Rounds {@linkcode num} to a fixed amount of decimal places, specified by {@linkcode fractionDigits} (supports negative values to round to the nearest power of 10).
46
+ * @example ```ts
47
+ * roundFixed(234.567, -2); // 200
48
+ * roundFixed(234.567, -1); // 230
49
+ * roundFixed(234.567, 0); // 235
50
+ * roundFixed(234.567, 1); // 234.6
51
+ * roundFixed(234.567, 2); // 234.57
52
+ * roundFixed(234.567, 3); // 234.567
53
+ * ```
54
+ */
55
+ export declare function roundFixed(num: number, fractionDigits: number): number;
56
+ /**
57
+ * Checks if the {@linkcode bitSet} has the {@linkcode checkVal} set
58
+ * @example ```ts
59
+ * // the two vertically adjacent bits are tested for:
60
+ * bitSetHas(
61
+ * 0b1110,
62
+ * 0b0010,
63
+ * ); // true
64
+ *
65
+ * bitSetHas(
66
+ * 0b1110,
67
+ * 0b0001,
68
+ * ); // false
69
+ *
70
+ * // with TS enums (or JS maps):
71
+ * enum MyEnum {
72
+ * A = 1, B = 2, C = 4,
73
+ * D = 8, E = 16, F = 32,
74
+ * }
75
+ *
76
+ * const myBitSet = MyEnum.A | MyEnum.B;
77
+ * bitSetHas(myBitSet, MyEnum.B); // true
78
+ * bitSetHas(myBitSet, MyEnum.F); // false
79
+ * ```
80
+ */
81
+ export declare function bitSetHas<TType extends number | bigint>(bitSet: TType, checkVal: TType): boolean;
@@ -2,13 +2,18 @@
2
2
  * @module lib/misc
3
3
  * This module contains miscellaneous functions that don't fit in another category - [see the documentation for more info](https://github.com/Sv443-Network/UserUtils/blob/main/docs.md#misc)
4
4
  */
5
- import type { Prettify, Stringifiable } from "./types.js";
5
+ import type { ListWithLength, Prettify, Stringifiable } from "./types.js";
6
+ /** Which plural form to use when auto-pluralizing */
7
+ export type PluralType = "auto" | "-s" | "-ies";
6
8
  /**
7
- * Automatically appends an `s` to the passed {@linkcode word}, if {@linkcode num} is not equal to 1
8
- * @param word A word in singular form, to auto-convert to plural
9
- * @param num If this is an array or NodeList, the amount of items is used
9
+ * Automatically pluralizes the given string by adding an `-s` or `-ies` to the passed {@linkcode term}, if {@linkcode num} is not equal to 1.
10
+ * By default, words ending in `-y` will have it replaced with `-ies`, and all other words will simply have `-s` appended.
11
+ * {@linkcode pluralType} will default to `auto` if invalid and {@linkcode num} is set to 2 if it resolves to `NaN`.
12
+ * @param term The term, written in singular form, to auto-convert to plural form
13
+ * @param num A number, or list-like value that has either a `length`, `count` or `size` property, like an array, Map or NodeList - does not support iterables, they need to be converted to an array first
14
+ * @param pluralType Which plural form to use when auto-pluralizing. Defaults to `"auto"`, which removes the last char and uses `-ies` for words ending in `y` and simply appends `-s` for all other words
10
15
  */
11
- export declare function autoPlural(word: Stringifiable, num: number | unknown[] | NodeList): string;
16
+ export declare function autoPlural(term: Stringifiable, num: number | ListWithLength, pluralType?: PluralType): string;
12
17
  /**
13
18
  * Inserts the passed values into a string at the respective placeholders.
14
19
  * The placeholder format is `%n`, where `n` is the 1-indexed argument number.
@@ -16,8 +21,12 @@ export declare function autoPlural(word: Stringifiable, num: number | unknown[]
16
21
  * @param values The values to insert, in order, starting at `%1`
17
22
  */
18
23
  export declare function insertValues(input: string, ...values: Stringifiable[]): string;
19
- /** Pauses async execution for the specified time in ms */
20
- export declare function pauseFor(time: number): Promise<void>;
24
+ /**
25
+ * Pauses async execution for the specified time in ms.
26
+ * If an `AbortSignal` is passed, the pause will be aborted when the signal is triggered.
27
+ * By default, this will resolve the promise, but you can set {@linkcode rejectOnAbort} to true to reject it instead.
28
+ */
29
+ export declare function pauseFor(time: number, signal?: AbortSignal, rejectOnAbort?: boolean): Promise<void>;
21
30
  /** Options for the `fetchAdvanced()` function */
22
31
  export type FetchAdvancedOpts = Prettify<Partial<{
23
32
  /** Timeout in milliseconds after which the fetch call will be canceled with an AbortController signal */
@@ -46,3 +55,9 @@ export type StringGen = ValueGen<Stringifiable>;
46
55
  * @template TStrUnion The union of strings that the StringGen should yield - this allows for finer type control compared to {@linkcode consumeGen()}
47
56
  */
48
57
  export declare function consumeStringGen<TStrUnion extends string>(strGen: StringGen): Promise<TStrUnion>;
58
+ /**
59
+ * Returns the length of the given list-like object (anything with a numeric `length`, `size` or `count` property, like an array, Map or NodeList).
60
+ * If the object doesn't have any of these properties, it will return 0 by default.
61
+ * Set {@linkcode zeroOnInvalid} to false to return NaN instead of 0 if the object doesn't have any of the properties.
62
+ */
63
+ export declare function getListLength(obj: ListWithLength, zeroOnInvalid?: boolean): number;
@@ -120,35 +120,42 @@ declare function getFallbackLanguage(): string | undefined;
120
120
  * After all %n-formatted values have been injected, the transform functions will be called sequentially in the order they were added.
121
121
  * @example
122
122
  * ```ts
123
- * tr.addTranslations("en", {
124
- * "greeting": {
125
- * "with_username": "Hello, ${USERNAME}",
126
- * "headline_html": "Hello, ${USERNAME}<br><c=red>You have ${UNREAD_NOTIFS} unread notifications.</c>"
123
+ * import { tr, type TrKeys } from "@sv443-network/userutils";
124
+ *
125
+ * const transEn = {
126
+ * "headline": {
127
+ * "basic": "Hello, ${USERNAME}",
128
+ * "html": "Hello, ${USERNAME}<br><c=red>You have ${UNREAD_NOTIFS} unread notifications.</c>"
127
129
  * }
128
- * });
130
+ * } as const;
131
+ *
132
+ * tr.addTranslations("en", transEn);
129
133
  *
130
- * // replace ${PATTERN}
131
- * tr.addTransform(/<\$([A-Z_]+)>/g, ({ matches }) => {
134
+ * // replace ${PATTERN} with predefined values
135
+ * tr.addTransform(/\$\{([A-Z_]+)\}/g, ({ matches }) => {
132
136
  * switch(matches?.[1]) {
133
- * default: return "<UNKNOWN_PATTERN>";
134
- * // these would be grabbed from elsewhere in the application:
135
- * case "USERNAME": return "JohnDoe45";
136
- * case "UNREAD_NOTIFS": return 5;
137
+ * default:
138
+ * return `[UNKNOWN: ${matches?.[1]}]`;
139
+ * // these would be grabbed from elsewhere in the application, like a DataStore, global state or variable:
140
+ * case "USERNAME":
141
+ * return "JohnDoe45";
142
+ * case "UNREAD_NOTIFS":
143
+ * return 5;
137
144
  * }
138
145
  * });
139
146
  *
140
- * // replace <c=red>...</c> with <span class="color red">...</span>
147
+ * // replace <c=red>...</c> with <span style="color: red;">...</span>
141
148
  * tr.addTransform(/<c=([a-z]+)>(.*?)<\/c>/g, ({ matches }) => {
142
149
  * const color = matches?.[1];
143
150
  * const content = matches?.[2];
144
151
  *
145
- * return "<span class=\"color " + color + "\">" + content + "</span>";
152
+ * return `<span style="color: ${color};">${content}</span>`;
146
153
  * });
147
154
  *
148
- * tr.setLanguage("en");
155
+ * const t = tr.use<TrKeys<typeof transEn>>("en");
149
156
  *
150
- * tr("greeting.with_username"); // "Hello, JohnDoe45"
151
- * tr("greeting.headline"); // "<b>Hello, JohnDoe45</b>\nYou have 5 unread notifications."
157
+ * t("headline.basic"); // "Hello, JohnDoe45"
158
+ * t("headline.html"); // "Hello, JohnDoe45<br><span style="color: red;">You have 5 unread notifications.</span>"
152
159
  * ```
153
160
  * @param args A tuple containing the regular expression to match and the transform function to call if the pattern is found in a translation string
154
161
  */
@@ -28,3 +28,11 @@ export type NonEmptyString<TString extends string> = TString extends "" ? never
28
28
  export type Prettify<T> = {
29
29
  [K in keyof T]: T[K];
30
30
  } & {};
31
+ /** Any value that is list-like, i.e. has a numeric length, count or size property */
32
+ export type ListWithLength = {
33
+ length: number;
34
+ } | {
35
+ count: number;
36
+ } | {
37
+ size: number;
38
+ };