UNPKG

@evolv/mutate

Version:

A library of standard DOM mutations by Evolv AI.

1,489 lines (1,454 loc) 172 kB
/*! * Evolv Mutate v2.4.1 <https://github.com/evolv-ai/mutate> * * Copyright 2021-2025 Evolv Technology Solutions * * All rights reserved. */ const KEY_SEPARATOR = '\x90\x90'; /** * Generates a scoped name by combining a scope string and a name. * * @param name - The name to scope. * @param scope - The scope to prepend to the name. Can be `undefined`. * @param toCollectorName - Whether to normalize the separator to `:`. `KEY_SEPARATOR` is used internally, a ':' separates the scoped collector key. * @return A scoped name */ const SCOPE = (name, scope, toCollectorName = false) => { const separator = toCollectorName ? ':' : KEY_SEPARATOR; return scope ? scope + separator + name : name; }; /** * Extracts the "name" part from a scoped string. * A scoped string is expected to follow the format `scope<separator>name`. * * @param scoped - The scoped string to extract the name from. * @return The name part of the scoped string, or the input string if no separator is found. */ SCOPE.getName = (scoped) => { return (scoped === null || scoped === void 0 ? void 0 : scoped.includes(KEY_SEPARATOR)) ? scoped.split(KEY_SEPARATOR)[1] : scoped; }; /** * Extracts the "scope" part from a scoped string. * A scoped string is expected to follow the format `scope<separator>name`. * * @param scoped - The scoped string to extract the scope from. * @return The scope part of the scoped string, or `undefined` if no separator is found. */ SCOPE.getScope = (scoped) => { return (scoped === null || scoped === void 0 ? void 0 : scoped.includes(KEY_SEPARATOR)) ? scoped.split(KEY_SEPARATOR)[0] : undefined; }; /** * Replaces the KEY_SEPARATOR with a `:` to resolve it to the scoped collector name. * * @param name - The name to normalize. * @return The normalized name, or `undefined` if the input is invalid. */ SCOPE.getCollectorName = (name) => { return name === null || name === void 0 ? void 0 : name.replace(KEY_SEPARATOR, ':'); }; /** * Default interval time in ms */ const INTERVAL = 50; var EventType; (function (EventType) { EventType[EventType["ADDED"] = 0] = "ADDED"; EventType[EventType["REMOVED"] = 1] = "REMOVED"; EventType[EventType["MODIFIED"] = 2] = "MODIFIED"; })(EventType || (EventType = {})); var CollectorState; (function (CollectorState) { CollectorState[CollectorState["INITIALIZED"] = 0] = "INITIALIZED"; CollectorState[CollectorState["VALIDATED"] = 1] = "VALIDATED"; CollectorState[CollectorState["FAILED"] = 2] = "FAILED"; CollectorState[CollectorState["PAUSED"] = 3] = "PAUSED"; CollectorState[CollectorState["UNPAUSED"] = 4] = "UNPAUSED"; CollectorState[CollectorState["DESTROYED"] = 5] = "DESTROYED"; })(CollectorState || (CollectorState = {})); class EvolvError extends Error { constructor(message) { super('Evolv: ' + message); } } class CollectorNotFoundError extends EvolvError { constructor(collectorName) { super(`Collector '${collectorName}' not found.`); } } class CollectorDestroyedError extends EvolvError { constructor() { super('This collector has been destroyed'); } } var LogLevel; (function (LogLevel) { LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG"; LogLevel[LogLevel["INFO"] = 1] = "INFO"; LogLevel[LogLevel["WARNING"] = 2] = "WARNING"; LogLevel[LogLevel["ERROR"] = 3] = "ERROR"; })(LogLevel || (LogLevel = {})); const LOGGERS = { [LogLevel.DEBUG]: console.debug, [LogLevel.INFO]: console.info, [LogLevel.WARNING]: console.warn, [LogLevel.ERROR]: console.error, }; /*** * Outputs messages to the console. */ class MessageLog { get logLevel() { return this._logLevel; } constructor(scope = null, logLevel = LogLevel.WARNING) { var _a, _b; this._scope = null; this._errorLimit = 50; this._errorCount = 0; this._scope = scope; this._logLevel = ((_b = (_a = window.evolv) === null || _a === void 0 ? void 0 : _a.config) === null || _b === void 0 ? void 0 : _b.logLevel) || logLevel; } qualifyMessage(message) { return `[Evolv AI > ${this._scope}] ${message}`; } debug(message) { this.log(LogLevel.DEBUG, message); } info(message) { this.log(LogLevel.INFO, message); } warn(message) { this.log(LogLevel.WARNING, message); } error(message) { if (this._errorCount < this._errorLimit) { this._errorCount++; this.log(LogLevel.ERROR, message); } } log(logLevel, message) { if (logLevel < this._logLevel) { return; } const qualifiedMessage = this.qualifyMessage(message); LOGGERS[logLevel](qualifiedMessage); } } const MUTATE_SOURCE_ATTR = 'mutatorSrc'; class AbstractAction { pauseEvaluation() { this._evaluationPaused = true; } unpauseEvaluation() { this._evaluationPaused = false; } get subject() { return this._subject; } get state() { return this._state; } get branches() { return this._branches; } get factory() { return this._factory; } constructor(state, subject, factory) { this._branches = []; this._applying = false; this._evaluationPaused = false; this._state = state; this._subject = subject; this._factory = factory; } _evaluate(event) { if (!this._applying) { this._applying = true; if (!this._evaluationPaused) { // @ts-expect-error We are indexing an object with a passed in string to retrieve a function. this._branches.forEach((a) => a[event]()); } this._applying = false; } } removed() { this._evaluate('removed'); } modified() { this._evaluate('modified'); } revert() { this._evaluate('revert'); } connect(action) { this._branches.push(action); } disconnect(action) { this._branches.push(action); } addMutatorIdsToTree(element, includeSelf = false) { if (includeSelf) { this.addToMutateSrcArray(element, this.state.mutator.id); } for (const child of element.children) { this.addToMutateSrcArray(child, this.state.mutator.id); this.addMutatorIdsToTree(child); } } removeMutatorIdsFromTree(element, includeSelf = false) { if (includeSelf) { this.elementRemoveMutateSrc(element, this.state.mutator.id); } for (const child of element.children) { this.removeMutatorIdsFromTree(child); this.elementRemoveMutateSrc(child, this.state.mutator.id); } } addToMutateSrcArray(element, mutateSrc) { const mutateSrcs = getMutateSrcArray(element); if (elementHasMutateSrc(element, mutateSrc)) { return; } mutateSrcs.push(mutateSrc); element.dataset[MUTATE_SOURCE_ATTR] = mutateSrcs.join(','); } elementRemoveMutateSrc(element, mutateSrc) { const mutateSrcs = getMutateSrcArray(element); const index = mutateSrcs.indexOf(mutateSrc); if (index > -1) { mutateSrcs.splice(index, 1); } if (mutateSrcs.length) { element.dataset[MUTATE_SOURCE_ATTR] = mutateSrcs.join(','); } else { delete element.dataset[MUTATE_SOURCE_ATTR]; } } } const getMutateSrcArray = (element) => { var _a; const strMutateSrc = (_a = element.dataset[MUTATE_SOURCE_ATTR]) !== null && _a !== void 0 ? _a : ''; if (strMutateSrc) { return strMutateSrc.split(','); } return []; }; const elementHasMutateSrc = (element, mutateSrc) => { const mutateSrcs = getMutateSrcArray(element); return mutateSrcs.includes(mutateSrc); }; class AbstractActionFactory { constructor() { this._instances = new Map(); this._once = false; } get instances() { return this._instances; } get once() { return this._once; } set once(value) { this._once = value; } get next() { return this._next; } set next(value) { if (!value) { return; } this._instances.forEach((branch) => { value.attach(branch.state, branch.subject, branch); }); this._next = value; } attach(state, subject, branch, attachNext = true) { let inst = this.instances.get(subject); if (inst === undefined) { inst = this.createInstance(state, subject, branch); branch.connect(inst); this.instances.set(subject, inst); } else { inst.modified(); } if (!(attachNext && inst && this.next)) { return inst; } this.next.attach(inst.state, inst.subject, inst); return inst; } } /*** * A type represents the state within an {@link Effect} tree. */ class State { get properties() { return this._properties; } get mutator() { return this._mutator; } get collector() { return this._collector; } constructor(collector, mutator, properties) { this._collector = collector; this._mutator = mutator; this._properties = properties || new Map(); } } class AbstractBinding extends AbstractAction { get stateProperty() { return this._stateProperty; } get state() { return this._localState; } get localProperties() { return this._localProperties; } get transform() { return this._transform || ((v) => v); } constructor(state, stateProperty, subject, transform, factory) { super(state, subject, factory); this._stateProperty = stateProperty; this._transform = transform; this._localProperties = new Map(state.properties); this._localState = new State(state.collector, state.mutator, this._localProperties); } mutated() { const transformedValue = this.transform ? this.transform(this.value) : this.value; if (transformedValue === this._previousValue) { return; } this._previousValue = transformedValue; this.localProperties.set(this.stateProperty, transformedValue); } } class AbstractBindingFactory extends AbstractActionFactory { get stateProperty() { return this._stateProperty; } get transform() { return this._transform; } constructor(stateProperty, transform) { super(); this._stateProperty = stateProperty; this._transform = transform; } } class AttributeBinding extends AbstractBinding { constructor(state, stateProperty, subject, attrName, transform, factory) { super(state, stateProperty, subject, transform, factory); this._attrName = attrName; this.mutated(); } get value() { return this.subject.getAttribute(this._attrName); } modified() { var _a; if (!((_a = this.factory) === null || _a === void 0 ? void 0 : _a.once)) { this.mutated(); } super.modified(); } } class AttributeBindingFactory extends AbstractBindingFactory { constructor(stateProperty, attrName, transform) { super(stateProperty, transform); this._attrName = attrName; } createInstance(state, subject) { return new AttributeBinding(state, this.stateProperty, subject, this._attrName, this.transform, this); } } class ContentBinding extends AbstractBinding { constructor(state, stateProperty, subject, transform, factory) { super(state, stateProperty, subject, transform, factory); this.mutated(); } get value() { return this.subject.textContent; } modified() { var _a; if (!((_a = this.factory) === null || _a === void 0 ? void 0 : _a.once)) { this.mutated(); } super.modified(); } } class ContentBindingFactory extends AbstractBindingFactory { createInstance(state, subject) { return new ContentBinding(state, this.stateProperty, subject, this.transform, this); } } const CONTEXT_INITIALIZED = 'context.initialized'; const CONTEXT_VALUE_REMOVED = 'context.value.removed'; const CONTEXT_VALUE_ADDED = 'context.value.added'; const CONTEXT_VALUE_CHANGED = 'context.value.changed'; const CONTEXT_DESTROYED = 'context.destroyed'; class ContextBinding extends AbstractBinding { get value() { return this._currentValue; } constructor(state, stateProperty, propertyName, subject, transform, factory) { var _a; super(state, stateProperty, subject, transform, factory); this._propertyName = propertyName; if (!((_a = window.evolv) === null || _a === void 0 ? void 0 : _a.context)) { throw new EvolvError('Mutate: Evolv Context not found.'); } const handler = () => { const before = this._currentValue; this._currentValue = window.evolv.context.get(this._propertyName); if (this._currentValue !== before) { this.mutated(); } }; window.evolv.client.on(CONTEXT_INITIALIZED, handler); window.evolv.client.on(CONTEXT_VALUE_REMOVED, handler); window.evolv.client.on(CONTEXT_VALUE_ADDED, handler); window.evolv.client.on(CONTEXT_VALUE_CHANGED, handler); window.evolv.client.on(CONTEXT_DESTROYED, handler); } } class ContextBindingFactory extends AbstractBindingFactory { constructor(stateProperty, propertyName, transform) { super(stateProperty, transform); this._propertyName = propertyName; } createInstance(state, subject) { return new ContextBinding(state, this.stateProperty, this._propertyName, subject, this.transform, this); } } class ComputedBinding extends AbstractBinding { get value() { return this._currentValue; } constructor(computable, state, stateProperty, subject, factory) { super(state, stateProperty, subject, undefined, factory); this.computable = computable; this._currentValue = computable(state, subject, undefined); this.mutated(); } modified() { var _a; if (!((_a = this.factory) === null || _a === void 0 ? void 0 : _a.once)) { this._currentValue = this.computable(this.state, this.subject, this._currentValue); this.mutated(); } super.modified(); } } class ComputedBindingFactory extends AbstractBindingFactory { constructor(stateProperty, computable) { super(stateProperty); this.computable = computable; } createInstance(state, subject) { return new ComputedBinding(this.computable, state, this.stateProperty, subject, this); } } class AbstractEffect extends AbstractAction { computeValue(subject, value, name) { return typeof value === 'function' ? value(this.state, this.subject, subject.getAttribute(name)) : value; } } class AbstractEffectFactory extends AbstractActionFactory { } class AttributeModifiedEffect extends AbstractEffect { constructor(state, subject, name, value, factory) { super(state, subject, factory); this._name = name; this._value = value; // @ts-ignore if (subject instanceof Element && subject.getAttribute(this._name) !== value) { // @ts-ignore this._previousValue = subject.getAttribute(this._name); } this._set(this.subject, this._value); } _set(subject, value) { const actualValue = this.computeValue(subject, value, this._name); if (actualValue === undefined || actualValue === null) { subject.removeAttribute(this._name); return; } // @ts-ignore if (subject instanceof Element && subject.getAttribute(this._name) !== value) { // @ts-ignore subject.setAttribute(this._name, actualValue); } } modified() { var _a; if (!((_a = this.factory) === null || _a === void 0 ? void 0 : _a.once)) { this._set(this.subject, this._value); } super.modified(); } revert() { var _a; // Revert the children before the parent in case some downstream changes are dependent on this change. super.revert(); this._set(this.subject, (_a = this._previousValue) !== null && _a !== void 0 ? _a : ''); } } class AttributeModifiedEffectFactory extends AbstractEffectFactory { constructor(name, value) { super(); this._name = name; this._value = value; } createInstance(state, subject) { return new AttributeModifiedEffect(state, subject, this._name, this._value, this); } } class ClassModifiedEffect extends AbstractEffect { constructor(state, subject, name, value, factory) { super(state, subject, factory); this._previousValue = null; this._name = name; this._value = value; this._previousValue = subject.classList.contains(this._name); this._set(this.subject, this._value); } _set(subject, value) { const actualValue = this.computeValue(subject, value, this._name); if (actualValue && !subject.classList.contains(this._name)) { subject.classList.add(this._name); } else if (!actualValue && subject.classList.contains(this._name)) { subject.classList.remove(this._name); } } modified() { var _a; if (!((_a = this.factory) === null || _a === void 0 ? void 0 : _a.once)) { this._set(this.subject, this._value); } super.modified(); } revert() { // Revert the children before the parent in case some downstream changes are dependent on this change. super.revert(); this._set(this.subject, this._previousValue); } } class ClassModifiedEffectFactory extends AbstractEffectFactory { constructor(name, value) { super(); this._name = name; this._value = value; } createInstance(state, subject) { return new ClassModifiedEffect(state, subject, this._name, this._value, this); } } class CustomMutation extends AbstractEffect { constructor(state, subject, initialize, modify, revert, remove, factory) { super(state, subject, factory); this._initialize = initialize; this._modify = modify; this._revert = revert; this._remove = remove; if (this._initialize) { this._initialize(state, subject); } } modified() { var _a; if (this._modify && !((_a = this.factory) === null || _a === void 0 ? void 0 : _a.once)) { this._modify(this.state, this.subject); } super.modified(); } revert() { // Revert the children before the parent in case some downstream changes are dependent on this change. super.revert(); if (this._revert) { this._revert(this.state, this.subject); } } removed() { if (this._remove) { this._remove(this.state, this.subject); } super.removed(); } } class CustomMutationFactory extends AbstractEffectFactory { constructor(initialize, modify, revert, remove) { super(); this._initialize = initialize; this._modify = modify; this._revert = revert; this._remove = remove; } createInstance(state, subject) { return new CustomMutation(state, subject, this._initialize, this._modify, this._revert, this._remove, this); } } function create(html) { const template = document.createElement('template'); template.innerHTML = html; return Array.from(template.content.childNodes); } /*** * Compares two {@link Array}s of primitives to determine if they are equal * @param array1 The first {@link Array} to compare * @param array1 The second {@link Array} to compare */ const areEqual$1 = (array1, array2) => { if (array1.length !== array2.length) { return false; } for (let i = 0; i < array1.length; i++) { if (array1[i] !== array2[i]) { return false; } } return true; }; /*** * Compare two {@link Map}s containing primitives or {@link Array}s * * @param map1 The first {@link Map} to compare * @param map2 The second {@link Map} to compare */ const areEqual = (map1, map2) => { if (map1.size !== map2.size) { return false; } for (const [key, value1] of map1) { const value2 = map2.get(key); if (Array.isArray(value1) && Array.isArray(value2)) { if (!areEqual$1(value1, value2)) { return false; } } else if (value2 !== value1 || (value2 === undefined && !map2.has(key))) { return false; } } return true; }; class InjectNodesEffect extends AbstractEffect { constructor(state, subject, injectable, inserter, context, factory) { super(state, subject, factory); this._injectable = injectable; this._reverse = context.get('reverse'); this._inserter = inserter; this._nodesInjected = []; this._previousProperties = new Map(state.properties); this.injectNodes(); } injectNodes(nodes = this.nodes) { nodes.forEach((node) => { this._nodesInjected = [ ...this._nodesInjected, ...this._inserter(this.subject, node), ]; }); } removeInjectedNodes() { this._nodesInjected.forEach((node) => { if (node instanceof Text || node instanceof Element) { node.remove(); } }); this._nodesInjected = []; } get nodes() { let nodes; function handleInjectableType(injectable) { if (typeof injectable === 'string') { return create(injectable); } else if (injectable instanceof Node) { return [injectable]; } else if (Array.isArray(injectable) || injectable instanceof NodeList || injectable instanceof HTMLCollection) { return [...injectable]; } else if (injectable === null || injectable === undefined) { return []; } try { return create(String(injectable)); } catch (_a) { return []; } } if (typeof this._injectable === 'function') { nodes = handleInjectableType(this._injectable(this.state, this.subject)); } else { nodes = handleInjectableType(this._injectable); } return this._reverse ? nodes.reverse() : nodes; } modified() { var _a; if (!((_a = this.factory) === null || _a === void 0 ? void 0 : _a.once) && (this._nodesInjected.length === 0 || this._nodesInjected.some((injectedNode) => !injectedNode.isConnected) || (this._injectable instanceof Function && !areEqual(this._previousProperties, this.state.properties)))) { this.removeInjectedNodes(); this.injectNodes(); } this._previousProperties = new Map(this.state.properties); super.modified(); } revert() { // Revert the children before the parent in case some downstream changes are dependent on this change. super.revert(); this.removeInjectedNodes(); } } class InjectNodesEffectFactory extends AbstractEffectFactory { constructor(injectable, inserter, context) { super(); this._injectable = injectable; this._inserter = inserter; this._context = context; } createInstance(state, subject) { return new InjectNodesEffect(state, subject, this._injectable, this._inserter, this._context, this); } } class AbstractNodeEffect extends AbstractEffect { get node() { return this._node; } constructor(state, subject, node) { super(state, subject); this._node = node; } append() { var _a; this.subject.appendChild((_a = this.node) !== null && _a !== void 0 ? _a : this.subject); } remove() { var _a; (_a = this.node) === null || _a === void 0 ? void 0 : _a.remove(); } unhide(toUnhide, originalDisplayValue) { toUnhide === null || toUnhide === void 0 ? void 0 : toUnhide.style.setProperty('display', originalDisplayValue !== null && originalDisplayValue !== void 0 ? originalDisplayValue : ''); } hide(toHide) { toHide === null || toHide === void 0 ? void 0 : toHide.style.setProperty('display', 'none'); } } class AbstractNodeEffectFactory extends AbstractEffectFactory { } class InsertNodeEffect extends AbstractNodeEffect { constructor(state, subject, node) { super(state, subject, node); if (this.node instanceof Element) { this.addMutatorIdsToTree(node, true); } this.append(); } modified() { var _a; if (!((_a = this.node) === null || _a === void 0 ? void 0 : _a.isConnected)) { this.addMutatorIdsToTree(this.node, true); this.append(); } super.modified(); } revert() { var _a; // Revert the children before the parent in case some downstream changes are dependent on this change. super.revert(); if (this.subject === ((_a = this.node) === null || _a === void 0 ? void 0 : _a.parentNode)) { this.remove(); } } } class InsertNodeEffectFactory extends AbstractNodeEffectFactory { constructor(node, clone = true) { super(); this._node = node; this._clone = clone; } createInstance(state, subject) { if (elementHasMutateSrc(subject, state.mutator.id)) { throw new Error('Evolv AI: A previous mutation from this mutator has been identified by the Collector.' + ' This indicates it will be overwritten or may cause an infinite loop.' + ' Please make your selector more specific.'); } return new InsertNodeEffect(state, subject, this._clone ? this._node.cloneNode(true) : this._node); } } class RemoveNodeEffect extends AbstractNodeEffect { constructor(state, subject, node) { super(state, subject, node); this._toRemove = node !== null && node !== void 0 ? node : subject; this._originalDisplayValue = this._toRemove.style.display; this.hide(this._toRemove); } modified() { this.hide(this._toRemove); super.modified(); } revert() { // Revert the children before the parent in case some downstream changes are dependent on this change. super.revert(); this.unhide(this._toRemove, this._originalDisplayValue); } } class RemoveNodeEffectFactory extends AbstractNodeEffectFactory { constructor(node) { super(); this._node = node; } createInstance(state, subject) { return new RemoveNodeEffect(state, subject, this._node); } } class ReplaceNodeEffect extends AbstractNodeEffect { constructor(state, subject, node) { super(state, subject, node); this.append(); } modified() { this.append(); super.modified(); } revert() { // Revert the children before the parent in case some downstream changes are dependent on this change. super.revert(); this.remove(); } } class ReplaceNodeEffectFactory extends AbstractNodeEffectFactory { constructor(node) { super(); this._node = node; } createInstance(state, subject) { return new ReplaceNodeEffect(state, subject, this._node); } } class StyleModifiedEffect extends AbstractEffect { constructor(state, subject, name, value, factory) { super(state, subject, factory); this.TRANSITION_DEFINITION = 'evolvOriginalTransition'; this.TRANSITION_DEFINITION_INLINE = 'evolvOriginalTransitionInline'; this._previousValue = null; this._name = name; this._value = value; this._camelCaseName = name.replace(/-([a-z])/g, (g) => g[1].toUpperCase()); this._set(this.subject, this._value); } _set(subject, value) { const actualValue = this.computeValue(subject, value, this._name); if (subject instanceof HTMLElement && (this._computedValueFor !== actualValue || subject.style[this._camelCaseName] !== this._computedValue)) { this.pauseTransitions(this.subject); this._previousValue = subject.style[this._camelCaseName]; subject.style[this._camelCaseName] = actualValue; this._computedValueFor = actualValue; this._computedValue = subject.style[this._camelCaseName]; setTimeout(() => { this.resumeTransitions(this.subject); }, 0); } } modified() { var _a; if (!((_a = this.factory) === null || _a === void 0 ? void 0 : _a.once)) { this._set(this.subject, this._value); } super.modified(); } revert() { // Revert the children before the parent in case some downstream changes are dependent on this change. super.revert(); if (this.subject instanceof HTMLElement) { this.pauseTransitions(this.subject); this.subject.style[this._camelCaseName] = this._previousValue; setTimeout(() => { this.resumeTransitions(this.subject); }, 0); } } pauseTransitions(subject) { if (subject instanceof HTMLElement) { this.setAndStoreIsTransitionInline(subject); this.setAndStoreTransitionDefinition(subject); if (this.getTransitionDefinition(subject)) { subject.style.transition = 'none'; } } } resumeTransitions(subject) { if (subject instanceof HTMLElement && this.getTransitionDefinition(subject)) { if (this.isTransitionInline(subject)) { subject.style.transition = this.getTransitionDefinition(subject); } else { subject.style.transition = ''; } this.clearStoredTransitionData(subject); } } // We need to know if transition was originally set inline or by style to correctly revert. // Use a data attribute to store this information to be visible across multiple StyleModifiedEffects setAndStoreIsTransitionInline(subject) { const unset = subject.dataset[this.TRANSITION_DEFINITION_INLINE] === undefined; if (unset) { subject.dataset[this.TRANSITION_DEFINITION_INLINE] = (!!subject.style .transition).toString(); } } isTransitionInline(subject) { return subject.dataset[this.TRANSITION_DEFINITION_INLINE] === 'true'; } setAndStoreTransitionDefinition(subject) { const storedTransition = this.getTransitionDefinition(subject); if (!storedTransition) { subject.dataset[this.TRANSITION_DEFINITION] = getComputedStyle(subject).transition; } } getTransitionDefinition(subject) { return subject.dataset[this.TRANSITION_DEFINITION] || ''; } clearStoredTransitionData(subject) { delete subject.dataset[this.TRANSITION_DEFINITION]; delete subject.dataset[this.TRANSITION_DEFINITION_INLINE]; } } class StyleModifiedEffectFactory extends AbstractEffectFactory { constructor(name, value) { super(); this._name = name; this._value = value; } createInstance(state, subject) { return new StyleModifiedEffect(state, subject, this._name, this._value, this); } } class TextModifiedEffect extends AbstractEffect { get text() { const handleTextType = (text) => { if (typeof text === 'string') { return text; } else if (text === undefined || text === null) { return ''; } else { try { return String(text); } catch (_a) { return ''; } } }; return this._text instanceof Function ? handleTextType(this._text(this.state, this.subject, this._originalText)) : handleTextType(this._text); } constructor(state, subject, text, factory) { super(state, subject, factory); this._text = text; this._originalText = subject.textContent; this._set(this.text); } _set(value) { const element = this.subject; if (element.textContent !== value) { element.textContent = value; } } modified() { var _a; if (!((_a = this.factory) === null || _a === void 0 ? void 0 : _a.once)) { this._set(this.text); } super.modified(); } revert() { super.revert(); if (this._originalText !== undefined) { this._set(this._originalText); } } } class TextModifiedEffectFactory extends AbstractEffectFactory { constructor(text) { super(); this._text = text; } createInstance(state, subject) { return new TextModifiedEffect(state, subject, this._text, this); } } class HTMLModifiedEffect extends AbstractEffect { get html() { return typeof this._html === 'function' ? this._html(this.state, this.subject, this._previousHTML) : this._html || ''; } constructor(state, subject, html, factory) { super(state, subject, factory); this._html = html; this._set(this.html); } _set(value, resetPreviousHTML) { var _a, _b; const element = this.subject; // Some elements (for example <input>) if you set innerHTML still return empty string for innerHTML property. // We don't want to set innerHTML for such elements, because it leads us to an infinite loop. const fakeElement = document.createElement(element.tagName); fakeElement.innerHTML = 'test'; if (fakeElement.innerHTML !== 'test' || this._equalExcludingMutatorIds(element, value)) { // replace the mutator ids when we are calling with the same value return; } if (resetPreviousHTML) { this._previousHTML = undefined; } else { this._previousHTML = (_b = (_a = this._previousHTML) !== null && _a !== void 0 ? _a : element.innerHTML) !== null && _b !== void 0 ? _b : ''; } element.innerHTML = value || ''; this.addMutatorIdsToTree(element); } // Exposed for testing _equalExcludingMutatorIds(element, value) { const normalizingElement = document.createElement(element.tagName); normalizingElement.innerHTML = value || ''; const elementCopy = element.cloneNode(true); this.removeMutatorIdsFromTree(elementCopy); return elementCopy.innerHTML === normalizingElement.innerHTML; } modified() { var _a; // after we reset _previousHTML, we don't want to update it anymore if (this._previousHTML === undefined) { return; } if (!((_a = this.factory) === null || _a === void 0 ? void 0 : _a.once)) { this._set(this.html); } super.modified(); } revert() { super.revert(); if (this._previousHTML !== undefined) { this._set(this._previousHTML, true); this.removeMutatorIdsFromTree(this.subject); } } } class HTMLModifiedEffectFactory extends AbstractEffectFactory { constructor(newHtml) { super(); if (newHtml instanceof Node) { const container = document.createElement('div'); if (newHtml) { container.appendChild(newHtml); } this._html = container.innerHTML; } else { this._html = newHtml; } } createInstance(state, subject) { if (elementHasMutateSrc(subject, state.mutator.id)) { throw new Error('Evolv AI: A previous mutation from this mutator has been identified by the Collector.' + ' This indicates it will be overwritten or may cause an infinite loop.' + ' Please make your selector more specific.'); } return new HTMLModifiedEffect(state, subject, this._html, this); } } class AddListenerEffect extends AbstractEffect { constructor(state, subject, event, listener) { super(state, subject); this._event = event; this._listener = listener; this._reverted = false; this.initialize(); } initialize() { // Initialize the children before the parent in case some downstream changes are dependent on this change. if (this._reverted) { return; } this._reverted = false; this.subject.addEventListener(this._event, this._listener); } revert() { // Revert the children before the parent in case some downstream changes are dependent on this change. super.revert(); if (this._reverted) { return; } this._reverted = true; this.subject.removeEventListener(this._event, this._listener); } } class AddListenerEffectFactory extends AbstractEffectFactory { constructor(event, listener) { super(); this._event = event; this._listener = listener; } createInstance(state, subject) { return new AddListenerEffect(state, subject, this._event, this._listener); } } class RemoveListenerEffect extends AbstractEffect { constructor(state, subject, event, listener) { super(state, subject); this._event = event; this._listener = listener; this._reverted = false; this.initialize(); } initialize() { // Initialize the children before the parent in case some downstream changes are dependent on this change. if (!this._reverted) { return; } this._reverted = false; this.subject.removeEventListener(this._event, this._listener); } revert() { // Revert the children before the parent in case some downstream changes are dependent on this change. super.revert(); if (this._reverted) { return; } this._reverted = true; this.subject.addEventListener(this._event, this._listener); } } class RemoveListenerEffectFactory extends AbstractEffectFactory { constructor(event, listener) { super(); this._event = event; this._listener = listener; } createInstance(state, subject) { return new RemoveListenerEffect(state, subject, this._event, this._listener); } } class AbstractMutation extends AbstractAction { get branch() { return this._effects[this._effects.length - 1]; } get effects() { return [...this._effects]; } constructor(state, subject, branch) { super(state, subject); this._effects = []; this._effects.push(branch); } _createEffectsForMapEntries(subject, type, map) { const iterableEntries = map instanceof Map ? map : Object.entries(map); for (const entry of iterableEntries) { const effect = new type(entry[0], entry[1]).attach(this.state, subject, this.branch); this._effects.push(effect); } } createAttributeEffect(subject, name, value) { const effect = new AttributeModifiedEffectFactory(name, value).attach(this.state, subject, this.branch); this._effects.push(effect); } createAttributeEffects(subject, attributes) { this._createEffectsForMapEntries(subject, AttributeModifiedEffectFactory, attributes); } createStyleModifiedEffects(subject, styles) { this._createEffectsForMapEntries(subject, StyleModifiedEffectFactory, styles); } createClassModifiedEffects(subject, classes) { this._createEffectsForMapEntries(subject, ClassModifiedEffectFactory, classes); } createTextModifiedEffect(subject, text) { const effect = new TextModifiedEffectFactory(text).attach(this.state, subject, this.branch); this._effects.push(effect); } createInsertNodeEffect(subject, node) { const effect = new InsertNodeEffectFactory(node).attach(this.state, subject, this.branch); this._effects.push(effect); } createRemoveNodeEffect(subject, node) { const effect = new RemoveNodeEffectFactory(node).attach(this.state, subject, this.branch); this._effects.push(effect); } createReplaceNodeEffect(subject, node) { const effect = new ReplaceNodeEffectFactory(node).attach(this.state, subject, this.branch); this._effects.push(effect); } } class AbstractMutationFactory extends AbstractActionFactory { } function closest(element, selector) { if (element.closest) { return element.closest(selector); } let el = element; do { if (Element.prototype.matches.call(el, selector)) { return el; } el = el.parentElement || el.parentNode; } while (el !== null && el.nodeType === 1); return null; } const now = () => new Date().getTime(); /*** * Throttle a function call to a maximum of once per `delay` milliseconds. * * @param func The function to throttle. * @param waitFor The minimum time between calls. */ const throttle = (func, waitFor) => { let timeout; let startTime = now() - waitFor; const resetStartTime = () => (startTime = now()); return (...args) => { if (waitFor <= 0) { return Promise.resolve(func(...args)); } return new Promise((resolve) => { const timeLeft = startTime + waitFor - now(); if (timeout) { clearTimeout(timeout); } if (startTime + waitFor <= now()) { resetStartTime(); resolve(func(...args)); } else { timeout = window.setTimeout(() => { resetStartTime(); resolve(func(...args)); }, timeLeft); } }); }; }; function isEqual(left, right) { if (left.size !== right.size) { return false; } for (const a of left) { if (!right.has(a)) { return false; } } return true; } var _a, _b, _c, _d; const LOG$2 = new MessageLog(`Abstract Collector`); const PARENT_OBSERVER_OPTIONS = { subtree: true, childList: true, }; const ELEMENT_OBSERVER_OPTIONS = { subtree: true, attributes: true, characterData: true, }; const COLLECTORS = new Map(); let PARENT_OBSERVED_ELEMENTS = []; let PARENT_MUTATION_RECORD_QUEUE = []; let ELEMENT_MUTATION_RECORD_QUEUE = []; const COLLECTOR_RELATIONSHIP_HANDLER = (eventType, id, subject) => { const collector = COLLECTORS.get(id); if (!collector || collector.destroyed) { return; } switch (eventType) { case EventType.ADDED: collector.takeResponsibilityForElement(subject); break; case EventType.MODIFIED: collector.modified(subject); break; case EventType.REMOVED: collector.releaseResponsibilityForElement(subject); break; } }; const FOR_EACH_COLLECTOR = (subject, eventType) => { var _a, _b; let collectedElement = subject; // Loop through collected ancestors and notify them of modifications do { if (!((_a = collectedElement.__evolv__) === null || _a === void 0 ? void 0 : _a.collectors)) { continue; } // Provides a safe reference to the collected element const collectedElementCopy = collectedElement; // Process disconnected elements first, so mutators for connected collectors take priority for (const phase of [false, true]) { collectedElement.__evolv__.collectors.forEach((active, id) => { if (phase === active) { COLLECTOR_RELATIONSHIP_HANDLER(eventType, id, collectedElementCopy); } }); } } while ((collectedElement = (_b = collectedElement.parentElement) === null || _b === void 0 ? void 0 : _b.closest('[data-mutate-id]'))); }; const runExistingMutations = () => { const existingMutations = ELEMENT_OBSERVER.takeRecords(); ELEMENT_MUTATION_RECORD_QUEUE.push(...existingMutations); ELEMENT_MUTATION_HANDLER().catch((e) => { LOG$2.error(`Element mutation observer failed: ${e.message}`); }); }; const flushMutations = () => { ELEMENT_OBSERVER.takeRecords(); }; const NOTIFY = (subject, eventType) => { if (eventType === EventType.MODIFIED || eventType === EventType.REMOVED) { FOR_EACH_COLLECTOR(subject, eventType); } COLLECTORS.forEach((c) => { if (c instanceof AbstractCollector) { return; } const collector = c; if (!collector.selector || c.destroyed) { return; } let collectedElements; if (typeof collector.selector === 'string') { if (subject.matches(collector.selector)) { collector.takeResponsibilityForElement(subject); return; } collectedElements = [...subject.querySelectorAll(collector.selector)]; } else if (Array.isArray(collector.selector)) { if (collector.selector.some((s) => subject.matches(s))) { collector.takeResponsibilityForElement(subject); return; } collectedElements = collector.selector.reduce((acc, s) => { return acc.concat(Array.from(subject.querySelectorAll(s))); }, []); } else { collectedElements = [...collector.selector(window.document)]; // TODO: Test this, it looks like it returns true for no elements and false for 1 if ([].indexOf.call(collectedElements, subject)) { collector.takeResponsibilityForElement(subject); return; } } if (collectedElements) { collectedElements.forEach((e) => { collector.takeResponsibilityForElement(e); }); } }); }; const PARENT_MUTATION_HANDLER = throttle(() => { const queue = PARENT_MUTATION_RECORD_QUEUE; PARENT_MUTATION_RECORD_QUEUE = []; for (const record of queue) { const target = record.target; let index = record.removedNodes.length; while (index--) { const node = record.removedNodes.item(index); NOTIFY(target, node === target ? EventType.REMOVED : EventType.MODIFIED); } if (!target.isConnected) { continue; } index = record.addedNodes.length; while (index--) { const node = record.addedNodes.item(index); NOTIFY(target, node === target ? EventType.ADDED : EventType.MODIFIED); } } }, ((_b = (_a = window.evolv) === null || _a === void 0 ? void 0 : _a.collect) === null || _b === void 0 ? void 0 : _b.throttle) || 25); const ELEMENT_MUTATION_HANDLER = throttle(() => { const queue = ELEMENT_MUTATION_RECORD_QUEUE; ELEMENT_MUTATION_RECORD_QUEUE = []; for (const record of queue) { if (!record.target.isConnected) { continue; } const target = (record.target.nodeType === Node.ELEMENT_NODE ? record.target : record.target.parentElement); NOTIFY(target, EventType.MODIFIED); } }, ((_d = (_c = window.evolv) === null || _c === void 0 ? void 0 : _c.collect) === null || _d === void 0 ? void 0 : _d.throttle) || 25); const PARENT_OBSERVER = new MutationObserver((records) => { PARENT_MUTATION_RECORD_QUEUE = PARENT_MUTATION_RECORD_QUEUE.concat(records); PARENT_MUTATION_HANDLER().catch((e) => { LOG$2.error(`Parent mutation observer failed: ${e.message}`); }); }); const ELEMENT_OBSERVER = new MutationObserver((records) => { ELEMENT_MUTATION_RECORD_QUEUE.push(...records); ELEMENT_MUTATION_HANDLER().catch((e) => { LOG$2.error(`Element mutation observer failed: ${e.message}`); }); }); const UPDATE_OBSERVER = () => { const parents = new Set(PARENT_OBSERVED_ELEMENTS); PARENT_OBSERVER.disconnect(); for (const parent of parents) { PARENT_OBSERVER.observe(parent, PARENT_OBSERVER_OPTIONS); } ELEMENT_OBSERVER.disconnect(); COLLECTORS.forEach((collector) => { for (const element of collector.elements) { ELEMENT_OBSERVER.observe(element, ELEMENT_OBSERVER_OPTIONS); } }); }; class AbstractCollector { get isWaitingForValidation() { return !!this._withinInterval; } get hasSubscribers() { return this._listeners.length > 0; } get elementSet() { return this._elements; } get validationWindowH