UNPKG

dom-track

Version:

Fluent utility to track DOM elements as they appear, change, or get removed — using both callback and Promise-based APIs.

218 lines (141 loc) 8.14 kB
# dom-track <img alt="dom-track logo" src="https://gitlab.com/horlacher/dom-track/-/raw/main/logo.png" width="128" /> [[_TOC_]] ## Fluent DOM element tracking with Promise-based API 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 tracker - `.once()` `.each()` `.timeout()` — Fluent Promise API ## 📦 Install Install [from npm](https://www.npmjs.com/package/dom-track): ```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 - `observeChildList` Default: true Watches for **added** or **removed child elements** in the DOM. - `observeAttributes` Default: false Watches for **attribute changes** (e.g., class, style, data-*). - `observeCharacterData` Default: false Watches for **text content changes** in nodes. - `observeSubtree` Default: true Watches **nested children** inside the container. - `debounceMs` Default: null Delay before firing callbacks, useful for reducing noise on rapid changes. Set to `null` for immediate processing (no debounce), or a number in milliseconds to debounce. - `waitForTimeoutMs` Default: 5000 How long `.timeout()` waits by default when watching for elements or changes. - `signal` Optional ➡️ [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) An AbortSignal to cancel the tracker externally. See ➡️ [API Options](https://gitlab.com/horlacher/dom-track/-/blob/main/docs/api_options.md) for more details and examples. ### Tracking methods #### .seen(selector, callback?) Watches for elements matching the selector appearing in the DOM. - With callback → returns `TrackHandle` (`.cancel()`) - Without callback → returns `FluentPromise<HTMLElement>` (`.once()`, `.each()`, `.timeout()`, etc.) **Callback signature:** `(element: HTMLElement, container: HTMLElement) => void` #### .changed(selector, callback?) Watches for DOM changes inside elements matching the selector. (Same return types as `.seen()`) **Callback signature:** `(element: HTMLElement, container: HTMLElement) => void` #### .gone(selector, callback) Watches for matching elements being removed. Returns `TrackHandle`. **Callback signature:** `(element: HTMLElement, container: HTMLElement) => void` #### .goneForElement(element, callback) Watches for a specific element to be removed. Returns `TrackHandle`. **Callback signature:** `(element: HTMLElement, container: HTMLElement) => void` #### .seen() and .changed() support two usage styles ##### Callback style Pass a callback to get a TrackHandle: ```ts tracker.seen('.item', (el, container) => { ... }); ``` ##### Fluent style Use fluent methods for more control: ```ts // Fire callback once, then auto-cancel tracker.seen('.item').timeout().once((el, container) => { ... }); // Fire callback for each match tracker.seen('.item').each((el, container) => { ... }); ``` FluentPromise methods: - `.once(callback)` — Register callback that fires once then auto-cancels, returns `TrackHandle` - `.each(callback)` — Register callback that fires for each match, returns `TrackHandle` - `.timeout(ms?, onTimeout?)` — Reject promise after timeout, uses **waitForTimeoutMs** as default for `ms` - `.abortSignal(signal)` — Cancel via AbortController ➡️ [Cancellation examples](https://gitlab.com/horlacher/dom-track/-/blob/main/docs/examples.md#cancellation-examples) - `.then()`, `.catch()`, `.finally()` — Standard Promise chaining ##### Promise Chaining Behavior Configuration methods like `.timeout()` and `.abortSignal()` can be called in any order before `.then()`, `.catch()`, or `.finally()`. These configure how the tracker behaves, but do not start it until a standard Promise method is called. **Note**: `.once()` and `.each()` return `TrackHandle` objects for callback management. ## ✨ Examples ```ts import { DomTrack } from "dom-track"; // Init tracker on a DOM object const tracker = new DomTrack(document.body, { debounceMs: 300 }); // Track all toasts that appear tracker.seen(".toasts", (el, container) => { console.log("One of many toasts appeared", el); }); // Fluent style const handle = tracker.seen(".toasts").each((el, container) => { console.log("Another toast appeared", el); }); const handle = tracker.seen(".toasts").timeout().once((el, container) => { console.log("Track only the first toast, then stop"); }); const handle = tracker.changed('.chat-box').each((el, container) => { console.log("Chat box content changed", el); }); ``` See more ➡️ [usage examples](https://gitlab.com/horlacher/dom-track/-/blob/main/docs/examples.md#-usage-examples). ### Container Boundary Behavior **Important:** `seen()` callbacks may fire for elements that were initially added to the container but then moved outside before the callback runs (e.g., modals moved to `document.body` for z-index reasons). If you need strict boundary checking, implement it in your callback using the `container` parameter. See ➡️ [boundary checking examples](https://gitlab.com/horlacher/dom-track/-/blob/main/docs/examples.md#boundary-checking-examples). ## ⚡ Performance To keep DomTrack efficient: - Use `.gone()` / `.goneForElement()` to track removed elements. - Call `.cancel()` when a tracker is no longer needed. Once all trackers 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. Set to `null` (default) for immediate processing, or a number for debounced processing. - 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` or `observeCharacterData` enabled. Use `observeSubtree` to only monitor changes in direct descendants. - Combine multiple scoped `DomTrack` instances with a single high-level `DomTrackRemovals` to monitor removals without incurring extra observation overhead. See ➡️ [DomTrackRemovals](https://gitlab.com/horlacher/dom-track/-/blob/main/docs/dom_track_removals.md) for more details. - 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. ## 🧪 DomTrack development ```sh npm install npm run test ``` ### publish on NPM repo ```sh npm adduser npm publish --access public ``` ## 📚 Docs Directory ➡️ [API Options](https://gitlab.com/horlacher/dom-track/-/blob/main/docs/api_options.md) ➡️ [DomTrackRemovals](https://gitlab.com/horlacher/dom-track/-/blob/main/docs/dom_track_removals.md) ➡️ [Usage examples](https://gitlab.com/horlacher/dom-track/-/blob/main/docs/examples.md) ## 📝 Changelog See [CHANGELOG.md](https://gitlab.com/horlacher/dom-track/-/blob/main/CHANGELOG.md) for version history and breaking changes. ## 📄 License [AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.html) - This project is licensed under the GNU Affero General Public License v3.0. If you need to use this in a proprietary application without open-sourcing your code, please contact the maintainer for commercial licensing options.