UNPKG

dom-track

Version:

Minimal utility for observing DOM elements appearing and disappearing using callbacks or Promises.

245 lines (193 loc) โ€ข 7.46 kB
# domTrack [[_TOC_]] ## ๐Ÿง Tracks DOM changes with Promise chaining Fluent utility to track DOM elements as they appear, change, or get removed โ€” using both callback and Promise-based APIs. A lazy-evaluated DSL powered by `MutationObserver` with a simpler, ergonomic API designed for real-world UIs. Efficient enough for isolated components, and scalable for full-page applications. Supports: - `seen()` โ€” when elements matching a selector appear - `changed()` โ€” when matching elements have their content updated - `gone()` โ€” when elements are removed from the DOM - `goneForElement()` โ€” one-off removal watcher - `.once()` `.timeout()` โ€” Promise chaining support ## ๐Ÿ“ฆ Install ```sh npm install dom-track ``` ```ts // Typescript import {DomTrack} from 'dom-track'; ``` ```js // CommonJS (require) const {DomTrack} = require('dom-track'); // ES Modules (import) import {DomTrack} from 'dom-track'; ``` ## ๐Ÿ”ง API ```ts new DomTrack(container: HTMLElement, options?: DomTrackOptions) ``` Options: ```ts interface DomTrackOptions { observeAttributes?: boolean; // default false observeSubtree?: boolean; // default true debounceMs?: number; // default 0 waitForTimeoutMs?: number; // default 5000 signal?: AbortSignal; } ``` Note: - `debounceMs` must be set on the observer instance and applies to all its `.seen()` and `.changed()` handlers. - `.gone()` and `.goneForElement()` callbacks are invoked immediately and are **not debounced**. ### watches #### .seen(selector, callback?) Watches for elements matching the selector appearing in the DOM. - With callback โ†’ returns `WatchHandle` (`.cancel()`) - Without callback โ†’ returns `WatchPromise<HTMLElement>` (`.once()`, `.timeout()`, etc.) #### .changed(selector, callback?) Watches for DOM changes inside elements matching the selector. (Same return types as `.seen()`) #### .gone(selector, callback) Watches for matching elements being removed. Returns `WatchHandle`. #### .goneForElement(element, callback) Watches for a specific element to be removed. Returns `WatchHandle`. #### .seen() and .changed() support two usage styles ##### Callback style Pass a callback to get a WatchHandle ```ts const handle = tracker.seen('.item', el => { ... }); handle.cancel(); ``` #### Promise style Omit the callback to get a WatchPromise<HTMLElement> that supports fluent chaining: ```ts tracker.seen('.item') .once() .timeout(2000) .then(el => { ... }); ``` The WatchPromise supports: - `.once()` โ€” Resolve only on first match - `.timeout(ms, onTimeout?)` โ€” Reject after timeout - `.abortSignal(signal)` โ€” Cancel via AbortController - `.then()`, `.catch()`, `.finally()` โ€” Standard Promise chaining ## โœจ Example ```ts import {DomTrack} from 'dom-track'; // Init tracker on a DOM object const tracker = new DomTrack(document.body, {debounceMs: 3000}); tracker.seen('.toasts', el => { console.log('One of many toasts appeared', el) }); // Limit to once and and set timeout tracker.seen('.toasts') .once() .timeout(3000, ()=>{console.log('Toast didn\'t appear');}) .then(el => { console.log('One of many toasts appeared', el); }); // Use external callback function callback(el:HTMLElement){ console.log('Toast has appeared', el); } tracker.seen('.toast').then(callback); // Watch for changes tracker.changed('.chat-box') .once() .timeout(2000) .then(el => { console.log("Chat box content changed once, exiting", el); }); ``` ## โšก Performance To keep DomTrack efficient: - Use `.gone()` / `.goneForElement()` to stop watching removed elements. - Call `.cancel()` when a watch is no longer needed. Once all watchers of an instance are cancelled, the internally managed `MutationObserver is automatically suspended and reinitialized only when required. - Use `debounceMs` to reduce how often callbacks are triggered by rapid DOM changes. - Scope wisely: avoid observing all of `document.body` unless truly necessary. Narrower containers are more performant. This especially applies to `DomTrack` instances with expensive options like `observeAttributes` enabled. - Combine multiple scoped `DomTrack` instances with a single high-level `DomTrackRemovals` to monitor removals without incurring extra observation overhead. - Call `.disconnect()` when you're done tracking or if the container element has been removed. `MutationObservers` remain active even after their target is detached, which can lead to wasted processing and memory overhead. - Test performance in your environment โ€” use browser dev tools to measure observer impact in context. ### About DomTrackRemovals `DomTrackRemovals` is a optional lightweight removal-only tracker ideal for watching large containers like `document.body`. It observes only element removals, making it faster than a full DomTrack instance that tracks all mutation types. Best used when watching removals from a higher-level container in the DOM. Best practices: - Use scoped `DomTrack` instances for fine-grained `.seen()` or `.changed()` tracking - Use `DomTrackRemovals` higher in the DOM to monitor removals efficiently - Avoid using both on the same broad container โ€” this will slow down execution unnecessarily - If you're already using `DomTrack` on `document.body`, `DomTrackRemovals` likely won't add value #### Example ```html <body> <div id="app"> <div id="head"> <div class="toast">Toast 1</div> </div> <!-- ...other stuff going on... --> <div id="content"> <div class="chat-box">Chat content</div> </div> </div> </body> ``` ```ts import { DomTrack, DomTrackRemovals } from 'dom-track'; // Scoped trackers for additions and changes const trackerHead = new DomTrack(document.getElementById('head')!, { debounceMs: 200 }); trackerHead.seen('.toast', el => console.log('Toast appeared:', el)); const trackerContent = new DomTrack(document.getElementById('content')!, { debounceMs: 200 }); trackerContent.changed('.chat-box', el => console.log('Chat box changed:', el)); // Efficient high-level removal tracker const removalTracker = new DomTrackRemovals(document.body); removalTracker.gone('.toast', el => console.log('Toast removed:', el)); removalTracker.gone('.chat-box', el => console.log('Chat box removed:', el)); removalTracker.gone('#app', el => { console.log('App removed:', el); trackerHead.disconnect(); trackerContent.disconnect(); }); ``` ## ๐Ÿ’ก Advanced Usage examples ```ts const tracker = new DomTrack(document.body); // Cancel via .cancel() const toasts = tracker.seen('.toasts') .then(el => { console.log('Toast appeared', el); toasts.cancel(); }); // Cancel the entire tracker tracker.disconnect(); // Abort via AbortController const controller = new AbortController(); tracker.seen('.toast') .abortSignal(controller.signal) .then(el => { console.log("Toast appeared", el); }); controller.abort(); // Cancel a callback-based watcher const chatBoxWatcher = tracker.changed(".chat-box", (el) => { console.log("Chat box content changed:", el); }); chatBoxWatcher.cancel(); // Abort the entire tracker via signal const controller2 = new AbortController(); const observer2 = new DomTrack(document.body, { signal: controller2.signal }); controller2.abort(); ``` ## ๐Ÿงช DomTrack development ```sh npm install npm run test ``` ### publish on NPM repo ```sh npm adduser npm publish --access public ``` ## ๐Ÿ“„ License [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html)