@sv443-network/userutils 6.1.0 → 6.3.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,18 @@
1
1
  # @sv443-network/userutils
2
2
 
3
+ ## 6.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - fa09004: Made `openInNewTab()` use `GM.openInTab` by default and fall back to the old behavior.
8
+ Also added `background` param to specify if the tab should get focus when opened.
9
+
10
+ ## 6.2.0
11
+
12
+ ### Minor Changes
13
+
14
+ - 0173235: Add property to change the debounce edge in `SelectorObserver` instances
15
+
3
16
  ## 6.1.0
4
17
 
5
18
  ### Minor Changes
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  <!-- #MARKER Description -->
4
4
  ## UserUtils
5
- Zero-dependency library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, manage persistent user configurations, modify the DOM more easily and more.
5
+ Zero-dependency 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.
6
6
 
7
7
  Contains builtin TypeScript declarations. Fully web compatible and supports ESM and CJS imports and global declaration.
8
8
  If you like using this library, please consider [supporting the development ❤️](https://github.com/sponsors/Sv443)
@@ -159,6 +159,7 @@ Additionally, there are the following extra options:
159
159
  - `disableOnNoListeners` - whether to disable the SelectorObserver when there are no listeners left (defaults to false)
160
160
  - `enableOnAddListener` - whether to enable the SelectorObserver when a new listener is added (defaults to true)
161
161
  - `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)
162
+ - `defaultDebounceEdge` - can be set to "falling" (default) or "rising", to call the function at (rising) on the very first call and subsequent times after the given debounce time or (falling) the very last call after the debounce time passed with no new calls - [see `debounce()` for more info and a diagram](#debounce)
162
163
 
163
164
  ⚠️ 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.
164
165
 
@@ -177,16 +178,19 @@ The listener will be called immediately if the selector already exists in the DO
177
178
  > This will also include elements that were already found in a previous listener call.
178
179
  > If set to false (default), querySelector() will be used and only the first matching element will be returned.
179
180
 
180
- > If `options.continuous` is set to true, the listener will not be deregistered after it was called once (defaults to false).
181
+ > If `options.continuous` is set to true, this listener will not be deregistered after it was called once (defaults to false).
181
182
  >
182
- > ⚠️ You should keep usage of this option to a minimum, as it will cause the listener to be called every time the selector is *checked for and found* and this can stack up quite quickly.
183
+ > ⚠️ You should keep usage of this option to a minimum, as it will cause this listener to be called every time the selector is *checked for and found* and this can stack up quite quickly.
183
184
  > ⚠️ You should try to only use this option on SelectorObserver instances that are scoped really low in the DOM tree to prevent as many selector checks as possible from being triggered.
184
185
  > ⚠️ I also recommend always setting a debounce time (see constructor or below) if you use this option.
185
186
 
186
- > If `options.debounce` is set to a number above 0, the listener will be debounced by that amount of milliseconds (defaults to 0).
187
- > E.g. if the debounce time is set to 200 and the selector is found twice within 100ms, only the last call of the listener will be executed.
187
+ > If `options.debounce` is set to a number above 0, this listener will be debounced by that amount of milliseconds (defaults to 0).
188
+ > E.g. if the debounce time is set to 200 and the selector is found twice within 100ms, only the last call of this listener will be executed.
189
+
190
+ > `options.debounceEdge` is set to "falling" by default, which means the debounce timer will start after the last call of this listener.
191
+ > If set to "rising", the debounce timer will start after the first call of this listener.
188
192
 
189
- > When using TypeScript, the generic `TElement` can be used to specify the type of the element(s) that the listener will return.
193
+ > When using TypeScript, the generic `TElement` can be used to specify the type of the element(s) that this listener will return.
190
194
  > It will default to HTMLElement if left undefined.
191
195
 
192
196
  <br>
@@ -262,6 +266,9 @@ document.addEventListener("DOMContentLoaded", () => {
262
266
  attributeFilter: ["class", "style", "data-whatever"],
263
267
  // debounce all listeners by 100ms unless specified otherwise:
264
268
  defaultDebounce: 100,
269
+ // "rising" means listeners are called immediately and use the debounce as a timeout between subsequent calls - see the debounce() function for a better explanation
270
+ defaultDebounceEdge: "rising",
271
+ // other settings from the MutationObserver API can be set here too - see https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe#options
265
272
  });
266
273
 
267
274
  barObserver.addListener("#my-element", {
@@ -273,6 +280,8 @@ document.addEventListener("DOMContentLoaded", () => {
273
280
  barObserver.addListener("#my-other-element", {
274
281
  // set the debounce higher than provided by the defaultDebounce property:
275
282
  debounce: 250,
283
+ // adjust the debounceEdge back to the default "falling" for this specific listener:
284
+ debounceEdge: "falling",
276
285
  listener: (element) => {
277
286
  console.log("Other element's attributes changed:", element);
278
287
  },
@@ -573,14 +582,14 @@ preloadImages([
573
582
  ### openInNewTab()
574
583
  Usage:
575
584
  ```ts
576
- openInNewTab(url: string): void
585
+ openInNewTab(url: string, background?: boolean): void
577
586
  ```
578
587
 
579
- Creates an invisible anchor with a `_blank` target and clicks it.
580
- Contrary to `window.open()`, this has a lesser chance to get blocked by the browser's popup blocker and doesn't open the URL as a new window.
581
- This function has to be run in response to a user interaction event, else the browser might reject it.
588
+ Tries to use `GM.openInTab` to open the given URL in a new tab, or as a fallback if the grant is not given, creates an invisible anchor element and clicks it.
589
+ If `background` is set to true, the tab will be opened in the background. Leave `undefined` to use the browser's default behavior.
582
590
 
583
- ⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
591
+ ⚠️ Needs the `@grant GM.openInTab` directive, otherwise only the fallback behavior will be used and the warning below is extra important:
592
+ ⚠️ For the fallback to work, this function needs to be run in response to a user interaction event, else the browser might reject it.
584
593
 
585
594
  <details><summary><b>Example - click to view</b></summary>
586
595
 
@@ -588,7 +597,8 @@ This function has to be run in response to a user interaction event, else the br
588
597
  import { openInNewTab } from "@sv443-network/userutils";
589
598
 
590
599
  document.querySelector("#my-button").addEventListener("click", () => {
591
- openInNewTab("https://example.org/");
600
+ // open in background:
601
+ openInNewTab("https://example.org/", true);
592
602
  });
593
603
  ```
594
604
 
@@ -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.1.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 6.3.0
12
12
  // @license MIT
13
13
  // @copyright Sv443 (https://github.com/Sv443)
14
14
 
@@ -336,18 +336,22 @@ var UserUtils = (function (exports) {
336
336
  }));
