uconnect
Version:
An essential connected/disconnected DOM helper
95 lines (81 loc) • 3.18 kB
JavaScript
;
/**
* @typedef {Object} Handler an object that handle events.
* @property {(event: Event) => void} connected an optional method triggered when node is connected.
* @property {(event: Event) => void} disconnected an optional method triggered when node is disconnected.
*/
/**
* @typedef {Object} UConnect an utility to connect or disconnect nodes to observe.
* @property {(node: Node, handler?: Handler) => void} connect a method to start observing a generic Node.
* @property {(node: Node) => void} disconnect a method to stop observing a generic Node.
* @property {() => void} kill a method to kill/disconnect the MutationObserver.
*/
/**
* Attach a MutationObserver to a generic node and returns a UConnect instance.
* @param {Node} root a DOM node to observe for mutations.
* @param {string} parse the kind of nodes to parse: children, by default, or childNodes.
* @param {Event} CE an Event/CustomEvent constructor (polyfilled in SSR).
* @param {MutationObserver} MO a MutationObserver constructor (polyfilled in SSR).
* @returns {UConnect} an utility to connect or disconnect nodes to observe.
*/
const observe = (root, parse, CE, MO) => {
const observed = new WeakMap;
// these two should be WeakSet but IE11 happens
const wmin = new WeakMap;
const wmout = new WeakMap;
const has = node => observed.has(node);
const disconnect = node => {
if (has(node)) {
listener(node, node.removeEventListener, observed.get(node));
observed.delete(node);
}
};
const connect = (node, handler) => {
disconnect(node);
if (!(handler || (handler = {})).handleEvent)
handler.handleEvent = handleEvent;
listener(node, node.addEventListener, handler);
observed.set(node, handler);
};
const listener = (node, method, handler) => {
method.call(node, 'disconnected', handler);
method.call(node, 'connected', handler);
};
const notifyObserved = (nodes, type, wmin, wmout) => {
for (let {length} = nodes, i = 0; i < length; i++)
notifyTarget(nodes[i], type, wmin, wmout);
};
const notifyTarget = (node, type, wmin, wmout) => {
if (has(node) && !wmin.has(node)) {
wmout.delete(node);
wmin.set(node, 0);
node.dispatchEvent(new (CE || CustomEvent)(type));
}
notifyObserved(node[parse || 'children'] || [], type, wmin, wmout);
};
const mo = new (MO || MutationObserver)(nodes => {
for (let {length} = nodes, i = 0; i < length; i++) {
const {removedNodes, addedNodes} = nodes[i];
notifyObserved(removedNodes, 'disconnected', wmout, wmin);
notifyObserved(addedNodes, 'connected', wmin, wmout);
}
});
mo.add = add;
mo.add(root || document);
const {attachShadow} = Element.prototype;
if (attachShadow)
Element.prototype.attachShadow = function (init) {
const sd = attachShadow.call(this, init);
mo.add(sd);
return sd;
};
return {has, connect, disconnect, kill() { mo.disconnect(); }};
};
exports.observe = observe;
function add(node) {
this.observe(node, {subtree: true, childList: true});
}
function handleEvent(event) {
if (event.type in this)
this[event.type](event);
}