Skip to content

Commit 6b03381

Browse files
committed
feat: add auto-enable and -disable options for SelectorObserver
1 parent 275020c commit 6b03381

File tree

2 files changed

+35
-8
lines changed

2 files changed

+35
-8
lines changed

README.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,12 @@ If a selector string is passed instead, it will be used to find the element.
148148
If you want to observe the entire document, you can pass `document.body`
149149

150150
The `options` parameter is optional and will be passed to the MutationObserver that is used internally.
151-
The default options are `{ childList: true, subtree: true }` - you may see the [MutationObserver.observe() documentation](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe#options) for more information and a list of options.
152-
For example, if you want to trigger the listeners when certain attributes change, pass `{ attributes: true, attributeFilter: ["class", "data-my-attribute"] }`
151+
The MutationObserver options present by default are `{ childList: true, subtree: true }` - you may see the [MutationObserver.observe() documentation](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe#options) for more information and a list of options.
152+
For example, if you want to trigger the listeners when certain attributes change, pass `{ attributeFilter: ["class", "data-my-attribute"] }`
153+
153154
Additionally, there are the following extra options:
155+
- `disableOnNoListeners` - whether to disable the SelectorObserver when there are no listeners left (defaults to false)
156+
- `enableOnAddListener` - whether to enable the SelectorObserver when a new listener is added (defaults to true)
154157
- `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)
155158

156159
⚠️ 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.

lib/SelectorObserver.ts

+30-6
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,30 @@ type SelectorOptionsCommon = {
2222
debounce?: number;
2323
};
2424

25-
export type SelectorObserverOptions = MutationObserverInit & {
25+
export type SelectorObserverOptions = {
2626
/** If set, applies this debounce in milliseconds to all listeners that don't have their own debounce set */
2727
defaultDebounce?: number;
28+
/** Whether to disable the observer when no listeners are present - default is true */
29+
disableOnNoListeners?: boolean;
30+
/** Whether to ensure the observer is enabled when a new listener is added - default is true */
31+
enableOnAddListener?: boolean;
2832
};
2933

3034
/** Observes the children of the given element for changes */
3135
export class SelectorObserver {
3236
private enabled = false;
3337
private baseElement: Element | string;
3438
private observer: MutationObserver;
35-
private observerOptions: SelectorObserverOptions;
39+
private observerOptions: MutationObserverInit;
40+
private customOptions: SelectorObserverOptions;
3641
private listenerMap: Map<string, SelectorListenerOptions[]>;
3742

3843
/**
3944
* Creates a new SelectorObserver that will observe the children of the given base element selector for changes (only creation and deletion of elements by default)
4045
* @param baseElementSelector The selector of the element to observe
4146
* @param options Fine-tune what triggers the MutationObserver's checking function - `subtree` and `childList` are set to true by default
4247
*/
43-
constructor(baseElementSelector: string, options?: SelectorObserverOptions)
48+
constructor(baseElementSelector: string, options?: SelectorObserverOptions & MutationObserverInit)
4449
/**
4550
* Creates a new SelectorObserver that will observe the children of the given base element for changes (only creation and deletion of elements by default)
4651
* @param baseElement The element to observe
@@ -53,10 +58,24 @@ export class SelectorObserver {
5358
this.listenerMap = new Map<string, SelectorListenerOptions[]>();
5459
// if the arrow func isn't there, `this` will be undefined in the callback
5560
this.observer = new MutationObserver(() => this.checkAllSelectors());
61+
62+
const {
63+
defaultDebounce,
64+
disableOnNoListeners,
65+
enableOnAddListener,
66+
...observerOptions
67+
} = options;
68+
5669
this.observerOptions = {
5770
childList: true,
5871
subtree: true,
59-
...options,
72+
...observerOptions,
73+
};
74+
75+
this.customOptions = {
76+
defaultDebounce: defaultDebounce ?? 0,
77+
disableOnNoListeners: disableOnNoListeners ?? false,
78+
enableOnAddListener: enableOnAddListener ?? true,
6079
};
6180
}
6281

@@ -97,6 +116,8 @@ export class SelectorObserver {
97116
}
98117
if(this.listenerMap.get(selector)?.length === 0)
99118
this.listenerMap.delete(selector);
119+
if(this.listenerMap.size === 0 && this.customOptions.disableOnNoListeners)
120+
this.disable();
100121
}
101122
}
102123

@@ -119,17 +140,20 @@ export class SelectorObserver {
119140
*/
120141
public addListener<TElem extends Element = HTMLElement>(selector: string, options: SelectorListenerOptions<TElem>) {
121142
options = { all: false, continuous: false, debounce: 0, ...options };
122-
if((options.debounce && options.debounce > 0) || (this.observerOptions.defaultDebounce && this.observerOptions.defaultDebounce > 0)) {
143+
if((options.debounce && options.debounce > 0) || (this.customOptions.defaultDebounce && this.customOptions.defaultDebounce > 0)) {
123144
options.listener = this.debounce(
124145
options.listener as ((arg: NodeListOf<Element> | Element) => void),
125-
(options.debounce || this.observerOptions.defaultDebounce)!,
146+
(options.debounce || this.customOptions.defaultDebounce)!,
126147
);
127148
}
128149
if(this.listenerMap.has(selector))
129150
this.listenerMap.get(selector)!.push(options as SelectorListenerOptions<Element>);
130151
else
131152
this.listenerMap.set(selector, [options as SelectorListenerOptions<Element>]);
132153

154+
if(this.enabled === false && this.customOptions.enableOnAddListener)
155+
this.enable();
156+
133157
this.checkSelector(selector, [options as SelectorListenerOptions<Element>]);
134158
}
135159

0 commit comments

Comments
 (0)