@sv443-network/userutils 9.2.1 → 9.4.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 CHANGED
@@ -1,5 +1,25 @@
1
1
  # @sv443-network/userutils
2
2
 
3
+ ## 9.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 5075831: Added `Mixins` class for allowing multiple sources to modify values in a controlled way
8
+ - 99dedfd: Added unit tests
9
+ - 7530fd0: Added `Debouncer.getListeners()` method to get an array of all listener functions
10
+ - 48306da: Added `stores` filter parameter to the `DataStoreSerializer` methods `loadStoresData()`, `resetStoresData()` and `deleteStoresData()`
11
+
12
+ ### Patch Changes
13
+
14
+ - f6a68c7: Fixed error when calling `interceptEvent()` in a non-GM environment
15
+
16
+ ## 9.3.0
17
+
18
+ ### Minor Changes
19
+
20
+ - 89d7970: Added `DataStoreSerializer` methods `serializePartial()` and `deserializePartial()` for partial data exports and imports
21
+ - 5d71770: Added function `purifyObj()` to remove an object's prototype chain (i.e. omit all inherited properties like `toString`, `__proto__`, etc.)
22
+
3
23
  ## 9.2.1
4
24
 
5
25
  ### Patch Changes
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  <!-- #region Description -->
4
4
  ## UserUtils
5
5
  General purpose DOM/GreaseMonkey library that allows you to register listeners for when CSS selectors exist, intercept events, create persistent & synchronous data stores, modify the DOM more easily and much more.
