Просмотр исходного кода

feat(#49): interval-based SelectorObserver instances

Sv443 9 месяцев назад
Родитель
Сommit
ca6ff58c94
3 измененных файлов с 28 добавлено и 6 удалено
  1. 5 0
      .changeset/bright-snails-flash.md
  2. 3 1
      README.md
  3. 20 5
      lib/SelectorObserver.ts

+ 5 - 0
.changeset/bright-snails-flash.md

@@ -0,0 +1,5 @@
+---
+"@sv443-network/userutils": minor
+---
+
+Added option `checkInterval` to SelectorObserver to check on interval instead of on mutation

+ 3 - 1
README.md

@@ -146,7 +146,8 @@ new SelectorObserver(baseElementSelector: string, options?: SelectorObserverOpti
 ```
 
 A class that manages listeners that are called when elements at given selectors are found in the DOM.  
-This is useful for userscripts that need to wait for elements to be added to the DOM at an indeterminate point in time before they can be interacted with.  
+It is useful for userscripts that need to wait for elements to be added to the DOM at an indeterminate point in time before they can be interacted with.  
+By default, it uses the MutationObserver API to observe for any element changes, and as such is highly customizable, but can also be configured to run on a fixed interval.  
   
 The constructor takes a `baseElement`, which is a parent of the elements you want to observe.  
 If a selector string is passed instead, it will be used to find the element.  
@@ -161,6 +162,7 @@ Additionally, there are the following extra options:
 - `enableOnAddListener` - whether to enable the SelectorObserver when a new listener is added (defaults to true)
 - `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)
 - `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)
+- `checkInterval` - if set to a number, the checks will be run on interval instead of on mutation events - in that case all MutationObserverInit props will be ignored
   
 ⚠️ 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.
 

+ 20 - 5
lib/SelectorObserver.ts

@@ -1,5 +1,8 @@
 import { debounce } from "./misc.js";
 
+let domLoaded = false;
+document.addEventListener("DOMContentLoaded", () => domLoaded = true);
+
 /** Options for the `onSelector()` method of {@linkcode SelectorObserver} */
 export type SelectorListenerOptions<TElem extends Element = HTMLElement> = SelectorOptionsOne<TElem> | SelectorOptionsAll<TElem>;
 
@@ -38,6 +41,8 @@ export type SelectorObserverOptions = {
   disableOnNoListeners?: boolean;
   /** Whether to ensure the observer is enabled when a new listener is added - default is true */
   enableOnAddListener?: boolean;
+  /** If set to a number, the checks will be run on interval instead of on mutation events - in that case all MutationObserverInit props will be ignored */
+  checkInterval?: number;
 };
 
 export type SelectorObserverConstructorOptions = MutationObserverInit & SelectorObserverOptions;
@@ -46,7 +51,7 @@ export type SelectorObserverConstructorOptions = MutationObserverInit & Selector
 export class SelectorObserver {
   private enabled = false;
   private baseElement: Element | string;
-  private observer: MutationObserver;
+  private observer?: MutationObserver;
   private observerOptions: MutationObserverInit;
   private customOptions: SelectorObserverOptions;
   private listenerMap: Map<string, SelectorListenerOptions[]>;
@@ -67,8 +72,6 @@ export class SelectorObserver {
     this.baseElement = baseElement;
 
     this.listenerMap = new Map<string, SelectorListenerOptions[]>();
-    // if the arrow func isn't there, `this` will be undefined in the callback
-    this.observer = new MutationObserver(() => this.checkAllSelectors());
 
     const {
       defaultDebounce,
@@ -90,9 +93,21 @@ export class SelectorObserver {
       disableOnNoListeners: disableOnNoListeners ?? false,
       enableOnAddListener: enableOnAddListener ?? true,
     };
+
+    if(typeof this.customOptions.checkInterval !== "number") {
+      // if the arrow func isn't there, `this` will be undefined in the callback
+      this.observer = new MutationObserver(() => this.checkAllSelectors());
+    }
+    else {
+      this.checkAllSelectors();
+      setInterval(() => this.checkAllSelectors(), this.customOptions.checkInterval);
+    }
   }
 
   private checkAllSelectors(): void {
+    if(!this.enabled || !domLoaded)
+      return;
+
     for(const [selector, listeners] of this.listenerMap.entries())
       this.checkSelector(selector, listeners);
   }
@@ -172,7 +187,7 @@ export class SelectorObserver {
     if(!this.enabled)
       return;
     this.enabled = false;
-    this.observer.disconnect();
+    this.observer?.disconnect();
   }
 
   /**
@@ -185,7 +200,7 @@ export class SelectorObserver {
     if(this.enabled || !baseElement)
       return false;
     this.enabled = true;
-    this.observer.observe(baseElement, this.observerOptions);
+    this.observer?.observe(baseElement, this.observerOptions);
     if(immediatelyCheckSelectors)
       this.checkAllSelectors();
     return true;