@sv443-network/userutils 4.0.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @sv443-network/userutils
2
2
 
3
+ ## 4.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 47639f0: Added SelectorObserver options `disableOnNoListeners` and `enableOnAddListener`
8
+ - 4a58caa: `addGlobalStyle` now returns the created style element
9
+ - 5038967: `fetchAdvanced` is now a drop-in replacement and timeout can now optionally be disabled
10
+
11
+ ### Patch Changes
12
+
13
+ - 17a6ad5: `randomizeArray` now returns a copy if an empty array is passed as well
14
+
15
+ ## 4.1.0
16
+
17
+ ### Minor Changes
18
+
19
+ - 885323d: Added function `observeElementProp` to allow observing element property changes
20
+
3
21
  ## 4.0.0
4
22
 
5
23
  ### Major Changes
package/README.md CHANGED
@@ -33,6 +33,7 @@ View the documentation of previous major releases: [3.0.0](https://github.com/Sv
33
33
  - [interceptEvent()](#interceptevent) - conditionally intercepts events registered by `addEventListener()` on any given EventTarget object
34
34
  - [interceptWindowEvent()](#interceptwindowevent) - conditionally intercepts events registered by `addEventListener()` on the window object
35
35
  - [isScrollable()](#isscrollable) - check if an element has a horizontal or vertical scroll bar
36
+ - [observeElementProp()](#observeelementprop) - observe changes to an element's property that can't be observed with MutationObserver
36
37
  - [**Math:**](#math)
37
38
  - [clamp()](#clamp) - constrain a number between a min and max value
38
39
  - [mapRange()](#maprange) - map a number from one range to the same spot in another range
@@ -83,7 +84,7 @@ View the documentation of previous major releases: [3.0.0](https://github.com/Sv
83
84
 
84
85
  <br>
85
86
 
86
- - If you are not using a bundler, you can include the latest release from GreasyFork by adding this directive to the userscript header:
87
+ - If you are not using a bundler, you can include the latest release by adding one of these directives to the userscript header, depending on your preferred CDN:
87
88
  ```
88
89
  // @require https://greasyfork.org/scripts/472956-userutils/code/UserUtils.js
89
90
  ```
@@ -99,7 +100,7 @@ View the documentation of previous major releases: [3.0.0](https://github.com/Sv
99
100
  // or using object destructuring:
100
101
 
101
102
  const { clamp } = UserUtils;
102
- console.log(clamp(1, 5, 10); // 5
103
+ console.log(clamp(1, 5, 10)); // 5
103
104
  ```
104
105
 
105
106
  <br><br>
@@ -147,9 +148,12 @@ If a selector string is passed instead, it will be used to find the element.
147
148
  If you want to observe the entire document, you can pass `document.body`
148
149
 
149
150
  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"] }`
151
+ The MutationObserver options present by default 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.
152
+ For example, if you want to trigger the listeners when certain attributes change, pass `{ attributeFilter: ["class", "data-my-attribute"] }`
153
+
152
154
  Additionally, there are the following extra options:
155
+ - `disableOnNoListeners` - whether to disable the SelectorObserver when there are no listeners left (defaults to false)
156
+ - `enableOnAddListener` - whether to enable the SelectorObserver when a new listener is added (defaults to true)
153
157
  - `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)
154
158
 
155
159
  ⚠️ 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.
@@ -505,10 +509,11 @@ addParent(element, newParent);
505
509
  ### addGlobalStyle()
506
510
  Usage:
507
511
  ```ts
508
- addGlobalStyle(css: string): void
512
+ addGlobalStyle(css: string): HTMLStyleElement
509
513
  ```
510
514
 
511
515
  Adds a global style to the page in form of a `<style>` element that's inserted into the `<head>`.
516
+ Returns the style element that was just created.
512
517
  ⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
513
518
 
514
519
  <details><summary><b>Example - click to view</b></summary>
@@ -675,6 +680,59 @@ console.log("Element has a vertical scroll bar:", vertical);
675
680
 
676
681
  </details>
677
682
 
683
+ <br>
684
+
685
+ ### observeElementProp()
686
+ Usage:
687
+ ```ts
688
+ observeElementProp(
689
+ element: Element,
690
+ property: string,
691
+ callback: (oldValue: any, newValue: any) => void
692
+ ): void
693
+ ```
694
+
695
+ This function observes changes to the given property of a given element.
696
+ While regular HTML attributes can be observed using a MutationObserver, this is not always possible for properties that are assigned on the JS object.
697
+ This function shims the setter of the provided property and calls the callback function whenever it is changed through any means.
698
+
699
+ When using TypeScript, the types for `element`, `property` and the arguments for `callback` will be automatically inferred.
700
+
701
+ <details><summary><b>Example - click to view</b></summary>
702
+
703
+ ```ts
704
+ import { observeElementProp } from "@sv443-network/userutils";
705
+
706
+ const myInput = document.querySelector("input#my-input");
707
+
708
+ let value = 0;
709
+
710
+ setInterval(() => {
711
+ value += 1;
712
+ myInput.value = String(value);
713
+ }, 1000);
714
+
715
+
716
+ const observer = new MutationObserver((mutations) => {
717
+ // will never be called:
718
+ console.log("MutationObserver mutation:", mutations);
719
+ });
720
+
721
+ // one would think this should work, but "value" is a JS object *property*, not a DOM *attribute*
722
+ observer.observe(myInput, {
723
+ attributes: true,
724
+ attributeFilter: ["value"],
725
+ });
726
+
727
+
728
+ observeElementProp(myInput, "value", (oldValue, newValue) => {
729
+ // will be called every time the value changes:
730
+ console.log("Value changed from", oldValue, "to", newValue);
731
+ });
732
+ ```
733
+
734
+ </details>
735
+
678
736
  <br><br>
679
737
 
680
738
  <!-- #SECTION Math -->
@@ -769,6 +827,9 @@ randomId(length?: number, radix?: number): string
769
827
  ```
770
828
 
771
829
  Generates a cryptographically strong random ID of a given length and [radix (base).](https://en.wikipedia.org/wiki/Radix)
830
+ Uses the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues) for generating the random numbers.
831
+ ⚠️ This is not intended for generating encryption keys, only for generating IDs with a decent amount of entropy!
832
+
772
833
  The default length is 16 and the default radix is 16 (hexadecimal).
773
834
  You may change the radix to get digits from different numerical systems.
774
835
  Use 2 for binary, 8 for octal, 10 for decimal, 16 for hexadecimal and 36 for alphanumeric.
@@ -1004,14 +1065,15 @@ window.addEventListener("resize", debounce((event) => {
1004
1065
  ### fetchAdvanced()
1005
1066
  Usage:
1006
1067
  ```ts
1007
- fetchAdvanced(url: string, options?: {
1068
+ fetchAdvanced(input: string | Request | URL, options?: {
1008
1069
  timeout?: number,
1009
1070
  // any other options from fetch() except for signal
1010
1071
  }): Promise<Response>
1011
1072
  ```
1012
1073
 
1013
- A wrapper around the native `fetch()` function that adds options like a timeout property.
1014
- The timeout will default to 10 seconds if left undefined.
1074
+ A drop-in replacement for the native `fetch()` function that adds options like a timeout property.
1075
+ The timeout will default to 10 seconds if left undefined. Set it to a negative number to disable the timeout.
1076
+ Note that the `signal` option will be overwritten if passed.
1015
1077
 
1016
1078
  <details><summary><b>Example - click to view</b></summary>
1017
1079
 
@@ -1022,10 +1084,12 @@ fetchAdvanced("https://jokeapi.dev/joke/Any?safe-mode", {
1022
1084
  timeout: 5000,
1023
1085
  // also accepts any other fetch options like headers:
1024
1086
  headers: {
1025
- "Accept": "application/json",
1087
+ "Accept": "text/plain",
1026
1088
  },
1027
1089
  }).then(async (response) => {
1028
- console.log("Data:", await response.json());
1090
+ console.log("Fetch data:", await response.text());
1091
+ }).catch((err) => {
1092
+ console.error("Fetch error:", err);
1029
1093
  });
1030
1094
  ```
1031
1095
 
@@ -1215,7 +1279,7 @@ randomizeArray(array: Array): Array
1215
1279
  ```
1216
1280
 
1217
1281
  Returns a copy of an array with its items in a random order.
1218
- If the array is empty, the originally passed empty array will be returned without copying.
1282
+ If the array is empty, a new, empty array will be returned.
1219
1283
 
1220
1284
  <details><summary><b>Example - click to view</b></summary>
1221
1285
 
@@ -9,7 +9,7 @@
9
9
  // ==UserLibrary==
10
10
  // @name UserUtils
11
11
  // @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
12
- // @version 4.0.0
12
+ // @version 4.2.0
13
13
  // @license MIT
14
14
  // @copyright Sv443 (https://github.com/Sv443)
15
15
 
@@ -22,8 +22,6 @@
22
22
 
23
23
  var UserUtils = (function (exports) {
24
24
  var __defProp = Object.defineProperty;
25
- var __defProps = Object.defineProperties;
26
- var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
27
25
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
28
26
  var __hasOwnProp = Object.prototype.hasOwnProperty;
29
27
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
@@ -39,7 +37,18 @@ var UserUtils = (function (exports) {
39
37
  }
40
38
  return a;
41
39
  };
42
- var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
40
+ var __objRest = (source, exclude) => {
41
+ var target = {};
42
+ for (var prop in source)
43
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
44
+ target[prop] = source[prop];
45
+ if (source != null && __getOwnPropSymbols)
46
+ for (var prop of __getOwnPropSymbols(source)) {
47
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
48
+ target[prop] = source[prop];
49
+ }
50
+ return target;
51
+ };
43
52
  var __publicField = (obj, key, value) => {
44
53
  __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
45
54
  return value;
@@ -120,7 +129,7 @@ var UserUtils = (function (exports) {
120
129
  function randomizeArray(array) {
121
130
  const retArray = [...array];
122
131
  if (array.length === 0)
123
- return array;
132
+ return retArray;
124
133
  for (let i = retArray.length - 1; i > 0; i--) {
125
134
  const j = Math.floor(randRange(0, 1e4) / 1e4 * (i + 1));
126
135
  [retArray[i], retArray[j]] = [retArray[j], retArray[i]];
@@ -282,6 +291,7 @@ var UserUtils = (function (exports) {
282
291
  const styleElem = document.createElement("style");
283
292
  styleElem.innerHTML = style;
284
293
  document.head.appendChild(styleElem);
294
+ return styleElem;
285
295
  }
286
296
  function preloadImages(srcUrls, rejects = false) {
287
297
  const promises = srcUrls.map((src) => new Promise((res, rej) => {
@@ -333,6 +343,28 @@ var UserUtils = (function (exports) {
333
343
  horizontal: (overflowX === "scroll" || overflowX === "auto") && element.scrollWidth > element.clientWidth
334
344
  };
335
345
  }
346
+ function observeElementProp(element, property, callback) {
347
+ const elementPrototype = Object.getPrototypeOf(element);
348
+ if (elementPrototype.hasOwnProperty(property)) {
349
+ const descriptor = Object.getOwnPropertyDescriptor(elementPrototype, property);
350
+ Object.defineProperty(element, property, {
351
+ get: function() {
352
+ var _a;
353
+ return (_a = descriptor == null ? void 0 : descriptor.get) == null ? void 0 : _a.apply(this, arguments);
354
+ },
355
+ set: function() {
356
+ var _a;
357
+ const oldValue = this[property];
358
+ (_a = descriptor == null ? void 0 : descriptor.set) == null ? void 0 : _a.apply(this, arguments);
359
+ const newValue = this[property];
360
+ if (typeof callback === "function") {
361
+ callback.bind(this, oldValue, newValue);
362
+ }
363
+ return newValue;
364
+ }
365
+ });
366
+ }
367
+ }
336
368
 
337
369
  // lib/misc.ts
338
370
  function autoPlural(word, num) {
@@ -353,13 +385,15 @@ var UserUtils = (function (exports) {
353
385
  };
354
386
  }
355
387
  function fetchAdvanced(_0) {
356
- return __async(this, arguments, function* (url, options = {}) {
388
+ return __async(this, arguments, function* (input, options = {}) {
357
389
  const { timeout = 1e4 } = options;
358
- const controller = new AbortController();
359
- const id = setTimeout(() => controller.abort(), timeout);
360
- const res = yield fetch(url, __spreadProps(__spreadValues({}, options), {
361
- signal: controller.signal
362
- }));
390
+ let signalOpts = {}, id = void 0;
391
+ if (timeout >= 0) {
392
+ const controller = new AbortController();
393
+ id = setTimeout(() => controller.abort(), timeout);
394
+ signalOpts = { signal: controller.signal };
395
+ }
396
+ const res = yield fetch(input, __spreadValues(__spreadValues({}, options), signalOpts));
363
397
  clearTimeout(id);
364
398
  return res;
365
399
  });
@@ -409,14 +443,29 @@ var UserUtils = (function (exports) {
409
443
  __publicField(this, "baseElement");
410
444
  __publicField(this, "observer");
411
445
  __publicField(this, "observerOptions");
446
+ __publicField(this, "customOptions");
412
447
  __publicField(this, "listenerMap");
413
448
  this.baseElement = baseElement;
414
449
  this.listenerMap = /* @__PURE__ */ new Map();
415
450
  this.observer = new MutationObserver(() => this.checkAllSelectors());
451
+ const _a = options, {
452
+ defaultDebounce,
453
+ disableOnNoListeners,
454
+ enableOnAddListener
455
+ } = _a, observerOptions = __objRest(_a, [
456
+ "defaultDebounce",
457
+ "disableOnNoListeners",
458
+ "enableOnAddListener"
459
+ ]);
416
460
  this.observerOptions = __spreadValues({
417
461
  childList: true,
418
462
  subtree: true
419
- }, options);
463
+ }, observerOptions);
464
+ this.customOptions = {
465
+ defaultDebounce: defaultDebounce != null ? defaultDebounce : 0,
466
+ disableOnNoListeners: disableOnNoListeners != null ? disableOnNoListeners : false,
467
+ enableOnAddListener: enableOnAddListener != null ? enableOnAddListener : true
468
+ };
420
469
  }
421
470
  checkAllSelectors() {
422
471
  for (const [selector, listeners] of this.listenerMap.entries())
@@ -449,6 +498,8 @@ var UserUtils = (function (exports) {
449
498
  }
450
499
  if (((_a = this.listenerMap.get(selector)) == null ? void 0 : _a.length) === 0)
451
500
  this.listenerMap.delete(selector);
501
+ if (this.listenerMap.size === 0 && this.customOptions.disableOnNoListeners)
502
+ this.disable();
452
503
  }
453
504
  }
454
505
  debounce(func, time) {
@@ -469,16 +520,18 @@ var UserUtils = (function (exports) {
469
520
  */
470
521
  addListener(selector, options) {
471
522
  options = __spreadValues({ all: false, continuous: false, debounce: 0 }, options);
472
- if (options.debounce && options.debounce > 0 || this.observerOptions.defaultDebounce && this.observerOptions.defaultDebounce > 0) {
523
+ if (options.debounce && options.debounce > 0 || this.customOptions.defaultDebounce && this.customOptions.defaultDebounce > 0) {
473
524
  options.listener = this.debounce(
474
525
  options.listener,
475
- options.debounce || this.observerOptions.defaultDebounce
526
+ options.debounce || this.customOptions.defaultDebounce
476
527
  );
477
528
  }
478
529
  if (this.listenerMap.has(selector))
479
530
  this.listenerMap.get(selector).push(options);
480
531
  else
481
532
  this.listenerMap.set(selector, [options]);
533
+ if (this.enabled === false && this.customOptions.enableOnAddListener)
534
+ this.enable();
482
535
  this.checkSelector(selector, [options]);
483
536
  }
484
537
  /** Disables the observation of the child elements */
@@ -585,6 +638,7 @@ var UserUtils = (function (exports) {
585
638
  exports.interceptWindowEvent = interceptWindowEvent;
586
639
  exports.isScrollable = isScrollable;
587
640
  exports.mapRange = mapRange;
641
+ exports.observeElementProp = observeElementProp;
588
642
  exports.openInNewTab = openInNewTab;
589
643
  exports.pauseFor = pauseFor;
590
644
  exports.preloadImages = preloadImages;
package/dist/index.js CHANGED
@@ -1,8 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  var __defProp = Object.defineProperty;
4
- var __defProps = Object.defineProperties;
5
- var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
6
4
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
@@ -18,7 +16,18 @@ var __spreadValues = (a, b) => {
18
16
  }
19
17
  return a;
20
18
  };
21
- var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
19
+ var __objRest = (source, exclude) => {
20
+ var target = {};
21
+ for (var prop in source)
22
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
23
+ target[prop] = source[prop];
24
+ if (source != null && __getOwnPropSymbols)
25
+ for (var prop of __getOwnPropSymbols(source)) {
26
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
27
+ target[prop] = source[prop];
28
+ }
29
+ return target;
30
+ };
22
31
  var __publicField = (obj, key, value) => {
23
32
  __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
24
33
  return value;
@@ -99,7 +108,7 @@ function takeRandomItem(arr) {
99
108
  function randomizeArray(array) {
100
109
  const retArray = [...array];
101
110
  if (array.length === 0)
102
- return array;
111
+ return retArray;
103
112
  for (let i = retArray.length - 1; i > 0; i--) {
104
113
  const j = Math.floor(randRange(0, 1e4) / 1e4 * (i + 1));
105
114
  [retArray[i], retArray[j]] = [retArray[j], retArray[i]];
@@ -261,6 +270,7 @@ function addGlobalStyle(style) {
261
270
  const styleElem = document.createElement("style");
262
271
  styleElem.innerHTML = style;
263
272
  document.head.appendChild(styleElem);
273
+ return styleElem;
264
274
  }
265
275
  function preloadImages(srcUrls, rejects = false) {
266
276
  const promises = srcUrls.map((src) => new Promise((res, rej) => {
@@ -312,6 +322,28 @@ function isScrollable(element) {
312
322
  horizontal: (overflowX === "scroll" || overflowX === "auto") && element.scrollWidth > element.clientWidth
313
323
  };
314
324
  }
325
+ function observeElementProp(element, property, callback) {
326
+ const elementPrototype = Object.getPrototypeOf(element);
327
+ if (elementPrototype.hasOwnProperty(property)) {
328
+ const descriptor = Object.getOwnPropertyDescriptor(elementPrototype, property);
329
+ Object.defineProperty(element, property, {
330
+ get: function() {
331
+ var _a;
332
+ return (_a = descriptor == null ? void 0 : descriptor.get) == null ? void 0 : _a.apply(this, arguments);
333
+ },
334
+ set: function() {
335
+ var _a;
336
+ const oldValue = this[property];
337
+ (_a = descriptor == null ? void 0 : descriptor.set) == null ? void 0 : _a.apply(this, arguments);
338
+ const newValue = this[property];
339
+ if (typeof callback === "function") {
340
+ callback.bind(this, oldValue, newValue);
341
+ }
342
+ return newValue;
343
+ }
344
+ });
345
+ }
346
+ }
315
347
 
316
348
  // lib/misc.ts
317
349
  function autoPlural(word, num) {
@@ -332,13 +364,15 @@ function debounce(func, timeout = 300) {
332
364
  };
333
365
  }
334
366
  function fetchAdvanced(_0) {
335
- return __async(this, arguments, function* (url, options = {}) {
367
+ return __async(this, arguments, function* (input, options = {}) {
336
368
  const { timeout = 1e4 } = options;
337
- const controller = new AbortController();
338
- const id = setTimeout(() => controller.abort(), timeout);
339
- const res = yield fetch(url, __spreadProps(__spreadValues({}, options), {
340
- signal: controller.signal
341
- }));
369
+ let signalOpts = {}, id = void 0;
370
+ if (timeout >= 0) {
371
+ const controller = new AbortController();
372
+ id = setTimeout(() => controller.abort(), timeout);
373
+ signalOpts = { signal: controller.signal };
374
+ }
375
+ const res = yield fetch(input, __spreadValues(__spreadValues({}, options), signalOpts));
342
376
  clearTimeout(id);
343
377
  return res;
344
378
  });
@@ -388,14 +422,29 @@ var SelectorObserver = class {
388
422
  __publicField(this, "baseElement");
389
423
  __publicField(this, "observer");
390
424
  __publicField(this, "observerOptions");
425
+ __publicField(this, "customOptions");
391
426
  __publicField(this, "listenerMap");
392
427
  this.baseElement = baseElement;
393
428
  this.listenerMap = /* @__PURE__ */ new Map();
394
429
  this.observer = new MutationObserver(() => this.checkAllSelectors());
430
+ const _a = options, {
431
+ defaultDebounce,
432
+ disableOnNoListeners,
433
+ enableOnAddListener
434
+ } = _a, observerOptions = __objRest(_a, [
435
+ "defaultDebounce",
436
+ "disableOnNoListeners",
437
+ "enableOnAddListener"
438
+ ]);
395
439
  this.observerOptions = __spreadValues({
396
440
  childList: true,
397
441
  subtree: true
398
- }, options);
442
+ }, observerOptions);
443
+ this.customOptions = {
444
+ defaultDebounce: defaultDebounce != null ? defaultDebounce : 0,
445
+ disableOnNoListeners: disableOnNoListeners != null ? disableOnNoListeners : false,
446
+ enableOnAddListener: enableOnAddListener != null ? enableOnAddListener : true
447
+ };
399
448
  }
400
449
  checkAllSelectors() {
401
450
  for (const [selector, listeners] of this.listenerMap.entries())
@@ -428,6 +477,8 @@ var SelectorObserver = class {
428
477
  }
429
478
  if (((_a = this.listenerMap.get(selector)) == null ? void 0 : _a.length) === 0)
430
479
  this.listenerMap.delete(selector);
480
+ if (this.listenerMap.size === 0 && this.customOptions.disableOnNoListeners)
481
+ this.disable();
431
482
  }
432
483
  }
433
484
  debounce(func, time) {
@@ -448,16 +499,18 @@ var SelectorObserver = class {
448
499
  */
449
500
  addListener(selector, options) {
450
501
  options = __spreadValues({ all: false, continuous: false, debounce: 0 }, options);
451
- if (options.debounce && options.debounce > 0 || this.observerOptions.defaultDebounce && this.observerOptions.defaultDebounce > 0) {
502
+ if (options.debounce && options.debounce > 0 || this.customOptions.defaultDebounce && this.customOptions.defaultDebounce > 0) {
452
503
  options.listener = this.debounce(
453
504
  options.listener,
454
- options.debounce || this.observerOptions.defaultDebounce
505
+ options.debounce || this.customOptions.defaultDebounce
455
506
  );
456
507
  }
457
508
  if (this.listenerMap.has(selector))
458
509
  this.listenerMap.get(selector).push(options);
459
510
  else
460
511
  this.listenerMap.set(selector, [options]);
512
+ if (this.enabled === false && this.customOptions.enableOnAddListener)
513
+ this.enable();
461
514
  this.checkSelector(selector, [options]);
462
515
  }
463
516
  /** Disables the observation of the child elements */
@@ -564,6 +617,7 @@ exports.interceptEvent = interceptEvent;
564
617
  exports.interceptWindowEvent = interceptWindowEvent;
565
618
  exports.isScrollable = isScrollable;
566
619
  exports.mapRange = mapRange;
620
+ exports.observeElementProp = observeElementProp;
567
621
  exports.openInNewTab = openInNewTab;
568
622
  exports.pauseFor = pauseFor;
569
623
  exports.preloadImages = preloadImages;
package/dist/index.mjs CHANGED
@@ -1,6 +1,4 @@
1
1
  var __defProp = Object.defineProperty;
2
- var __defProps = Object.defineProperties;
3
- var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
2
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
3
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
4
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
@@ -16,7 +14,18 @@ var __spreadValues = (a, b) => {
16
14
  }
17
15
  return a;
18
16
  };
19
- var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
17
+ var __objRest = (source, exclude) => {
18
+ var target = {};
19
+ for (var prop in source)
20
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
21
+ target[prop] = source[prop];
22
+ if (source != null && __getOwnPropSymbols)
23
+ for (var prop of __getOwnPropSymbols(source)) {
24
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
25
+ target[prop] = source[prop];
26
+ }
27
+ return target;
28
+ };
20
29
  var __publicField = (obj, key, value) => {
21
30
  __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
22
31
  return value;
@@ -97,7 +106,7 @@ function takeRandomItem(arr) {
97
106
  function randomizeArray(array) {
98
107
  const retArray = [...array];
99
108
  if (array.length === 0)
100
- return array;
109
+ return retArray;
101
110
  for (let i = retArray.length - 1; i > 0; i--) {
102
111
  const j = Math.floor(randRange(0, 1e4) / 1e4 * (i + 1));
103
112
  [retArray[i], retArray[j]] = [retArray[j], retArray[i]];
@@ -259,6 +268,7 @@ function addGlobalStyle(style) {
259
268
  const styleElem = document.createElement("style");
260
269
  styleElem.innerHTML = style;
261
270
  document.head.appendChild(styleElem);
271
+ return styleElem;
262
272
  }
263
273
  function preloadImages(srcUrls, rejects = false) {
264
274
  const promises = srcUrls.map((src) => new Promise((res, rej) => {
@@ -310,6 +320,28 @@ function isScrollable(element) {
310
320
  horizontal: (overflowX === "scroll" || overflowX === "auto") && element.scrollWidth > element.clientWidth
311
321
  };
312
322
  }
323
+ function observeElementProp(element, property, callback) {
324
+ const elementPrototype = Object.getPrototypeOf(element);
325
+ if (elementPrototype.hasOwnProperty(property)) {
326
+ const descriptor = Object.getOwnPropertyDescriptor(elementPrototype, property);
327
+ Object.defineProperty(element, property, {
328
+ get: function() {
329
+ var _a;
330
+ return (_a = descriptor == null ? void 0 : descriptor.get) == null ? void 0 : _a.apply(this, arguments);
331
+ },
332
+ set: function() {
333
+ var _a;
334
+ const oldValue = this[property];
335
+ (_a = descriptor == null ? void 0 : descriptor.set) == null ? void 0 : _a.apply(this, arguments);
336
+ const newValue = this[property];
337
+ if (typeof callback === "function") {
338
+ callback.bind(this, oldValue, newValue);
339
+ }
340
+ return newValue;
341
+ }
342
+ });
343
+ }
344
+ }
313
345
 
314
346
  // lib/misc.ts
315
347
  function autoPlural(word, num) {
@@ -330,13 +362,15 @@ function debounce(func, timeout = 300) {
330
362
  };
331
363
  }
332
364
  function fetchAdvanced(_0) {
333
- return __async(this, arguments, function* (url, options = {}) {
365
+ return __async(this, arguments, function* (input, options = {}) {
334
366
  const { timeout = 1e4 } = options;
335
- const controller = new AbortController();
336
- const id = setTimeout(() => controller.abort(), timeout);
337
- const res = yield fetch(url, __spreadProps(__spreadValues({}, options), {
338
- signal: controller.signal
339
- }));
367
+ let signalOpts = {}, id = void 0;
368
+ if (timeout >= 0) {
369
+ const controller = new AbortController();
370
+ id = setTimeout(() => controller.abort(), timeout);
371
+ signalOpts = { signal: controller.signal };
372
+ }
373
+ const res = yield fetch(input, __spreadValues(__spreadValues({}, options), signalOpts));
340
374
  clearTimeout(id);
341
375
  return res;
342
376
  });
@@ -386,14 +420,29 @@ var SelectorObserver = class {
386
420
  __publicField(this, "baseElement");
387
421
  __publicField(this, "observer");
388
422
  __publicField(this, "observerOptions");
423
+ __publicField(this, "customOptions");
389
424
  __publicField(this, "listenerMap");
390
425
  this.baseElement = baseElement;
391
426
  this.listenerMap = /* @__PURE__ */ new Map();
392
427
  this.observer = new MutationObserver(() => this.checkAllSelectors());
428
+ const _a = options, {
429
+ defaultDebounce,
430
+ disableOnNoListeners,
431
+ enableOnAddListener
432
+ } = _a, observerOptions = __objRest(_a, [
433
+ "defaultDebounce",
434
+ "disableOnNoListeners",
435
+ "enableOnAddListener"
436
+ ]);
393
437
  this.observerOptions = __spreadValues({
394
438
  childList: true,
395
439
  subtree: true
396
- }, options);
440
+ }, observerOptions);
441
+ this.customOptions = {
442
+ defaultDebounce: defaultDebounce != null ? defaultDebounce : 0,
443
+ disableOnNoListeners: disableOnNoListeners != null ? disableOnNoListeners : false,
444
+ enableOnAddListener: enableOnAddListener != null ? enableOnAddListener : true
445
+ };
397
446
  }
398
447
  checkAllSelectors() {
399
448
  for (const [selector, listeners] of this.listenerMap.entries())
@@ -426,6 +475,8 @@ var SelectorObserver = class {
426
475
  }
427
476
  if (((_a = this.listenerMap.get(selector)) == null ? void 0 : _a.length) === 0)
428
477
  this.listenerMap.delete(selector);
478
+ if (this.listenerMap.size === 0 && this.customOptions.disableOnNoListeners)
479
+ this.disable();
429
480
  }
430
481
  }
431
482
  debounce(func, time) {
@@ -446,16 +497,18 @@ var SelectorObserver = class {
446
497
  */
447
498
  addListener(selector, options) {
448
499
  options = __spreadValues({ all: false, continuous: false, debounce: 0 }, options);
449
- if (options.debounce && options.debounce > 0 || this.observerOptions.defaultDebounce && this.observerOptions.defaultDebounce > 0) {
500
+ if (options.debounce && options.debounce > 0 || this.customOptions.defaultDebounce && this.customOptions.defaultDebounce > 0) {
450
501
  options.listener = this.debounce(
451
502
  options.listener,
452
- options.debounce || this.observerOptions.defaultDebounce
503
+ options.debounce || this.customOptions.defaultDebounce
453
504
  );
454
505
  }
455
506
  if (this.listenerMap.has(selector))
456
507
  this.listenerMap.get(selector).push(options);
457
508
  else
458
509
  this.listenerMap.set(selector, [options]);
510
+ if (this.enabled === false && this.customOptions.enableOnAddListener)
511
+ this.enable();
459
512
  this.checkSelector(selector, [options]);
460
513
  }
461
514
  /** Disables the observation of the child elements */
@@ -545,4 +598,4 @@ tr.getLanguage = () => {
545
598
  return curLang;
546
599
  };
547
600
 
548
- export { ConfigManager, SelectorObserver, addGlobalStyle, addParent, autoPlural, clamp, compress, debounce, decompress, fetchAdvanced, getUnsafeWindow, insertAfter, insertValues, interceptEvent, interceptWindowEvent, isScrollable, mapRange, openInNewTab, pauseFor, preloadImages, randRange, randomId, randomItem, randomItemIndex, randomizeArray, takeRandomItem, tr };
601
+ export { ConfigManager, SelectorObserver, addGlobalStyle, addParent, autoPlural, clamp, compress, debounce, decompress, fetchAdvanced, getUnsafeWindow, insertAfter, insertValues, interceptEvent, interceptWindowEvent, isScrollable, mapRange, observeElementProp, openInNewTab, pauseFor, preloadImages, randRange, randomId, randomItem, randomItemIndex, randomizeArray, takeRandomItem, tr };
@@ -18,9 +18,13 @@ type SelectorOptionsCommon = {
18
18
  /** Whether to debounce the listener to reduce calls to `querySelector` or `querySelectorAll` - set undefined or <=0 to disable (default) */
19
19
  debounce?: number;
20
20
  };
21
- export type SelectorObserverOptions = MutationObserverInit & {
21
+ export type SelectorObserverOptions = {
22
22
  /** If set, applies this debounce in milliseconds to all listeners that don't have their own debounce set */
23
23
  defaultDebounce?: number;
24
+ /** Whether to disable the observer when no listeners are present - default is true */
25
+ disableOnNoListeners?: boolean;
26
+ /** Whether to ensure the observer is enabled when a new listener is added - default is true */
27
+ enableOnAddListener?: boolean;
24
28
  };
25
29
  /** Observes the children of the given element for changes */
26
30
  export declare class SelectorObserver {
@@ -28,13 +32,14 @@ export declare class SelectorObserver {
28
32
  private baseElement;
29
33
  private observer;
30
34
  private observerOptions;
35
+ private customOptions;
31
36
  private listenerMap;
32
37
  /**
33
38
  * Creates a new SelectorObserver that will observe the children of the given base element selector for changes (only creation and deletion of elements by default)
34
39
  * @param baseElementSelector The selector of the element to observe
35
40
  * @param options Fine-tune what triggers the MutationObserver's checking function - `subtree` and `childList` are set to true by default
36
41
  */
37
- constructor(baseElementSelector: string, options?: SelectorObserverOptions);
42
+ constructor(baseElementSelector: string, options?: SelectorObserverOptions & MutationObserverInit);
38
43
  /**
39
44
  * Creates a new SelectorObserver that will observe the children of the given base element for changes (only creation and deletion of elements by default)
40
45
  * @param baseElement The element to observe
package/dist/lib/dom.d.ts CHANGED
@@ -16,8 +16,9 @@ export declare function addParent(element: Element, newParent: Element): Element
16
16
  * Adds global CSS style in the form of a `<style>` element in the document's `<head>`
17
17
  * This needs to be run after the `DOMContentLoaded` event has fired on the document object (or instantly if `@run-at document-end` is used).
18
18
  * @param style CSS string
19
+ * @returns Returns the created style element
19
20
  */
20
- export declare function addGlobalStyle(style: string): void;
21
+ export declare function addGlobalStyle(style: string): HTMLStyleElement;
21
22
  /**
22
23
  * Preloads an array of image URLs so they can be loaded instantly from the browser cache later on
23
24
  * @param rejects If set to `true`, the returned PromiseSettledResults will contain rejections for any of the images that failed to load
@@ -50,3 +51,13 @@ export declare function isScrollable(element: Element): {
50
51
  vertical: boolean;
51
52
  horizontal: boolean;
52
53
  };
54
+ /**
55
+ * Executes the callback when the passed element's property changes.
56
+ * Contrary to an element's attributes, properties can usually not be observed with a MutationObserver.
57
+ * This function shims the getter and setter of the property to invoke the callback.
58
+ *
59
+ * [Source](https://stackoverflow.com/a/61975440)
60
+ * @param property The name of the property to observe
61
+ * @param callback Callback to execute when the value is changed
62
+ */
63
+ export declare function observeElementProp<TElem extends Element = HTMLElement, TPropKey extends keyof TElem = keyof TElem>(element: TElem, property: TPropKey, callback: (oldVal: TElem[TPropKey], newVal: TElem[TPropKey]) => void): void;
@@ -12,6 +12,7 @@ export declare function randRange(max: number): number;
12
12
  /**
13
13
  * Generates a random ID with the specified length and radix (16 characters and hexadecimal by default)
14
14
  * Uses [`crypto.getRandomValues()`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues) for better cryptographic randomness
15
+ * ⚠️ Not suitable for generating encryption keys! Use [`crypto.subtle.generateKey()`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/generateKey) for that.
15
16
  * @param length The length of the ID to generate (defaults to 16)
16
17
  * @param radix The [radix](https://en.wikipedia.org/wiki/Radix) of each digit (defaults to 16 which is hexadecimal. Use 2 for binary, 10 for decimal, 36 for alphanumeric, etc.)
17
18
  */
@@ -29,12 +29,12 @@ export declare function pauseFor(time: number): Promise<void>;
29
29
  */
30
30
  export declare function debounce<TFunc extends (...args: TArgs[]) => void, TArgs = any>(func: TFunc, timeout?: number): (...args: TArgs[]) => void;
31
31
  /** Options for the `fetchAdvanced()` function */
32
- export type FetchAdvancedOpts = RequestInit & Partial<{
32
+ export type FetchAdvancedOpts = Omit<RequestInit & Partial<{
33
33
  /** Timeout in milliseconds after which the fetch call will be canceled with an AbortController signal */
34
34
  timeout: number;
35
- }>;
35
+ }>, "signal">;
36
36
  /** Calls the fetch API with special options like a timeout */
37
- export declare function fetchAdvanced(url: string, options?: FetchAdvancedOpts): Promise<Response>;
37
+ export declare function fetchAdvanced(input: RequestInfo | URL, options?: FetchAdvancedOpts): Promise<Response>;
38
38
  /**
39
39
  * Inserts the passed values into a string at the respective placeholders.
40
40
  * The placeholder format is `%n`, where `n` is the 1-indexed argument number.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sv443-network/userutils",
3
- "version": "4.0.0",
3
+ "version": "4.2.0",
4
4
  "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",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",