6
- Contains builtin TypeScript declarations. Supports ESM and CJS imports via a bundler and global declaration via `@require`
6
+ Contains builtin TypeScript declarations. Supports ESM and CJS imports via a bundler and global declaration via `@require` or `<script>`
7
7
  The library works in any DOM environment with or without the [GreaseMonkey API](https://wiki.greasespot.net/Greasemonkey_Manual:API), but some features will be unavailable or limited.
8
8
 
9
9
  You may want to check out my [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.
@@ -11,12 +11,14 @@ If you like using this library, please consider [supporting the development ❤
11
11
 
12
12
  <br>
13
13
 
14
- [![minified bundle size badge](https://badgen.net/bundlephobia/min/@sv443-network/userutils)](https://bundlephobia.com/package/@sv443-network/userutils)
15
- [![minified and gzipped bundle size badge](https://badgen.net/bundlephobia/minzip/@sv443-network/userutils)](https://bundlephobia.com/package/@sv443-network/userutils)
16
- [![tree shaking support badge](https://badgen.net/bundlephobia/tree-shaking/@sv443-network/userutils)](https://bundlephobia.com/package/@sv443-network/userutils)
14
+ [![Tree shaking support badge](https://badgen.net/bundlephobia/tree-shaking/@sv443-network/userutils)](https://bundlephobia.com/package/@sv443-network/userutils)
15
+ [![Code coverage percentage badge](https://img.shields.io/coverallsCoverage/github/Sv443-Network/UserUtils?branch=main)](https://coveralls.io/github/Sv443-Network/UserUtils)
16
+ [![Minified bundle size badge](https://badgen.net/bundlephobia/min/@sv443-network/userutils)](https://bundlephobia.com/package/@sv443-network/userutils)
17
+ [![Minified and gzipped bundle size badge](https://badgen.net/bundlephobia/minzip/@sv443-network/userutils)](https://bundlephobia.com/package/@sv443-network/userutils)
17
18
 
18
- [![github stargazers badge](https://badgen.net/github/stars/Sv443-Network/UserUtils?icon=github)](https://github.com/Sv443-Network/UserUtils/stargazers)
19
- [![discord server badge](https://badgen.net/discord/online-members/aBH4uRG?icon=discord)](https://dc.sv443.net/)
19
+ [![Discord server badge](https://badgen.net/discord/online-members/aBH4uRG?icon=discord)](https://dc.sv443.net/)
20
+ [![Github stargazers badge](https://badgen.net/github/stars/Sv443-Network/UserUtils?icon=github)](https://github.com/Sv443-Network/UserUtils/stargazers)
21
+ [![Support development on Github Sponsors badge](https://img.shields.io/github/sponsors/Sv443?icon=github)](https://github.com/sponsors/Sv443)
20
22
 
21
23
  <sup>
22
24
  View the documentation of previous major releases:
@@ -63,6 +65,7 @@ View the documentation of previous major releases:
63
65
  - [`DataStore`](./docs.md#datastore) - class that manages a hybrid sync & async persistent JSON database, including data migration
64
66
  - [`DataStoreSerializer`](./docs.md#datastoreserializer) - class for importing & exporting data of multiple DataStore instances, including compression, checksumming and running migrations
65
67
  - [`Dialog`](./docs.md#dialog) - class for creating custom modal dialogs with a promise-based API and a generic, default style
68
+ - [`Mixins`](./docs.md#mixins) - class for creating mixin functions that allow multiple sources to modify a target value in a highly flexible way
66
69
  - [`NanoEmitter`](./docs.md#nanoemitter) - tiny event emitter class with a focus on performance and simplicity (based on [nanoevents](https://npmjs.com/package/nanoevents))
67
70
  - [`Debouncer`](./docs.md#debouncer) - class for debouncing function calls with a given timeout
68
71
  - [`debounce()`](./docs.md#debounce) - function wrapper for the Debouncer class for easier usage
@@ -77,6 +80,7 @@ View the documentation of previous major releases:
77
80
  - [`consumeGen()`](./docs.md#consumegen) - consumes a ValueGen and returns the value
78
81
  - [`consumeStringGen()`](./docs.md#consumestringgen) - consumes a StringGen and returns the string
79
82
  - [`getListLength()`](./docs.md#getlistlength) - get the length of any object with a numeric `length`, `count` or `size` property
83
+ - [`purifyObj()`](./docs.md#purifyobj) - removes the prototype chain (all default properties like `toString`, `__proto__`, etc.) from an object
80
84
  - [**Arrays:**](./docs.md#arrays)
81
85
  - [`randomItem()`](./docs.md#randomitem) - returns a random item from an array
82
86
  - [`randomItemIndex()`](./docs.md#randomitemindex) - returns a tuple of a random item and its index from an array
package/dist/index.cjs CHANGED
@@ -3,6 +3,8 @@
3
3
  var nanoevents = require('nanoevents');
4
4
 
5
5
  var __defProp = Object.defineProperty;
6
+ var __defProps = Object.defineProperties;
7
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
6
8
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
7
9
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
10
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
@@ -18,6 +20,7 @@ var __spreadValues = (a, b) => {
18
20
  }
19
21
  return a;
20
22
  };
23
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
21
24
  var __objRest = (source, exclude) => {
22
25
  var target = {};
23
26
  for (var prop in source)
@@ -250,16 +253,16 @@ function addGlobalStyle(style) {
250
253
  function preloadImages(srcUrls, rejects = false) {
251
254
  const promises = srcUrls.map((src) => new Promise((res, rej) => {
252
255
  const image = new Image();
253
- image.src = src;
254
256
  image.addEventListener("load", () => res(image));
255
257
  image.addEventListener("error", (evt) => rejects && rej(evt));
258
+ image.src = src;
256
259
  }));
257
260
  return Promise.allSettled(promises);
258
261
  }
259
262
  function openInNewTab(href, background, additionalProps) {
260
- var _a;
261
263
  try {
262
- (_a = GM.openInTab) == null ? void 0 : _a.call(GM, href, background);
264
+ if (typeof window.GM === "object")
265
+ GM.openInTab(href, background);
263
266
  } catch (e) {
264
267
  const openElem = document.createElement("a");
265
268
  Object.assign(openElem, __spreadValues({
@@ -276,12 +279,17 @@ function openInNewTab(href, background, additionalProps) {
276
279
  });
277
280
  document.body.appendChild(openElem);
278
281
  openElem.click();
279
- setTimeout(openElem.remove, 0);
282
+ setTimeout(() => {
283
+ try {
284
+ openElem.remove();
285
+ } catch (e2) {
286
+ }
287
+ }, 0);
280
288
  }
281
289
  }
282
290
  function interceptEvent(eventObject, eventName, predicate = () => true) {
283
291
  var _a;
284
- if (((_a = GM == null ? undefined : GM.info) == null ? undefined : _a.scriptHandler) && GM.info.scriptHandler === "FireMonkey" && (eventObject === window || eventObject === getUnsafeWindow()))
292
+ if (typeof window.GM === "object" && ((_a = GM == null ? undefined : GM.info) == null ? undefined : _a.scriptHandler) && GM.info.scriptHandler === "FireMonkey" && (eventObject === window || eventObject === getUnsafeWindow()))
285
293
  throw new PlatformError("Intercepting window events is not supported on FireMonkey due to the isolated context the userscript runs in.");
286
294
  Error.stackTraceLimit = Math.max(Error.stackTraceLimit, 100);
287
295
  if (isNaN(Error.stackTraceLimit))
@@ -441,6 +449,8 @@ function computeHash(input, algorithm = "SHA-256") {
441
449
  });
442
450
  }
443
451
  function randomId(length = 16, radix = 16, enhancedEntropy = false, randomCase = true) {
452
+ if (length < 1)
453
+ throw new RangeError("The length argument must be at least 1");
444
454
  if (radix < 2 || radix > 36)
445
455
  throw new RangeError("The radix argument must be between 2 and 36");
446
456
  let arr = [];
@@ -747,14 +757,15 @@ var DataStoreSerializer = class _DataStoreSerializer {
747
757
  });
748
758
  }
749
759
  /**
750
- * Serializes the data stores into a string.
760
+ * Serializes only a subset of the data stores into a string.
761
+ * @param stores An array of store IDs or functions that take a store ID and return a boolean
751
762
  * @param useEncoding Whether to encode the data using each DataStore's `encodeData()` method
752
763
  * @param stringified Whether to return the result as a string or as an array of `SerializedDataStore` objects
753
764
  */
754
- serialize(useEncoding = true, stringified = true) {
765
+ serializePartial(stores, useEncoding = true, stringified = true) {
755
766
  return __async(this, null, function* () {
756
767
  const serData = [];
757
- for (const storeInst of this.stores) {
768
+ for (const storeInst of this.stores.filter((s) => typeof stores === "function" ? stores(s.id) : stores.includes(s.id))) {
758
769
  const data = useEncoding && storeInst.encodingEnabled() ? yield storeInst.encodeData(JSON.stringify(storeInst.getData())) : JSON.stringify(storeInst.getData());
759
770
  serData.push({
760
771
  id: storeInst.id,
@@ -768,15 +779,25 @@ var DataStoreSerializer = class _DataStoreSerializer {
768
779
  });
769
780
  }
770
781
  /**
771
- * Deserializes the data exported via {@linkcode serialize()} and imports it into the DataStore instances.
782
+ * Serializes the data stores into a string.
783
+ * @param useEncoding Whether to encode the data using each DataStore's `encodeData()` method
784
+ * @param stringified Whether to return the result as a string or as an array of `SerializedDataStore` objects
785
+ */
786
+ serialize(useEncoding = true, stringified = true) {
787
+ return __async(this, null, function* () {
788
+ return this.serializePartial(this.stores.map((s) => s.id), useEncoding, stringified);
789
+ });
790
+ }
791
+ /**
792
+ * Deserializes the data exported via {@linkcode serialize()} and imports only a subset into the DataStore instances.
772
793
  * Also triggers the migration process if the data format has changed.
773
794
  */
774
- deserialize(serializedData) {
795
+ deserializePartial(stores, data) {
775
796
  return __async(this, null, function* () {
776
- const deserStores = typeof serializedData === "string" ? JSON.parse(serializedData) : serializedData;
777
- if (!Array.isArray(deserStores) || !deserStores.every(_DataStoreSerializer.isSerializedDataStore))
797
+ const deserStores = typeof data === "string" ? JSON.parse(data) : data;
798
+ if (!Array.isArray(deserStores) || !deserStores.every(_DataStoreSerializer.isSerializedDataStoreObj))
778
799
  throw new TypeError("Invalid serialized data format! Expected an array of SerializedDataStore objects.");
779
- for (const storeData of deserStores) {
800
+ for (const storeData of deserStores.filter((s) => typeof stores === "function" ? stores(s.id) : stores.includes(s.id))) {
780
801
  const storeInst = this.stores.find((s) => s.id === storeData.id);
781
802
  if (!storeInst)
782
803
  throw new Error(`DataStore instance with ID "${storeData.id}" not found! Make sure to provide it in the DataStoreSerializer constructor.`);
@@ -795,44 +816,71 @@ Has: ${checksum}`);
795
816
  }
796
817
  });
797
818
  }
819
+ /**
820
+ * Deserializes the data exported via {@linkcode serialize()} and imports the data into all matching DataStore instances.
821
+ * Also triggers the migration process if the data format has changed.
822
+ */
823
+ deserialize(data) {
824
+ return __async(this, null, function* () {
825
+ return this.deserializePartial(this.stores.map((s) => s.id), data);
826
+ });
827
+ }
798
828
  /**
799
829
  * Loads the persistent data of the DataStore instances into the in-memory cache.
800
830
  * Also triggers the migration process if the data format has changed.
831
+ * @param stores An array of store IDs or a function that takes the store IDs and returns a boolean - if omitted, all stores will be loaded
801
832
  * @returns Returns a PromiseSettledResult array with the results of each DataStore instance in the format `{ id: string, data: object }`
802
833
  */
803
- loadStoresData() {
834
+ loadStoresData(stores) {
804
835
  return __async(this, null, function* () {
805
- return Promise.allSettled(this.stores.map(
806
- (store) => __async(this, null, function* () {
836
+ return Promise.allSettled(
837
+ this.getStoresFiltered(stores).map((store) => __async(this, null, function* () {
807
838
  return {
808
839
  id: store.id,
809
840
  data: yield store.loadData()
810
841
  };
811
- })
812
- ));
842
+ }))
843
+ );
813
844
  });
814
845
  }
815
- /** Resets the persistent data of the DataStore instances to their default values. */
816
- resetStoresData() {
846
+ /**
847
+ * Resets the persistent and in-memory data of the DataStore instances to their default values.
848
+ * @param stores An array of store IDs or a function that takes the store IDs and returns a boolean - if omitted, all stores will be affected
849
+ */
850
+ resetStoresData(stores) {
817
851
  return __async(this, null, function* () {
818
- return Promise.allSettled(this.stores.map((store) => store.saveDefaultData()));
852
+ return Promise.allSettled(
853
+ this.getStoresFiltered(stores).map((store) => store.saveDefaultData())
854
+ );
819
855
  });
820
856
  }
821
857
  /**
822
858
  * Deletes the persistent data of the DataStore instances.
823
- * Leaves the in-memory data untouched.
859
+ * Leaves the in-memory data untouched.
860
+ * @param stores An array of store IDs or a function that takes the store IDs and returns a boolean - if omitted, all stores will be affected
824
861
  */
825
- deleteStoresData() {
862
+ deleteStoresData(stores) {
826
863
  return __async(this, null, function* () {
827
- return Promise.allSettled(this.stores.map((store) => store.deleteData()));
864
+ return Promise.allSettled(
865
+ this.getStoresFiltered(stores).map((store) => store.deleteData())
866
+ );
828
867
  });
829
868
  }
869
+ /** Checks if a given value is an array of SerializedDataStore objects */
870
+ static isSerializedDataStoreObjArray(obj) {
871
+ return Array.isArray(obj) && obj.every((o) => typeof o === "object" && o !== null && "id" in o && "data" in o && "formatVersion" in o && "encoded" in o);
872
+ }
830
873
  /** Checks if a given value is a SerializedDataStore object */
831
- static isSerializedDataStore(obj) {
874
+ static isSerializedDataStoreObj(obj) {
832
875
  return typeof obj === "object" && obj !== null && "id" in obj && "data" in obj && "formatVersion" in obj && "encoded" in obj;
833
876
  }
877
+ /** Returns the DataStore instances whose IDs match the provided array or function */
878
+ getStoresFiltered(stores) {
879
+ return this.stores.filter((s) => typeof stores === "undefined" ? true : Array.isArray(stores) ? stores.includes(s.id) : stores(s.id));
880
+ }
834
881
  };
835
882
  var NanoEmitter = class {
883
+ /** Creates a new instance of NanoEmitter - a lightweight event emitter with helper methods and a strongly typed event map */
836
884
  constructor(options = {}) {
837
885
  __publicField(this, "events", nanoevents.createNanoEvents());
838
886
  __publicField(this, "eventUnsubscribes", []);
@@ -841,7 +889,27 @@ var NanoEmitter = class {
841
889
  publicEmit: false
842
890
  }, options);
843
891
  }
844
- /** Subscribes to an event - returns a function that unsubscribes the event listener */
892
+ /**
893
+ * Subscribes to an event and calls the callback when it's emitted.
894
+ * @param event The event to subscribe to. Use `as "_"` in case your event names aren't thoroughly typed (like when using a template literal, e.g. \`event-${val}\` as "_")
895
+ * @returns Returns a function that can be called to unsubscribe the event listener
896
+ * @example ```ts
897
+ * const emitter = new NanoEmitter<{
898
+ * foo: (bar: string) => void;
899
+ * }>({
900
+ * publicEmit: true,
901
+ * });
902
+ *
903
+ * let i = 0;
904
+ * const unsub = emitter.on("foo", (bar) => {
905
+ * // unsubscribe after 10 events:
906
+ * if(++i === 10) unsub();
907
+ * console.log(bar);
908
+ * });
909
+ *
910
+ * emitter.emit("foo", "bar");
911
+ * ```
912
+ */
845
913
  on(event, cb) {
846
914
  let unsub;
847
915
  const unsubProxy = () => {
@@ -854,19 +922,43 @@ var NanoEmitter = class {
854
922
  this.eventUnsubscribes.push(unsub);
855
923
  return unsubProxy;
856
924
  }
857
- /** Subscribes to an event and calls the callback or resolves the Promise only once */
925
+ /**
926
+ * Subscribes to an event and calls the callback or resolves the Promise only once when it's emitted.
927
+ * @param event The event to subscribe to. Use `as "_"` in case your event names aren't thoroughly typed (like when using a template literal, e.g. \`event-${val}\` as "_")
928
+ * @param cb The callback to call when the event is emitted - if provided or not, the returned Promise will resolve with the event arguments
929
+ * @returns Returns a Promise that resolves with the event arguments when the event is emitted
930
+ * @example ```ts
931
+ * const emitter = new NanoEmitter<{
932
+ * foo: (bar: string) => void;
933
+ * }>();
934
+ *
935
+ * // Promise syntax:
936
+ * const [bar] = await emitter.once("foo");
937
+ * console.log(bar);
938
+ *
939
+ * // Callback syntax:
940
+ * emitter.once("foo", (bar) => console.log(bar));
941
+ * ```
942
+ */
858
943
  once(event, cb) {
859
944
  return new Promise((resolve) => {
860
945
  let unsub;
861
946
  const onceProxy = (...args) => {
862
- unsub();
863
947
  cb == null ? undefined : cb(...args);
948
+ unsub == null ? undefined : unsub();
864
949
  resolve(args);
865
950
  };
866
- unsub = this.on(event, onceProxy);
951
+ unsub = this.events.on(event, onceProxy);
952
+ this.eventUnsubscribes.push(unsub);
867
953
  });
868
954
  }
869
- /** Emits an event on this instance - Needs `publicEmit` to be set to true in the constructor! */
955
+ /**
956
+ * Emits an event on this instance.
957
+ * ⚠️ Needs `publicEmit` to be set to true in the NanoEmitter constructor or super() call!
958
+ * @param event The event to emit
959
+ * @param args The arguments to pass to the event listeners
960
+ * @returns Returns true if `publicEmit` is true and the event was emitted successfully
961
+ */
870
962
  emit(event, ...args) {
871
963
  if (this.emitterOptions.publicEmit) {
872
964
  this.events.emit(event, ...args);
@@ -874,7 +966,7 @@ var NanoEmitter = class {
874
966
  }
875
967
  return false;
876
968
  }
877
- /** Unsubscribes all event listeners */
969
+ /** Unsubscribes all event listeners from this instance */
878
970
  unsubscribeAll() {
879
971
  for (const unsub of this.eventUnsubscribes)
880
972
  unsub();
@@ -914,6 +1006,10 @@ var Debouncer = class extends NanoEmitter {
914
1006
  removeAllListeners() {
915
1007
  this.listeners = [];
916
1008
  }
1009
+ /** Returns all registered listeners */
1010
+ getListeners() {
1011
+ return this.listeners;
1012
+ }
917
1013
  //#region timeout
918
1014
  /** Sets the timeout for the debouncer */
919
1015
  setTimeout(timeout) {
@@ -942,7 +1038,7 @@ var Debouncer = class extends NanoEmitter {
942
1038
  const cl = (...a) => {
943
1039
  this.queuedCall = undefined;
944
1040
  this.emit("call", ...a);
945
- this.listeners.forEach((l) => l.apply(this, a));
1041
+ this.listeners.forEach((l) => l.call(this, ...a));
946
1042
  };
947
1043
  const setRepeatTimeout = () => {
948
1044
  this.activeTimeout = setTimeout(() => {
@@ -1416,8 +1512,6 @@ function autoPlural(term, num, pluralType = "auto") {
1416
1512
  return `${term}${n === 1 ? "" : "s"}`;
1417
1513
  case "-ies":
1418
1514
  return `${String(term).slice(0, -1)}${n === 1 ? "y" : "ies"}`;
1419
- default:
1420
- return String(term);
1421
1515
  }
1422
1516
  }
1423
1517
  function insertValues(input, ...values) {
@@ -1472,6 +1566,115 @@ function consumeStringGen(strGen) {
1472
1566
  function getListLength(obj, zeroOnInvalid = true) {
1473
1567
  return "length" in obj ? obj.length : "size" in obj ? obj.size : "count" in obj ? obj.count : zeroOnInvalid ? 0 : NaN;
1474
1568
  }
1569
+ function purifyObj(obj) {
1570
+ return Object.assign(/* @__PURE__ */ Object.create(null), obj);
1571
+ }
1572
+
1573
+ // lib/Mixins.ts
1574
+ var Mixins = class {
1575
+ /**
1576
+ * Creates a new Mixins instance.
1577
+ * @param config Configuration object to customize the behavior.
1578
+ */
1579
+ constructor(config = {}) {
1580
+ /** List of all registered mixins */
1581
+ __publicField(this, "mixins", []);
1582
+ /** Default configuration object for mixins */
1583
+ __publicField(this, "defaultMixinCfg");
1584
+ /** Whether the priorities should auto-increment if not specified */
1585
+ __publicField(this, "autoIncPrioEnabled");
1586
+ /** The current auto-increment priority counter */
1587
+ __publicField(this, "autoIncPrioCounter", /* @__PURE__ */ new Map());
1588
+ var _a, _b, _c;
1589
+ this.defaultMixinCfg = purifyObj({
1590
+ priority: (_a = config.defaultPriority) != null ? _a : 0,
1591
+ stopPropagation: (_b = config.defaultStopPropagation) != null ? _b : false,
1592
+ signal: config.defaultSignal
1593
+ });
1594
+ this.autoIncPrioEnabled = (_c = config.autoIncrementPriority) != null ? _c : false;
1595
+ }
1596
+ //#region public
1597
+ /**
1598
+ * Adds a mixin function to the given {@linkcode mixinKey}.
1599
+ * If no priority is specified, it will be calculated via the protected method {@linkcode calcPriority()} based on the constructor configuration, or fall back to the default priority.
1600
+ * @param mixinKey The key to identify the mixin function.
1601
+ * @param mixinFn The function to be called to apply the mixin. The first argument is the input value, the second argument is the context object (if any).
1602
+ * @param config Configuration object to customize the mixin behavior, or just the priority if a number is passed.
1603
+ * @returns Returns a cleanup function, to be called when this mixin is no longer needed.
1604
+ */
1605
+ add(mixinKey, mixinFn, config = purifyObj({})) {
1606
+ const calcPrio = typeof config === "number" ? config : this.calcPriority(mixinKey, config);
1607
+ const mixin = purifyObj(__spreadValues(__spreadValues(__spreadProps(__spreadValues({}, this.defaultMixinCfg), {
1608
+ key: mixinKey,
1609
+ fn: mixinFn
1610
+ }), typeof config === "object" ? config : {}), typeof calcPrio === "number" && !isNaN(calcPrio) ? { priority: calcPrio } : {}));
1611
+ this.mixins.push(mixin);
1612
+ const rem = () => {
1613
+ this.mixins = this.mixins.filter((m) => m !== mixin);
1614
+ };
1615
+ if (mixin.signal)
1616
+ mixin.signal.addEventListener("abort", rem, { once: true });
1617
+ return rem;
1618
+ }
1619
+ /** Returns a list of all added mixins with their keys and configuration objects, but not their functions */
1620
+ list() {
1621
+ return this.mixins.map((_a) => {
1622
+ var _b = _a, rest = __objRest(_b, ["fn"]);
1623
+ return rest;
1624
+ });
1625
+ }
1626
+ /**
1627
+ * Applies all mixins with the given key to the input value, respecting the priority and stopPropagation settings.
1628
+ * If additional context is set in the MixinMap, it will need to be passed as the third argument.
1629
+ * @returns The modified value after all mixins have been applied.
1630
+ */
1631
+ resolve(mixinKey, inputValue, ...inputCtx) {
1632
+ const mixins = this.mixins.filter((m) => m.key === mixinKey);
1633
+ const sortedMixins = [...mixins].sort((a, b) => b.priority - a.priority);
1634
+ let result = inputValue;
1635
+ for (let i = 0; i < sortedMixins.length; i++) {
1636
+ const mixin = sortedMixins[i];
1637
+ result = mixin.fn(result, ...inputCtx);
1638
+ if (result instanceof Promise) {
1639
+ return (() => __async(this, null, function* () {
1640
+ result = yield result;
1641
+ if (mixin.stopPropagation)
1642
+ return result;
1643
+ for (let j = i + 1; j < sortedMixins.length; j++) {
1644
+ const mixin2 = sortedMixins[j];
1645
+ result = yield mixin2.fn(result, ...inputCtx);
1646
+ if (mixin2.stopPropagation)
1647
+ break;
1648
+ }
1649
+ return result;
1650
+ }))();
1651
+ } else if (mixin.stopPropagation)
1652
+ break;
1653
+ }
1654
+ return result;
1655
+ }
1656
+ //#region protected
1657
+ /** Calculates the priority for a mixin based on the given configuration and the current auto-increment state of the instance */
1658
+ calcPriority(mixinKey, config) {
1659
+ var _a;
1660
+ if (config.priority !== undefined)
1661
+ return undefined;
1662
+ if (!this.autoIncPrioEnabled)
1663
+ return (_a = config.priority) != null ? _a : this.defaultMixinCfg.priority;
1664
+ if (!this.autoIncPrioCounter.has(mixinKey))
1665
+ this.autoIncPrioCounter.set(mixinKey, this.defaultMixinCfg.priority);
1666
+ let prio = this.autoIncPrioCounter.get(mixinKey);
1667
+ while (this.mixins.some((m) => m.key === mixinKey && m.priority === prio))
1668
+ prio++;
1669
+ this.autoIncPrioCounter.set(mixinKey, prio + 1);
1670
+ return prio;
1671
+ }
1672
+ /** Removes all mixins with the given key */
1673
+ removeAll(mixinKey) {
1674
+ this.mixins.filter((m) => m.key === mixinKey);
1675
+ this.mixins = this.mixins.filter((m) => m.key !== mixinKey);
1676
+ }
1677
+ };
1475
1678
 
1476
1679
  // lib/SelectorObserver.ts
1477
1680
  var SelectorObserver = class {
@@ -1730,15 +1933,15 @@ function getFallbackLanguage() {
1730
1933
  return fallbackLang;
1731
1934
  }
1732
1935
  function addTransform(transform) {
1733
- const [pattern, fn] = transform;
1936
+ const [regex, fn] = transform;
1734
1937
  valTransforms.push({
1735
1938
  fn,
1736
- regex: typeof pattern === "string" ? new RegExp(pattern, "gm") : pattern
1939
+ regex
1737
1940
  });
1738
1941
  }
1739
1942
  function deleteTransform(patternOrFn) {
1740
1943
  const idx = valTransforms.findIndex(
1741
- (t) => typeof patternOrFn === "function" ? t.fn === patternOrFn : typeof patternOrFn === "string" ? t.regex.source === patternOrFn : t.regex === patternOrFn
1944
+ (t) => typeof patternOrFn === "function" ? t.fn === patternOrFn : t.regex === patternOrFn
1742
1945
  );
1743
1946
  if (idx !== -1) {
1744
1947
  valTransforms.splice(idx, 1);
@@ -1815,6 +2018,7 @@ exports.DataStoreSerializer = DataStoreSerializer;
1815
2018
  exports.Debouncer = Debouncer;
1816
2019
  exports.Dialog = Dialog;
1817
2020
  exports.MigrationError = MigrationError;
2021
+ exports.Mixins = Mixins;
1818
2022
  exports.NanoEmitter = NanoEmitter;
1819
2023
  exports.PlatformError = PlatformError;
1820
2024
  exports.SelectorObserver = SelectorObserver;
@@ -1853,6 +2057,7 @@ exports.openInNewTab = openInNewTab;
1853
2057
  exports.pauseFor = pauseFor;
1854
2058
  exports.preloadImages = preloadImages;
1855
2059
  exports.probeElementStyle = probeElementStyle;
2060
+ exports.purifyObj = purifyObj;
1856
2061
  exports.randRange = randRange;
1857
2062
  exports.randomId = randomId;
1858
2063
  exports.randomItem = randomItem;