Преглед на файлове

feat: functions isDomLoaded and onDomLoad

Sv443 преди 1 месец
родител
ревизия
905fea4999
променени са 6 файла, в които са добавени 104 реда и са изтрити 0 реда
  1. 5 0
      .changeset/cuddly-goats-pump.md
  2. 5 0
      .changeset/metal-ravens-call.md
  3. 2 0
      README-summary.md
  4. 2 0
      README.md
  5. 61 0
      docs.md
  6. 29 0
      lib/dom.ts

+ 5 - 0
.changeset/cuddly-goats-pump.md

@@ -0,0 +1,5 @@
+---
+"@sv443-network/userutils": minor
+---
+
+Added function `isDomLoaded()` to check if the DOM is queryable, regardless of `@run-at` setting

+ 5 - 0
.changeset/metal-ravens-call.md

@@ -0,0 +1,5 @@
+---
+"@sv443-network/userutils": minor
+---
+
+Added function `onDomLoad()` to call a callback and/or resolve a Promise when the DOM is loaded, even retroactively

+ 2 - 0
README-summary.md

@@ -36,6 +36,8 @@ View the documentation of previous major releases:
 - **DOM:**
     - [`SelectorObserver`](https://github.com/Sv443-Network/UserUtils/blob/main/docs.md#selectorobserver) - class that manages listeners that are called when selectors are found in the DOM
     - [`getUnsafeWindow()`](https://github.com/Sv443-Network/UserUtils/blob/main/docs.md#getunsafewindow) - get the unsafeWindow object or fall back to the regular window object
+    - [`isDomLoaded()`](https://github.com/Sv443-Network/UserUtils/blob/main/docs.md#isdomloaded) - check if the DOM has finished loading and can be queried and modified
+    - [`onDomLoad()`](https://github.com/Sv443-Network/UserUtils/blob/main/docs.md#ondomload) - run a function or pause async execution until the DOM has finished loading (or immediately if DOM is already loaded)
     - [`addParent()`](https://github.com/Sv443-Network/UserUtils/blob/main/docs.md#addparent) - add a parent element around another element
     - [`addGlobalStyle()`](https://github.com/Sv443-Network/UserUtils/blob/main/docs.md#addglobalstyle) - add a global style to the page
     - [`preloadImages()`](https://github.com/Sv443-Network/UserUtils/blob/main/docs.md#preloadimages) - preload images into the browser cache for faster loading later on

+ 2 - 0
README.md

@@ -39,6 +39,8 @@ View the documentation of previous major releases:
   - [**DOM:**](./docs.md#dom)
     - [`SelectorObserver`](./docs.md#selectorobserver) - class that manages listeners that are called when selectors are found in the DOM
     - [`getUnsafeWindow()`](./docs.md#getunsafewindow) - get the unsafeWindow object or fall back to the regular window object
+    - [`isDomLoaded()`](./docs.md#isdomloaded) - check if the DOM has finished loading and can be queried and modified
+    - [`onDomLoad()`](./docs.md#ondomload) - run a function or pause async execution until the DOM has finished loading (or immediately if DOM is already loaded)
     - [`addParent()`](./docs.md#addparent) - add a parent element around another element
     - [`addGlobalStyle()`](./docs.md#addglobalstyle) - add a global style to the page
     - [`preloadImages()`](./docs.md#preloadimages) - preload images into the browser cache for faster loading later on

+ 61 - 0
docs.md

@@ -28,6 +28,8 @@ For submitting bug reports or feature requests, please use the [GitHub issue tra
   - [**DOM:**](#dom)
     - [`SelectorObserver`](#selectorobserver) - class that manages listeners that are called when selectors are found in the DOM
     - [`getUnsafeWindow()`](#getunsafewindow) - get the unsafeWindow object or fall back to the regular window object
+    - [`isDomLoaded()`](#isdomloaded) - check if the DOM has finished loading and can be queried and modified
+    - [`onDomLoad()`](#ondomload) - run a function or pause async execution until the DOM has finished loading (or immediately if DOM is already loaded)
     - [`addParent()`](#addparent) - add a parent element around another element
     - [`addGlobalStyle()`](#addglobalstyle) - add a global style to the page
     - [`preloadImages()`](#preloadimages) - preload images into the browser cache for faster loading later on
@@ -447,6 +449,65 @@ document.body.dispatchEvent(mouseEvent);
 
 <br>
 
+### isDomLoaded()
+Signature:  
+```ts
+isDomLoaded(): boolean
+```
+  
+Returns whether or not the DOM has finished loading and can be queried, manipulated, interacted with, etc.  
+  
+As long as the library is loaded immediately on page load, this function will always return the correct value, even if your runtime is executed after the DOM has finished loading (like when using `@run-at document-end`).  
+Just make sure to not lazy-load the library, evaluate it on-demand, or anything else that would delay the execution.  
+  
+<details><summary><b>Example - click to view</b></summary>
+
+```ts
+import { isDomLoaded } from "@sv443-network/userutils";
+
+console.log(isDomLoaded()); // false
+
+document.addEventListener("DOMContentLoaded", () => {
+  console.log(isDomLoaded()); // true
+});
+```
+</details>
+
+<br>
+
+### onDomLoad()
+Signature:  
+```ts
+onDomLoad(cb?: () => void): Promise<void>
+```
+  
+Executes a callback and/or resolves the returned Promise when the DOM has finished loading.  
+Immediately executes/resolves if the DOM is already loaded.  
+  
+This alleviates the problem of the `DOMContentLoaded` event only being fired once and if you missed it, you're out of luck and can't really be certain.  
+  
+<details><summary><b>Example - click to view</b></summary>
+
+```ts
+import { onDomLoad } from "@sv443-network/userutils";
+
+// callback gets executed at basically the same time as the `console.log("DOM loaded!")` below:
+onDomLoad(() => {
+  console.log("DOM has finished loading.");
+});
+
+document.addEventListener("DOMContentLoaded", async () => {
+  console.log("DOM loaded!");
+
+  // immediately resolves because the DOM is already loaded:
+  await onDomLoad();
+
+  console.log("DOM has finished loading.");
+});
+```
+
+<br>
+
 ### addParent()
 Signature:  
 ```ts

+ 29 - 0
lib/dom.ts

@@ -3,6 +3,10 @@
  * This module contains various functions for working with the DOM - [see the documentation for more info](https://github.com/Sv443-Network/UserUtils/blob/main/docs.md#dom)
  */
 
+/** Whether the DOM has finished loading */
+let domReady = false;
+document.addEventListener("DOMContentLoaded", () => domReady = true);
+
 /**
  * Returns `unsafeWindow` if the `@grant unsafeWindow` is given, otherwise falls back to the regular `window`
  */
@@ -311,3 +315,28 @@ export function probeElementStyle<
   setTimeout(() => el.remove(), 1);
   return result;
 }
+
+/** Returns whether or not the DOM has finished loading */
+export function isDomLoaded(): boolean {
+  return domReady;
+}
+
+/**
+ * Executes a callback and/or resolves the returned Promise when the DOM has finished loading.  
+ * Immediately executes/resolves if the DOM is already loaded.
+ * @param cb Callback to execute when the DOM has finished loading
+ * @returns Returns a Promise that resolves when the DOM has finished loading
+ */
+export function onDomLoad(cb?: () => void): Promise<void> {
+  return new Promise((res) => {
+    if(domReady) {
+      cb?.();
+      res();
+    }
+    else
+      document.addEventListener("DOMContentLoaded", () => {
+        cb?.();
+        res();
+      });
+  });
+}