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
Markdown
"dom-track logo" src="https://gitlab.com/horlacher/dom-track/-/raw/main/logo.png" width="128" />
[[_TOC_]]
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 [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";
```
```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`
Watches for DOM changes inside elements matching the selector.
(Same return types as `.seen()`)
**Callback signature:** `(element: HTMLElement, container: HTMLElement) => void`
Watches for matching elements being removed.
Returns `TrackHandle`.
**Callback signature:** `(element: HTMLElement, container: HTMLElement) => void`
Watches for a specific element to be removed.
Returns `TrackHandle`.
**Callback signature:** `(element: HTMLElement, container: HTMLElement) => void`
Pass a callback to get a TrackHandle:
```ts
tracker.seen('.item', (el, container) => { ... });
```
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
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.
```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).
**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.
<img alt=