@evolv/mutate
Version:
A library of standard DOM mutations by Evolv AI.
1,489 lines (1,454 loc) • 172 kB
JavaScript
/*!
* 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