@sv443-network/userutils 6.2.0 → 7.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.
@@ -7,8 +7,8 @@
7
7
 
8
8
  // ==UserLibrary==
9
9
  // @name UserUtils
10
- // @description 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
11
- // @version 6.2.0
10
+ // @description Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, create persistent & synchronous data stores, modify the DOM more easily and more
11
+ // @version 7.0.0
12
12
  // @license MIT
13
13
  // @copyright Sv443 (https://github.com/Sv443)
14
14
 
@@ -99,14 +99,6 @@ var UserUtils = (function (exports) {
99
99
  throw new TypeError(`Parameter "min" can't be bigger than "max"`);
100
100
  return Math.floor(Math.random() * (max - min + 1)) + min;
101
101
  }
102
- function randomId(length = 16, radix = 16) {
103
- const arr = new Uint8Array(length);
104
- crypto.getRandomValues(arr);
105
- return Array.from(
106
- arr,
107
- (v) => mapRange(v, 0, 255, 0, radix).toString(radix).substring(0, 1)
108
- ).join("");
109
- }
110
102
 
111
103
  // lib/array.ts
112
104
  function randomItem(array) {
@@ -142,28 +134,32 @@ var UserUtils = (function (exports) {
142
134
  * Creates an instance of DataStore to manage a sync & async database that is cached in memory and persistently saved across sessions.
143
135
  * Supports migrating data from older versions to newer ones and populating the cache with default data if no persistent data is found.
144
136
  *
145
- * ⚠️ Requires the directives `@grant GM.getValue` and `@grant GM.setValue`
137
+ * ⚠️ Requires the directives `@grant GM.getValue` and `@grant GM.setValue` if the storageMethod is left as the default of `"GM"`
146
138
  * ⚠️ Make sure to call {@linkcode loadData()} at least once after creating an instance, or the returned data will be the same as `options.defaultData`
147
139
  *
148
- * @template TData The type of the data that is saved in persistent storage (will be automatically inferred from `options.defaultData`) - this should also be the type of the data format associated with the current `options.formatVersion`
140
+ * @template TData The type of the data that is saved in persistent storage for the currently set format version (will be automatically inferred from `defaultData` if not provided) - **This has to be a JSON-compatible object!** (no undefined, circular references, etc.)
149
141
  * @param options The options for this DataStore instance
150
- */
142
+ */
151
143
  constructor(options) {
152
144
  __publicField(this, "id");
153
145
  __publicField(this, "formatVersion");
154
146
  __publicField(this, "defaultData");
155
- __publicField(this, "cachedData");
156
- __publicField(this, "migrations");
157
147
  __publicField(this, "encodeData");
158
148
  __publicField(this, "decodeData");
149
+ __publicField(this, "storageMethod");
150
+ __publicField(this, "cachedData");
151
+ __publicField(this, "migrations");
152
+ var _a;
159
153
  this.id = options.id;
160
154
  this.formatVersion = options.formatVersion;
161
155
  this.defaultData = options.defaultData;
162
156
  this.cachedData = options.defaultData;
163
157
  this.migrations = options.migrations;
158
+ this.storageMethod = (_a = options.storageMethod) != null ? _a : "GM";
164
159
  this.encodeData = options.encodeData;
165
160
  this.decodeData = options.decodeData;
166
161
  }
162
+ //#region public
167
163
  /**
168
164
  * Loads the data saved in persistent storage into the in-memory cache and also returns it.
169
165
  * Automatically populates persistent storage with default data if it doesn't contain any data yet.
@@ -172,19 +168,25 @@ var UserUtils = (function (exports) {
172
168
  loadData() {
173
169
  return __async(this, null, function* () {
174
170
  try {
175
- const gmData = yield GM.getValue(`_uucfg-${this.id}`, this.defaultData);
176
- let gmFmtVer = Number(yield GM.getValue(`_uucfgver-${this.id}`));
171
+ const gmData = yield this.getValue(`_uucfg-${this.id}`, JSON.stringify(this.defaultData));
172
+ let gmFmtVer = Number(yield this.getValue(`_uucfgver-${this.id}`, NaN));
177
173
  if (typeof gmData !== "string") {
178
174
  yield this.saveDefaultData();
179
175
  return __spreadValues({}, this.defaultData);
180
176
  }
181
- const isEncoded = yield GM.getValue(`_uucfgenc-${this.id}`, false);
182
- if (isNaN(gmFmtVer))
183
- yield GM.setValue(`_uucfgver-${this.id}`, gmFmtVer = this.formatVersion);
177
+ const isEncoded = Boolean(yield this.getValue(`_uucfgenc-${this.id}`, false));
178
+ let saveData = false;
179
+ if (isNaN(gmFmtVer)) {
180
+ yield this.setValue(`_uucfgver-${this.id}`, gmFmtVer = this.formatVersion);
181
+ saveData = true;
182
+ }
184
183
  let parsed = yield this.deserializeData(gmData, isEncoded);
185
184
  if (gmFmtVer < this.formatVersion && this.migrations)
186
185
  parsed = yield this.runMigrations(parsed, gmFmtVer);
187
- return __spreadValues({}, this.cachedData = parsed);
186
+ if (saveData)
187
+ yield this.setData(parsed);
188
+ this.cachedData = __spreadValues({}, parsed);
189
+ return this.cachedData;
188
190
  } catch (err) {
189
191
  console.warn("Error while parsing JSON data, resetting it to the default value.", err);
190
192
  yield this.saveDefaultData();
@@ -195,19 +197,20 @@ var UserUtils = (function (exports) {
195
197
  /**
196
198
  * Returns a copy of the data from the in-memory cache.
197
199
  * Use {@linkcode loadData()} to get fresh data from persistent storage (usually not necessary since the cache should always exactly reflect persistent storage).
200
+ * @param deepCopy Whether to return a deep copy of the data (default: `false`) - only necessary if your data object is nested and may have a bigger performance impact if enabled
198
201
  */
199
- getData() {
200
- return this.deepCopy(this.cachedData);
202
+ getData(deepCopy = false) {
203
+ return deepCopy ? this.deepCopy(this.cachedData) : __spreadValues({}, this.cachedData);
201
204
  }
202
205
  /** Saves the data synchronously to the in-memory cache and asynchronously to the persistent storage */
203
206
  setData(data) {
204
207
  this.cachedData = data;
205
- const useEncoding = Boolean(this.encodeData && this.decodeData);
208
+ const useEncoding = this.encodingEnabled();
206
209
  return new Promise((resolve) => __async(this, null, function* () {
207
210
  yield Promise.all([
208
- GM.setValue(`_uucfg-${this.id}`, yield this.serializeData(data, useEncoding)),
209
- GM.setValue(`_uucfgver-${this.id}`, this.formatVersion),
210
- GM.setValue(`_uucfgenc-${this.id}`, useEncoding)
211
+ this.setValue(`_uucfg-${this.id}`, yield this.serializeData(data, useEncoding)),
212
+ this.setValue(`_uucfgver-${this.id}`, this.formatVersion),
213
+ this.setValue(`_uucfgenc-${this.id}`, useEncoding)
211
214
  ]);
212
215
  resolve();
213
216
  }));
@@ -216,12 +219,12 @@ var UserUtils = (function (exports) {
216
219
  saveDefaultData() {
217
220
  return __async(this, null, function* () {
218
221
  this.cachedData = this.defaultData;
219
- const useEncoding = Boolean(this.encodeData && this.decodeData);
222
+ const useEncoding = this.encodingEnabled();
220
223
  return new Promise((resolve) => __async(this, null, function* () {
221
224
  yield Promise.all([
222
- GM.setValue(`_uucfg-${this.id}`, yield this.serializeData(this.defaultData, useEncoding)),
223
- GM.setValue(`_uucfgver-${this.id}`, this.formatVersion),
224
- GM.setValue(`_uucfgenc-${this.id}`, useEncoding)
225
+ this.setValue(`_uucfg-${this.id}`, yield this.serializeData(this.defaultData, useEncoding)),
226
+ this.setValue(`_uucfgver-${this.id}`, this.formatVersion),
227
+ this.setValue(`_uucfgenc-${this.id}`, useEncoding)
225
228
  ]);
226
229
  resolve();
227
230
  }));
@@ -237,14 +240,25 @@ var UserUtils = (function (exports) {
237
240
  deleteData() {
238
241
  return __async(this, null, function* () {
239
242
  yield Promise.all([
240
- GM.deleteValue(`_uucfg-${this.id}`),
241
- GM.deleteValue(`_uucfgver-${this.id}`),
242
- GM.deleteValue(`_uucfgenc-${this.id}`)
243
+ this.deleteValue(`_uucfg-${this.id}`),
244
+ this.deleteValue(`_uucfgver-${this.id}`),
245
+ this.deleteValue(`_uucfgenc-${this.id}`)
243
246
  ]);
244
247
  });
245
248
  }
246
- /** Runs all necessary migration functions consecutively - may be overwritten in a subclass */
247
- runMigrations(oldData, oldFmtVer) {
249
+ /** Returns whether encoding and decoding are enabled for this DataStore instance */
250
+ encodingEnabled() {
251
+ return Boolean(this.encodeData && this.decodeData);
252
+ }
253
+ //#region migrations
254
+ /**
255
+ * Runs all necessary migration functions consecutively and saves the result to the in-memory cache and persistent storage and also returns it.
256
+ * This method is automatically called by {@linkcode loadData()} if the data format has changed since the last time the data was saved.
257
+ * Though calling this method manually is not necessary, it can be useful if you want to run migrations for special occasions like a user importing potentially outdated data that has been previously exported.
258
+ *
259
+ * If one of the migrations fails, the data will be reset to the default value if `resetOnError` is set to `true` (default). Otherwise, an error will be thrown and no data will be saved.
260
+ */
261
+ runMigrations(oldData, oldFmtVer, resetOnError = true) {
248
262
  return __async(this, null, function* () {
249
263
  if (!this.migrations)
250
264
  return oldData;
@@ -259,6 +273,8 @@ var UserUtils = (function (exports) {
259
273
  newData = migRes instanceof Promise ? yield migRes : migRes;
260
274
  lastFmtVer = oldFmtVer = ver;
261
275
  } catch (err) {
276
+ if (!resetOnError)
277
+ throw new Error(`Error while running migration function for format version '${fmtVer}'`);
262
278
  console.error(`Error while running migration function for format version '${fmtVer}' - resetting to the default value.`, err);
263
279
  yield this.saveDefaultData();
264
280
  return this.getData();
@@ -266,18 +282,19 @@ var UserUtils = (function (exports) {
266
282
  }
267
283
  }
268
284
  yield Promise.all([
269
- GM.setValue(`_uucfg-${this.id}`, yield this.serializeData(newData)),
270
- GM.setValue(`_uucfgver-${this.id}`, lastFmtVer),
271
- GM.setValue(`_uucfgenc-${this.id}`, Boolean(this.encodeData && this.decodeData))
285
+ this.setValue(`_uucfg-${this.id}`, yield this.serializeData(newData)),
286
+ this.setValue(`_uucfgver-${this.id}`, lastFmtVer),
287
+ this.setValue(`_uucfgenc-${this.id}`, this.encodingEnabled())
272
288
  ]);
273
- return newData;
289
+ return this.cachedData = __spreadValues({}, newData);
274
290
  });
275
291
  }
292
+ //#region serialization
276
293
  /** Serializes the data using the optional this.encodeData() and returns it as a string */
277
294
  serializeData(data, useEncoding = true) {
278
295
  return __async(this, null, function* () {
279
296
  const stringData = JSON.stringify(data);
280
- if (!this.encodeData || !this.decodeData || !useEncoding)
297
+ if (!this.encodingEnabled() || !useEncoding)
281
298
  return stringData;
282
299
  const encRes = this.encodeData(stringData);
283
300
  if (encRes instanceof Promise)
@@ -288,16 +305,131 @@ var UserUtils = (function (exports) {
288
305
  /** Deserializes the data using the optional this.decodeData() and returns it as a JSON object */
289
306
  deserializeData(data, useEncoding = true) {
290
307
  return __async(this, null, function* () {
291
- let decRes = this.decodeData && this.encodeData && useEncoding ? this.decodeData(data) : void 0;
308
+ let decRes = this.encodingEnabled() && useEncoding ? this.decodeData(data) : void 0;
292
309
  if (decRes instanceof Promise)
293
310
  decRes = yield decRes;
294
311
  return JSON.parse(decRes != null ? decRes : data);
295
312
  });
296
313
  }
297
- /** Copies a JSON-compatible object and loses its internal references */
314
+ //#region misc
315
+ /** Copies a JSON-compatible object and loses all its internal references in the process */
298
316
  deepCopy(obj) {
299
317
  return JSON.parse(JSON.stringify(obj));
300
318
  }
319
+ //#region storage
320
+ /** Gets a value from persistent storage - can be overwritten in a subclass if you want to use something other than GM storage */
321
+ getValue(name, defaultValue) {
322
+ return __async(this, null, function* () {
323
+ var _a, _b;
324
+ switch (this.storageMethod) {
325
+ case "localStorage":
326
+ return (_a = localStorage.getItem(name)) != null ? _a : defaultValue;
327
+ case "sessionStorage":
328
+ return (_b = sessionStorage.getItem(name)) != null ? _b : defaultValue;
329
+ default:
330
+ return GM.getValue(name, defaultValue);
331
+ }
332
+ });
333
+ }
334
+ /**
335
+ * Sets a value in persistent storage - can be overwritten in a subclass if you want to use something other than GM storage.
336
+ * The default storage engines will stringify all passed values like numbers or booleans, so be aware of that.
337
+ */
338
+ setValue(name, value) {
339
+ return __async(this, null, function* () {
340
+ switch (this.storageMethod) {
341
+ case "localStorage":
342
+ return localStorage.setItem(name, String(value));
343
+ case "sessionStorage":
344
+ return sessionStorage.setItem(name, String(value));
345
+ default:
346
+ return GM.setValue(name, String(value));
347
+ }
348
+ });
349
+ }
350
+ /** Deletes a value from persistent storage - can be overwritten in a subclass if you want to use something other than GM storage */
351
+ deleteValue(name) {
352
+ return __async(this, null, function* () {
353
+ switch (this.storageMethod) {
354
+ case "localStorage":
355
+ return localStorage.removeItem(name);
356
+ case "sessionStorage":
357
+ return sessionStorage.removeItem(name);
358
+ default:
359
+ return GM.deleteValue(name);
360
+ }
361
+ });
362
+ }
363
+ };
364
+
365
+ // lib/DataStoreSerializer.ts
366
+ var DataStoreSerializer = class {
367
+ constructor(stores, options = {}) {
368
+ __publicField(this, "stores");
369
+ __publicField(this, "options");
370
+ if (!getUnsafeWindow().crypto || !getUnsafeWindow().crypto.subtle)
371
+ throw new Error("DataStoreSerializer has to run in a secure context (HTTPS)!");
372
+ this.stores = stores;
373
+ this.options = __spreadValues({
374
+ addChecksum: true,
375
+ ensureIntegrity: true
376
+ }, options);
377
+ }
378
+ /** Calculates the checksum of a string */
379
+ calcChecksum(input) {
380
+ return __async(this, null, function* () {
381
+ return computeHash(input, "SHA-256");
382
+ });
383
+ }
384
+ /** Serializes a DataStore instance */
385
+ serializeStore(storeInst) {
386
+ return __async(this, null, function* () {
387
+ const data = storeInst.encodingEnabled() ? yield storeInst.encodeData(JSON.stringify(storeInst.getData())) : JSON.stringify(storeInst.getData());
388
+ const checksum = this.options.addChecksum ? yield this.calcChecksum(data) : void 0;
389
+ return {
390
+ id: storeInst.id,
391
+ data,
392
+ formatVersion: storeInst.formatVersion,
393
+ encoded: storeInst.encodingEnabled(),
394
+ checksum
395
+ };
396
+ });
397
+ }
398
+ /** Serializes the data stores into a string */
399
+ serialize() {
400
+ return __async(this, null, function* () {
401
+ const serData = [];
402
+ for (const store of this.stores)
403
+ serData.push(yield this.serializeStore(store));
404
+ return JSON.stringify(serData);
405
+ });
406
+ }
407
+ /**
408
+ * Deserializes the data exported via {@linkcode serialize()} and imports it into the DataStore instances.
409
+ * Also triggers the migration process if the data format has changed.
410
+ */
411
+ deserialize(serializedData) {
412
+ return __async(this, null, function* () {
413
+ const deserStores = JSON.parse(serializedData);
414
+ for (const storeData of deserStores) {
415
+ const storeInst = this.stores.find((s) => s.id === storeData.id);
416
+ if (!storeInst)
417
+ throw new Error(`DataStore instance with ID "${storeData.id}" not found! Make sure to provide it in the DataStoreSerializer constructor.`);
418
+ if (this.options.ensureIntegrity && typeof storeData.checksum === "string") {
419
+ const checksum = yield this.calcChecksum(storeData.data);
420
+ if (checksum !== storeData.checksum)
421
+ throw new Error(`Checksum mismatch for DataStore with ID "${storeData.id}"!
422
+ Expected: ${storeData.checksum}
423
+ Has: ${checksum}`);
424
+ }
425
+ const decodedData = storeData.encoded && storeInst.encodingEnabled() ? yield storeInst.decodeData(storeData.data) : storeData.data;
426
+ if (storeData.formatVersion && !isNaN(Number(storeData.formatVersion)) && Number(storeData.formatVersion) < storeInst.formatVersion)
427
+ yield storeInst.runMigrations(JSON.parse(decodedData), Number(storeData.formatVersion), false);
428
+ else
429
+ yield storeInst.setData(JSON.parse(decodedData));
430
+ }
431
+ });
432
+ }
301
433
  };
302
434
 
303
435
  // lib/dom.ts
@@ -308,11 +440,6 @@ var UserUtils = (function (exports) {
308
440
  return window;
309
441
  }
310
442
  }
311
- function insertAfter(beforeElement, afterElement) {
312
- var _a;
313
- (_a = beforeElement.parentNode) == null ? void 0 : _a.insertBefore(afterElement, beforeElement.nextSibling);
314
- return afterElement;
315
- }
316
443
  function addParent(element, newParent) {
317
444
  const oldParent = element.parentNode;
318
445
  if (!oldParent)
@@ -336,18 +463,22 @@ var UserUtils = (function (exports) {
336
463
  }));
337
464
  return Promise.allSettled(promises);
338
465
  }
339
- function openInNewTab(href) {
340
- const openElem = document.createElement("a");
341
- Object.assign(openElem, {
342
- className: "userutils-open-in-new-tab",
343
- target: "_blank",
344
- rel: "noopener noreferrer",
345
- href
346
- });
347
- openElem.style.display = "none";
348
- document.body.appendChild(openElem);
349
- openElem.click();
350
- setTimeout(openElem.remove, 50);
466
+ function openInNewTab(href, background) {
467
+ try {
468
+ GM.openInTab(href, background);
469
+ } catch (e) {
470
+ const openElem = document.createElement("a");
471
+ Object.assign(openElem, {
472
+ className: "userutils-open-in-new-tab",
473
+ target: "_blank",
474
+ rel: "noopener noreferrer",
475
+ href
476
+ });
477
+ openElem.style.display = "none";
478
+ document.body.appendChild(openElem);
479
+ openElem.click();
480
+ setTimeout(openElem.remove, 50);
481
+ }
351
482
  }
352
483
  function interceptEvent(eventObject, eventName, predicate = () => true) {
353
484
  Error.stackTraceLimit = Math.max(Error.stackTraceLimit, 100);
@@ -455,9 +586,14 @@ var UserUtils = (function (exports) {
455
586
  id = setTimeout(() => controller.abort(), timeout);
456
587
  signalOpts = { signal: controller.signal };
457
588
  }
458
- const res = yield fetch(input, __spreadValues(__spreadValues({}, options), signalOpts));
459
- clearTimeout(id);
460
- return res;
589
+ try {
590
+ const res = yield fetch(input, __spreadValues(__spreadValues({}, options), signalOpts));
591
+ id && clearTimeout(id);
592
+ return res;
593
+ } catch (err) {
594
+ id && clearTimeout(id);
595
+ throw err;
596
+ }
461
597
  });
462
598
  }
463
599
  function insertValues(input, ...values) {
@@ -497,8 +633,38 @@ var UserUtils = (function (exports) {
497
633
  function str2ab(str) {
498
634
  return Uint8Array.from(getUnsafeWindow().atob(str), (c) => c.charCodeAt(0));
499
635
  }
636
+ function computeHash(input, algorithm = "SHA-256") {
637
+ return __async(this, null, function* () {
638
+ let data;
639
+ if (typeof input === "string") {
640
+ const encoder = new TextEncoder();
641
+ data = encoder.encode(input);
642
+ } else
643
+ data = input;
644
+ const hashBuffer = yield crypto.subtle.digest(algorithm, data);
645
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
646
+ const hashHex = hashArray.map((byte) => byte.toString(16).padStart(2, "0")).join("");
647
+ return hashHex;
648
+ });
649
+ }
650
+ function randomId(length = 16, radix = 16, enhancedEntropy = false) {
651
+ if (enhancedEntropy) {
652
+ const arr = new Uint8Array(length);
653
+ crypto.getRandomValues(arr);
654
+ return Array.from(
655
+ arr,
656
+ (v) => mapRange(v, 0, 255, 0, radix).toString(radix).substring(0, 1)
657
+ ).join("");
658
+ }
659
+ return Array.from(
660
+ { length },
661
+ () => Math.floor(Math.random() * radix).toString(radix)
662
+ ).join("");
663
+ }
500
664
 
501
665
  // lib/SelectorObserver.ts
666
+ var domLoaded = false;
667
+ document.addEventListener("DOMContentLoaded", () => domLoaded = true);
502
668
  var SelectorObserver = class {
503
669
  constructor(baseElement, options = {}) {
504
670
  __publicField(this, "enabled", false);
@@ -509,7 +675,6 @@ var UserUtils = (function (exports) {
509
675
  __publicField(this, "listenerMap");
510
676
  this.baseElement = baseElement;
511
677
  this.listenerMap = /* @__PURE__ */ new Map();
512
- this.observer = new MutationObserver(() => this.checkAllSelectors());
513
678
  const _a = options, {
514
679
  defaultDebounce,
515
680
  defaultDebounceEdge,
@@ -531,11 +696,21 @@ var UserUtils = (function (exports) {
531
696
  disableOnNoListeners: disableOnNoListeners != null ? disableOnNoListeners : false,
532
697
  enableOnAddListener: enableOnAddListener != null ? enableOnAddListener : true
533
698
  };
699
+ if (typeof this.customOptions.checkInterval !== "number") {
700
+ this.observer = new MutationObserver(() => this.checkAllSelectors());
701
+ } else {
702
+ this.checkAllSelectors();
703
+ setInterval(() => this.checkAllSelectors(), this.customOptions.checkInterval);
704
+ }
534
705
  }
706
+ /** Call to check all selectors in the {@linkcode listenerMap} using {@linkcode checkSelector()} */
535
707
  checkAllSelectors() {
708
+ if (!this.enabled || !domLoaded)
709
+ return;
536
710
  for (const [selector, listeners] of this.listenerMap.entries())
537
711
  this.checkSelector(selector, listeners);
538
712
  }
713
+ /** Checks if the element(s) with the given {@linkcode selector} exist in the DOM and calls the respective {@linkcode listeners} accordingly */
539
714
  checkSelector(selector, listeners) {
540
715
  var _a;
541
716
  if (!this.enabled)
@@ -567,9 +742,6 @@ var UserUtils = (function (exports) {
567
742
  this.disable();
568
743
  }
569
744
  }
570
- debounce(func, time, edge = "falling") {
571
- return debounce(func, time, edge);
572
- }
573
745
  /**
574
746
  * Starts observing the children of the base element for changes to the given {@linkcode selector} according to the set {@linkcode options}
575
747
  * @param selector The selector to observe
@@ -578,11 +750,16 @@ var UserUtils = (function (exports) {
578
750
  * @param [options.all] Whether to use `querySelectorAll()` instead - default is false
579
751
  * @param [options.continuous] Whether to call the listener continuously instead of just once - default is false
580
752
  * @param [options.debounce] Whether to debounce the listener to reduce calls to `querySelector` or `querySelectorAll` - set undefined or <=0 to disable (default)
753
+ * @returns Returns a function that can be called to remove this listener more easily
581
754
  */
582
755
  addListener(selector, options) {
583
- options = __spreadValues({ all: false, continuous: false, debounce: 0 }, options);
756
+ options = __spreadValues({
757
+ all: false,
758
+ continuous: false,
759
+ debounce: 0
760
+ }, options);
584
761
  if (options.debounce && options.debounce > 0 || this.customOptions.defaultDebounce && this.customOptions.defaultDebounce > 0) {
585
- options.listener = this.debounce(
762
+ options.listener = debounce(
586
763
  options.listener,
587
764
  options.debounce || this.customOptions.defaultDebounce,
588
765
  options.debounceEdge || this.customOptions.defaultDebounceEdge
@@ -595,13 +772,15 @@ var UserUtils = (function (exports) {
595
772
  if (this.enabled === false && this.customOptions.enableOnAddListener)
596
773
  this.enable();
597
774
  this.checkSelector(selector, [options]);
775
+ return () => this.removeListener(selector, options);
598
776
  }
599
777
  /** Disables the observation of the child elements */
600
778
  disable() {
779
+ var _a;
601
780
  if (!this.enabled)
602
781
  return;
603
782
  this.enabled = false;
604
- this.observer.disconnect();
783
+ (_a = this.observer) == null ? void 0 : _a.disconnect();
605
784
  }
606
785
  /**
607
786
  * Enables or reenables the observation of the child elements.
@@ -609,11 +788,12 @@ var UserUtils = (function (exports) {
609
788
  * @returns Returns true when the observation was enabled, false otherwise (e.g. when the base element wasn't found)
610
789
  */
611
790
  enable(immediatelyCheckSelectors = true) {
791
+ var _a;
612
792
  const baseElement = typeof this.baseElement === "string" ? document.querySelector(this.baseElement) : this.baseElement;
613
793
  if (this.enabled || !baseElement)
614
794
  return false;
615
795
  this.enabled = true;
616
- this.observer.observe(baseElement, this.observerOptions);
796
+ (_a = this.observer) == null ? void 0 : _a.observe(baseElement, this.observerOptions);
617
797
  if (immediatelyCheckSelectors)
618
798
  this.checkAllSelectors();
619
799
  return true;
@@ -684,18 +864,19 @@ var UserUtils = (function (exports) {
684
864
  };
685
865
 
686
866
  exports.DataStore = DataStore;
867
+ exports.DataStoreSerializer = DataStoreSerializer;
687
868
  exports.SelectorObserver = SelectorObserver;
688
869
  exports.addGlobalStyle = addGlobalStyle;
689
870
  exports.addParent = addParent;
690
871
  exports.autoPlural = autoPlural;
691
872
  exports.clamp = clamp;
692
873
  exports.compress = compress;
874
+ exports.computeHash = computeHash;
693
875
  exports.debounce = debounce;
694
876
  exports.decompress = decompress;
695
877
  exports.fetchAdvanced = fetchAdvanced;
696
878
  exports.getSiblingsFrame = getSiblingsFrame;
697
879
  exports.getUnsafeWindow = getUnsafeWindow;
698
- exports.insertAfter = insertAfter;
699
880
  exports.insertValues = insertValues;
700
881
  exports.interceptEvent = interceptEvent;
701
882
  exports.interceptWindowEvent = interceptWindowEvent;