337
337
  return Promise.allSettled(promises);
338
338
  }
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);
339
+ function openInNewTab(href, background) {
340
+ try {
341
+ GM.openInTab(href, background);
342
+ } catch (e) {
343
+ const openElem = document.createElement("a");
344
+ Object.assign(openElem, {
345
+ className: "userutils-open-in-new-tab",
346
+ target: "_blank",
347
+ rel: "noopener noreferrer",
348
+ href
349
+ });
350
+ openElem.style.display = "none";
351
+ document.body.appendChild(openElem);
352
+ openElem.click();
353
+ setTimeout(openElem.remove, 50);
354
+ }
351
355
  }
352
356
  function interceptEvent(eventObject, eventName, predicate = () => true) {
353
357
  Error.stackTraceLimit = Math.max(Error.stackTraceLimit, 100);
@@ -512,10 +516,12 @@ var UserUtils = (function (exports) {
512
516
  this.observer = new MutationObserver(() => this.checkAllSelectors());
513
517
  const _a = options, {
514
518
  defaultDebounce,
519
+ defaultDebounceEdge,
515
520
  disableOnNoListeners,
516
521
  enableOnAddListener
517
522
  } = _a, observerOptions = __objRest(_a, [
518
523
  "defaultDebounce",
524
+ "defaultDebounceEdge",
519
525
  "disableOnNoListeners",
520
526
  "enableOnAddListener"
521
527
  ]);
@@ -525,6 +531,7 @@ var UserUtils = (function (exports) {
525
531
  }, observerOptions);
526
532
  this.customOptions = {
527
533
  defaultDebounce: defaultDebounce != null ? defaultDebounce : 0,
534
+ defaultDebounceEdge: defaultDebounceEdge != null ? defaultDebounceEdge : "rising",
528
535
  disableOnNoListeners: disableOnNoListeners != null ? disableOnNoListeners : false,
529
536
  enableOnAddListener: enableOnAddListener != null ? enableOnAddListener : true
530
537
  };
@@ -564,12 +571,8 @@ var UserUtils = (function (exports) {
564
571
  this.disable();
565
572
  }
566
573
  }
567
- debounce(func, time) {
568
- let timeout;
569
- return function(...args) {
570
- clearTimeout(timeout);
571
- timeout = setTimeout(() => func.apply(this, args), time);
572
- };
574
+ debounce(func, time, edge = "falling") {
575
+ return debounce(func, time, edge);
573
576
  }
574
577
  /**
575
578
  * Starts observing the children of the base element for changes to the given {@linkcode selector} according to the set {@linkcode options}
@@ -585,7 +588,8 @@ var UserUtils = (function (exports) {
585
588
  if (options.debounce && options.debounce > 0 || this.customOptions.defaultDebounce && this.customOptions.defaultDebounce > 0) {
586
589
  options.listener = this.debounce(
587
590
  options.listener,
588
- options.debounce || this.customOptions.defaultDebounce
591
+ options.debounce || this.customOptions.defaultDebounce,
592
+ options.debounceEdge || this.customOptions.defaultDebounceEdge
589
593
  );
590
594
  }
591
595
  if (this.listenerMap.has(selector))
package/dist/index.js CHANGED
@@ -316,18 +316,22 @@ function preloadImages(srcUrls, rejects = false) {
316
316
  }));
317
317
  return Promise.allSettled(promises);
318
318
  }
319
- function openInNewTab(href) {
320
- const openElem = document.createElement("a");
321
- Object.assign(openElem, {
322
- className: "userutils-open-in-new-tab",
323
- target: "_blank",
324
- rel: "noopener noreferrer",
325
- href
326
- });
327
- openElem.style.display = "none";
328
- document.body.appendChild(openElem);
329
- openElem.click();
330
- setTimeout(openElem.remove, 50);
319
+ function openInNewTab(href, background) {
320
+ try {
321
+ GM.openInTab(href, background);
322
+ } catch (e) {
323
+ const openElem = document.createElement("a");
324
+ Object.assign(openElem, {
325
+ className: "userutils-open-in-new-tab",
326
+ target: "_blank",
327
+ rel: "noopener noreferrer",
328
+ href
329
+ });
330
+ openElem.style.display = "none";
331
+ document.body.appendChild(openElem);
332
+ openElem.click();
333
+ setTimeout(openElem.remove, 50);
334
+ }
331
335
  }
332
336
  function interceptEvent(eventObject, eventName, predicate = () => true) {
333
337
  Error.stackTraceLimit = Math.max(Error.stackTraceLimit, 100);
@@ -492,10 +496,12 @@ var SelectorObserver = class {
492
496
  this.observer = new MutationObserver(() => this.checkAllSelectors());
493
497
  const _a = options, {
494
498
  defaultDebounce,
499
+ defaultDebounceEdge,
495
500
  disableOnNoListeners,
496
501
  enableOnAddListener
497
502
  } = _a, observerOptions = __objRest(_a, [
498
503
  "defaultDebounce",
504
+ "defaultDebounceEdge",
499
505
  "disableOnNoListeners",
500
506
  "enableOnAddListener"
501
507
  ]);
@@ -505,6 +511,7 @@ var SelectorObserver = class {
505
511
  }, observerOptions);
506
512
  this.customOptions = {
507
513
  defaultDebounce: defaultDebounce != null ? defaultDebounce : 0,
514
+ defaultDebounceEdge: defaultDebounceEdge != null ? defaultDebounceEdge : "rising",
508
515
  disableOnNoListeners: disableOnNoListeners != null ? disableOnNoListeners : false,
509
516
  enableOnAddListener: enableOnAddListener != null ? enableOnAddListener : true
510
517
  };
@@ -544,12 +551,8 @@ var SelectorObserver = class {
544
551
  this.disable();
545
552
  }
546
553
  }
547
- debounce(func, time) {
548
- let timeout;
549
- return function(...args) {
550
- clearTimeout(timeout);
551
- timeout = setTimeout(() => func.apply(this, args), time);
552
- };
554
+ debounce(func, time, edge = "falling") {
555
+ return debounce(func, time, edge);
553
556
  }
554
557
  /**
555
558
  * Starts observing the children of the base element for changes to the given {@linkcode selector} according to the set {@linkcode options}
@@ -565,7 +568,8 @@ var SelectorObserver = class {
565
568
  if (options.debounce && options.debounce > 0 || this.customOptions.defaultDebounce && this.customOptions.defaultDebounce > 0) {
566
569
  options.listener = this.debounce(
567
570
  options.listener,
568
- options.debounce || this.customOptions.defaultDebounce
571
+ options.debounce || this.customOptions.defaultDebounce,
572
+ options.debounceEdge || this.customOptions.defaultDebounceEdge
569
573
  );
570
574
  }
571
575
  if (this.listenerMap.has(selector))
package/dist/index.mjs CHANGED
@@ -314,18 +314,22 @@ function preloadImages(srcUrls, rejects = false) {
314
314
  }));
315
315
  return Promise.allSettled(promises);
316
316
  }
317
- function openInNewTab(href) {
318
- const openElem = document.createElement("a");
319
- Object.assign(openElem, {
320
- className: "userutils-open-in-new-tab",
321
- target: "_blank",
322
- rel: "noopener noreferrer",
323
- href
324
- });
325
- openElem.style.display = "none";
326
- document.body.appendChild(openElem);
327
- openElem.click();
328
- setTimeout(openElem.remove, 50);
317
+ function openInNewTab(href, background) {
318
+ try {
319
+ GM.openInTab(href, background);
320
+ } catch (e) {
321
+ const openElem = document.createElement("a");
322
+ Object.assign(openElem, {
323
+ className: "userutils-open-in-new-tab",
324
+ target: "_blank",
325
+ rel: "noopener noreferrer",
326
+ href
327
+ });
328
+ openElem.style.display = "none";
329
+ document.body.appendChild(openElem);
330
+ openElem.click();
331
+ setTimeout(openElem.remove, 50);
332
+ }
329
333
  }
330
334
  function interceptEvent(eventObject, eventName, predicate = () => true) {
331
335
  Error.stackTraceLimit = Math.max(Error.stackTraceLimit, 100);
@@ -490,10 +494,12 @@ var SelectorObserver = class {
490
494
  this.observer = new MutationObserver(() => this.checkAllSelectors());
491
495
  const _a = options, {
492
496
  defaultDebounce,
497
+ defaultDebounceEdge,
493
498
  disableOnNoListeners,
494
499
  enableOnAddListener
495
500
  } = _a, observerOptions = __objRest(_a, [
496
501
  "defaultDebounce",
502
+ "defaultDebounceEdge",
497
503
  "disableOnNoListeners",
498
504
  "enableOnAddListener"
499
505
  ]);
@@ -503,6 +509,7 @@ var SelectorObserver = class {
503
509
  }, observerOptions);
504
510
  this.customOptions = {
505
511
  defaultDebounce: defaultDebounce != null ? defaultDebounce : 0,
512
+ defaultDebounceEdge: defaultDebounceEdge != null ? defaultDebounceEdge : "rising",
506
513
  disableOnNoListeners: disableOnNoListeners != null ? disableOnNoListeners : false,
507
514
  enableOnAddListener: enableOnAddListener != null ? enableOnAddListener : true
508
515
  };
@@ -542,12 +549,8 @@ var SelectorObserver = class {
542
549
  this.disable();
543
550
  }
544
551
  }
545
- debounce(func, time) {
546
- let timeout;
547
- return function(...args) {
548
- clearTimeout(timeout);
549
- timeout = setTimeout(() => func.apply(this, args), time);
550
- };
552
+ debounce(func, time, edge = "falling") {
553
+ return debounce(func, time, edge);
551
554
  }
552
555
  /**
553
556
  * Starts observing the children of the base element for changes to the given {@linkcode selector} according to the set {@linkcode options}
@@ -563,7 +566,8 @@ var SelectorObserver = class {
563
566
  if (options.debounce && options.debounce > 0 || this.customOptions.defaultDebounce && this.customOptions.defaultDebounce > 0) {
564
567
  options.listener = this.debounce(
565
568
  options.listener,
566
- options.debounce || this.customOptions.defaultDebounce
569
+ options.debounce || this.customOptions.defaultDebounce,
570
+ options.debounceEdge || this.customOptions.defaultDebounceEdge
567
571
  );
568
572
  }
569
573
  if (this.listenerMap.has(selector))
@@ -17,10 +17,17 @@ type SelectorOptionsCommon = {
17
17
  continuous?: boolean;
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
+ /** Whether to call the function at the very first call ("rising" edge) or the very last call ("falling" edge, default) */
21
+ debounceEdge?: "rising" | "falling";
20
22
  };
21
23
  export type SelectorObserverOptions = {
22
24
  /** If set, applies this debounce in milliseconds to all listeners that don't have their own debounce set */
23
25
  defaultDebounce?: number;
26
+ /**
27
+ * If set, applies this debounce edge to all listeners that don't have their own set.
28
+ * Edge = Whether to call the function at the very first call ("rising" edge) or the very last call ("falling" edge, default)
29
+ */
30
+ defaultDebounceEdge?: "rising" | "falling";
24
31
  /** Whether to disable the observer when no listeners are present - default is true */
25
32
  disableOnNoListeners?: boolean;
26
33
  /** Whether to ensure the observer is enabled when a new listener is added - default is true */
package/dist/lib/dom.d.ts CHANGED
@@ -26,12 +26,12 @@ export declare function addGlobalStyle(style: string): HTMLStyleElement;
26
26
  */
27
27
  export declare function preloadImages(srcUrls: string[], rejects?: boolean): Promise<PromiseSettledResult<unknown>[]>;
28
28
  /**
29
- * Creates an invisible anchor with a `_blank` target and clicks it.
30
- * Contrary to `window.open()`, this has a lesser chance to get blocked by the browser's popup blocker and doesn't open the URL as a new window.
31
- *
32
- * This function has to be run in response to a user interaction event, else the browser might reject it.
29
+ * Tries to use `GM.openInTab` to open the given URL in a new tab, otherwise if the grant is not given, creates an invisible anchor element and clicks it.
30
+ * For the fallback to work, this function needs to be run in response to a user interaction event, else the browser might reject it.
31
+ * @param href The URL to open in a new tab
32
+ * @param background If set to `true`, the tab will be opened in the background - set to `undefined` (default) to use the browser's default behavior
33
33
  */
34
- export declare function openInNewTab(href: string): void;
34
+ export declare function openInNewTab(href: string, background?: boolean): void;
35
35
  /**
36
36
  * Intercepts the specified event on the passed object and prevents it from being called if the called {@linkcode predicate} function returns a truthy value.
37
37
  * If no predicate is specified, all events will be discarded.
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@sv443-network/userutils",
3
3
  "libName": "UserUtils",
4
- "version": "6.1.0",
5
- "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",
4
+ "version": "6.3.0",
5
+ "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",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.mjs",
8
8
  "types": "dist/lib/index.d.ts",