@itrocks/build
Version:
Permanently apply javascript modifiers to your dynamic DOM
89 lines (88 loc) • 3.12 kB
JavaScript
import { SortedArrayBy } from '../sorted-array/sorted-array.js';
export const ALWAYS = 'always';
export const CALL = 'call';
class Callback {
event;
selectors;
callback;
priority;
args;
constructor(event, selectors, callback, priority, args) {
this.event = event;
this.selectors = selectors;
this.callback = callback;
this.priority = priority;
this.args = args;
}
applyInto(containerElement) {
for (const element of this.matchingElementsInto(containerElement)) {
if (this.event === CALL) {
this.callback(element, ...this.args);
}
else {
element.addEventListener(this.event, this.callback);
}
}
}
matchingElementsInto(element) {
const elements = new Set;
for (const selector of this.selectors) {
if ((selector[0] === ALWAYS[0]) && (selector === ALWAYS)) {
elements.add(element);
continue;
}
if (element.matches(selector)) {
elements.add(element);
}
element.querySelectorAll(selector).forEach(element => elements.add(element));
}
return elements;
}
}
const callbacks = new SortedArrayBy('priority');
const chainedSelectors = (selector) => {
const selectors = [''];
for (const sourcePart of selector) {
const addParts = sourcePart.split(',');
const addPart = addParts.pop();
const length = selectors.length;
for (const oldPart of selectors) {
for (const addPart of addParts) {
selectors.push(oldPart + ' ' + addPart);
}
}
for (let index = 0; index < length; index++) {
selectors[index] += ' ' + addPart;
}
}
return selectors;
};
export function build(event, type, callback) {
if (typeof event === 'function') {
event = { callback: event };
}
else if ((typeof event === 'string') || Array.isArray(event)) {
event = (callback ? { callback, type, selector: event } : { callback: type, selector: event });
}
event.args ??= [];
event.priority ??= 1000;
event.selector ??= ALWAYS;
event.type ??= CALL;
event.priority = (event.priority * 1000000) + callbacks.length;
event.selector = (typeof event.selector === 'string') ? [event.selector] : chainedSelectors(event.selector);
const buildCallback = new Callback(event.type, event.selector, event.callback, event.priority ?? 0, event.args);
buildCallback.applyInto(document.body);
callbacks.push(buildCallback);
}
const observer = new MutationObserver(mutations => {
for (const mutation of mutations) {
mutation.addedNodes.forEach(addedNode => {
if ((addedNode instanceof HTMLElement) && addedNode.closest('html')) {
for (const callback of callbacks) {
callback.applyInto(addedNode);
}
}
});
}
});
observer.observe(document.body, { childList: true, subtree: true });