@evolv/mutate
Version:
A library of standard DOM mutations by Evolv AI.
1,305 lines (1,285 loc) • 46.9 kB
TypeScript
declare enum LogLevel {
DEBUG = 0,
INFO = 1,
WARNING = 2,
ERROR = 3
}
/***
* Outputs messages to the console.
*/
declare class MessageLog {
private readonly _logLevel;
private readonly _scope;
private _errorLimit;
private _errorCount;
get logLevel(): LogLevel;
constructor(scope?: string | null, logLevel?: LogLevel);
qualifyMessage(message: string): string;
debug(message: string): void;
info(message: string): void;
warn(message: string): void;
error(message: string): void;
log(logLevel: LogLevel, message: string): void;
}
type SelectorFn = (document: Document) => NodeList | HTMLCollection | Element[];
/***
* A type represents the state within an {@link Effect} tree.
*/
declare class State {
private readonly _collector;
private readonly _mutator;
private readonly _properties;
get properties(): ReadonlyMap<string, any>;
get mutator(): Mutator;
get collector(): Collector;
constructor(collector: Collector, mutator: Mutator, properties?: Map<string, any>);
}
interface Action {
state: State;
subject: Element;
factory?: ActionFactory;
pauseEvaluation(): void;
unpauseEvaluation(): void;
removed(): void;
modified(): void;
revert(): void;
connect(action: Action): void;
disconnect(action: Action): void;
}
interface ActionFactory {
once: boolean;
next: ActionFactory | undefined;
attach(state: State, subject: Element, branch: Action): void;
}
declare abstract class AbstractActionFactory implements ActionFactory {
private _instances;
protected _next?: ActionFactory;
protected _once: boolean;
protected get instances(): Map<Element, Action>;
get once(): boolean;
set once(value: boolean);
get next(): ActionFactory | undefined;
set next(value: ActionFactory | undefined);
protected abstract createInstance(state: State, subject: Element, branch?: Action): Action;
attach(state: State, subject: Element, branch: Action, attachNext?: boolean): Action;
}
type Transform = (value: any) => any;
declare abstract class AbstractBindingFactory extends AbstractActionFactory {
private readonly _stateProperty;
private readonly _transform?;
get stateProperty(): string;
protected get transform(): Transform | undefined;
constructor(stateProperty: string, transform?: Transform);
}
interface LocatorHandler {
(collectorName: string, context: Map<string, any>, inserter: Inserter): Mutator;
}
interface Inserter {
(subject: Element, toInsert: Node): Node[];
}
declare class Locator {
private readonly _collectorName;
private readonly _handler;
private readonly _context;
private readonly _clone;
constructor(collectorName: string, context: Map<string, any>, handler: LocatorHandler);
private insertNode;
private createRelativeInserter;
private createInserter;
append(): Mutator;
prepend(): Mutator;
before(selector?: string): Mutator;
after(selector?: string): Mutator;
}
type AttributeValue = string | number | null | undefined | Computable<string | number | null | undefined>;
type CustomMutationOptions = {
add?: CustomMutationHandler;
modify?: CustomMutationHandler;
remove?: CustomMutationHandler;
revert?: CustomMutationHandler;
};
type CustomMutationHandler = (state: State, subject: Element) => void;
type Injectable = Node | Node[] | NodeList | HTMLCollection | string | null | undefined | Computable<Node | Node[] | NodeList | HTMLCollection | string | null | undefined>;
type TextValue = string | number | null | undefined | Computable<string | number | null | undefined>;
type IndexedTransform = (value: any, index?: number) => any;
declare abstract class AbstractGateFactory extends AbstractActionFactory {
protected _deferredNext: ActionFactory | undefined;
protected _stateProperties: string[];
protected _transforms?: Transform | Transform[];
get deferredNext(): ActionFactory | undefined;
get indexedTransform(): IndexedTransform;
set next(instance: ActionFactory | undefined);
constructor(stateProperties: string | string[], transforms?: Transform | Transform[]);
}
type BindingFactoryConstructor = new (stateProperty: string, ...any: any[]) => AbstractBindingFactory;
type GateFactoryConstructor = new (stateProperty: string | string[], ...any: any[]) => AbstractGateFactory;
/***
* {@link Binder} allows the {@link Effect} tree to be bound to contextual data.
*/
declare class Binder<T> {
protected readonly _factoryConstructor: BindingFactoryConstructor | GateFactoryConstructor;
private readonly _factoryArgs;
private readonly _push;
private readonly _result;
constructor(factoryConstructor: BindingFactoryConstructor | GateFactoryConstructor, factoryArgs: Array<any>, result: T, push: MutatorPushFunction);
/***
* Specify the name that the bound value should be associated with.
*
* @param name The name to use for this value.
*/
as(name: string): T;
}
/***
* @internal
*/
type MutatorPushFunction = (action: ActionFactory) => Mutator;
/***
* {@link Mutator} provides a collection of methods to persistently mutate the DOM.
*/
declare class Mutator {
private readonly _head;
private readonly _collector;
private _paused;
private _tail;
private readonly _collectors;
private readonly _id;
get collector(): Collector;
get collectors(): Map<string, Collector>;
get id(): string;
constructor(collector: Collector, collectors: Map<string, Collector>);
private _push;
private _evaluate;
/***
* Specify that the {@link Effect}s be applied no more than once. Only applies
* to the immediately preceding {@link Effect}. *Implemented in v2.2.0 for [when](#methods) and v2.3.0 for all {@link Mutator} methods*
*
* @example
* Using `once()`, `when.elements()` will only update the `c2-text` property one
* time. If the text of the element in `collector-2` changes `.new-div` will
* not update.
* ```js
* mutate('collector-1')
* .when.elements('collector-2', el => el.textContent).as('c2-text')
* .once()
* .inject(({properties}, collector1) => `<div class="new-div">${
* properties.get('c2-text')[0]
* }</div>`);
* ```
*
* @example
* When using `once()`, `when.expression()` will stop watching `appData.value`
* when it returns it's first result that is non-nullish. The text of
* `.new-div` will not change even `appData.value` does.
* ```js
* mutate('collector-1')
* .when.expression(() => window.appData?.value).as('app-value')
* .once()
* .inject(({properties}, collector1) => `<div class="new-div">${
* properties.get('app-value')
* }</div>`);
* ```
*
* @example
* Replace innerHTML and then modify the contents. This pattern can be useful
* in cases with throttle or modified control. Without `once()`
* `.html()` would reset HTML when `.text()` changes its contents. `inject()`
* would then replace the destroyed {@link Element} which would trigger `.html()`
* again resulting in an infinite loop.
*
* ```js
* // Context (modified Control)
* mutate('tile').html(`
* <h2>Title</h2>
* <p>Contents</p>
* `).once();
*
* // Variant 1
* mutate('tile').child('h2').text('New title');
* ```
*/
once: () => this;
/***
* Bind {@link Effect}s to non-DOM related events or state changes.
*
* The bound value will be available for use in {@link Effect}s as well as triggering reapplication of
* {@link Effect}s.
*/
with: {
/***
* Bind {@link Effect}s to an attribute after applying an optional transform.
*
* @param attrName The name of the attribute to bind.
* @param [transform] A function to transform the value before binding.
*
* @example
* Bind an attribute to a {@link State} property:
* ```js
* mutate('collector-1').with.attribute('data-attribute').as('attribute-value');
* ```
*
* @example
* Transform and bind an attribute value to a {@link State} property:
* ```js
* mutate('collector-1')
* .with.attribute('data-amount', (amount) => parseFloat(amount))
* .as('amount');
* ```
*
* @example
* Transform and bind an attribute value to an element's text content
* ```js
* mutate('product')
* .with
* .attribute('data-price', (price) => parseFloat(price)
* .toLocaleString('en-US', { style: 'currency', currency: 'USD' })
* )
* .as('price-amount')
* .text((state, subject) => `Now only ${state.properties.get('price-amount')}`);
* ```
*/
attribute: (attrName: string, transform?: Transform) => Binder<Mutator>;
/***
* Bind {@link Effect}s to an attribute after applying an optional transform.
*
* @param attrName The name of the attribute to bind.
* @param [transform] A function to transform the value before binding.
*
* @example
* Bind an attribute to a {@link State} property:
* ```js
* mutate('collector-1').with.attr('data-attribute').as('attribute-value');
* ```
*
* @example
* Transform and bind an attribute value to a {@link State} property:
* ```js
* mutate('collector-1')
* .with
* .attr('data-amount', (amount) => parseFloat(amount))
* .as('amount');
* ```
*
* @example
* Transform and bind an attribute value to an element's text content:
* ```js
* mutate('product')
* .with
* .attr('data-price', (price) => parseFloat(price)
* .toLocaleString('en-US', { style: 'currency', currency: 'USD' })
* )
* .as('price-amount')
* .text((state, subject) => `Now only ${state.properties.get('price-amount')}`);
* ```
*/
attr: (attrName: string, transform?: Transform) => Binder<Mutator>;
/***
* Bind {@link Effect}s to the content of an {@link Element} after applying an optional transform.
*
* @param [transform] A function to transform the value before binding.
*
* @example
* Bind an element's text content to a {@link State} property:
* ```js
* mutate('collector-1').with.content().as('text');
* ```
*
* @example
* Bind an element's text content to inject as a new sibling element:
* ```js
* mutate('collector-1')
* .with
* .content()
* .as('text')
* .inject((state, subject) => `<div>${state.properties.get('text')}</div`)
* .after();
* ```
*/
content: (transform?: Transform) => Binder<Mutator>;
/***
* Bind {@link Effect}s to a context property after applying an optional
* transform. The context can be used to share properties between collectors.
*
* *Note*: The context is a feature of the Evolv AI asset manager and is
* only available from within experiment code
*
* @param propertyName The name of a context property to bind.
* @param [transform] A function to transform the value before binding.
*
* @example
* Bind a context property to a {@link State} property:
* ```js
* mutate('collector-1').with.context('amount').as('amount');
* ```
*
* @example
* Transform and bind a context property to a {@link State} property:
* ```js
* mutate('collector-1')
* .with
* .context('amountString', (amountString) => parseFloat(amountString))
* .as('amount');
* ```
*
* @example
* Create a toggle:
* ```js
* mutate('button').on('click', ({ target }) => {
* const hide = window.evolv.context.get('hide') || false;
* window.evolv.context.set('hide', !hide);
* })
* .with
* .context('hide')
* .as('hide')
* .attributes({
* 'aria-controls': '#details',
* 'aria-expanded': ({ properties }) => !properties.get('hide')
* });
*
* mutate('details')
* .with
* .context('hide')
* .as('hide')
* .classes({
* hide: ({ properties }) => properties.get('hide')
* });
* ```
*/
context: (propertyName: string, transform?: Transform) => Binder<Mutator>;
/***
* Bind {@link Effect}s to a computed value.
*
* @param computed A function to compute a value to bind.
*
* @example
* Bind a computed value to {@link State} property:
* ```js
* mutate('collector-1')
* .with
* .computed((state, subject) => {
* const { properties } = state;
* const value1 = properties.get('value-1');
* const value2 = properties.get('value-2');
* return value1 + value2;
* })
* .as('value-3');
* ```
*/
computed: <T>(computed?: Computable<T>) => Binder<Mutator>;
};
/***
* The `when` methods are gate bindings that pause chained {@link Effect}s until
* conditions are met then bind values to properties and resume execution. If the
* conditions no longer met then evaluation is paused again and down-chain {@link Effect}s
* are reverted.
*/
when: {
/***
* Pauses chained {@link Effect}s until the indicated {@link Collector}s
* contain {@link Element}s that are children of {@link Element}s of the
* original {@link Collector}. Once those {@link Element}s are found they
* are bound to the indicated properties and {@link Effect}s are resumed.
* *Introduced in version 2.2.0*
*
* *Note:* If you leave the `as()` parameter blank the property names will
* fall back to the `collectorNames`.
*
* @param collectorNames The {@link Collector} or {@link Collector}s that have children of the original {@link Collector}.
* @param transforms A {@link Transform} or {@link Transform}s that accept an {@link Element}.
*
* @example
* Apply a class to an element when it has a child:
* ```js
* mutate('parent').when.children('child').as().classes({
* 'has-child': true
* });
* ```
*
* @example
* Apply a class to an element when it has a child containing a number over
* a certain value:
* ```js
* mutate('parent')
* .when.children('child', (element) => parseInt(element.textContent))
* .as('value').classes({
* 'has-element-with-value-over-100': ({ properties }) => properties.get('value')[0] > 100
* });
* ```
*
* @example
* In tiles containing two values insert a total:
* ```js
* mutate('tile')
* .when.children(['value-1', 'value-2'], (element) => parseFloat(element.textContent))
* .as()
* .inject(({ properties }) => `
* <div class="total">
* Total: ${properties.get('value-1')[0] + properties.get('value-2')[0]}
* </div>
* `)
* .append();
* ```
*/
children: (collectorNames: string | string[], transforms?: Transform | Transform[]) => Binder<Mutator>;
/***
* Pauses chained {@link Effect}s until the indicated {@link Collector}s
* contain {@link Element}s. Once those {@link Element}s are found they are
* bound to the indicated properties and {@link Effect}s are resumed.
* *Introduced in version 2.2.0*
*
* *Note:* If you leave the `as()` parameter blank the property names will
* fall back to the `collectorNames`.
*
* @param collectorNames The {@link Collector} or {@link Collector}s that have children of the original {@link Collector}.
* @param transforms A {@link Transform} or {@link Transform}s that accept an {@link Element}.
*
* @example
* Total the values of all the tiles on the page and append to container:
* ```js
* mutate('container')
* .when.elements('tile', (tile) =>
* parseInt(tile.querySelector('span.value'))
* )
* .as('tile-value')
* .inject(({ properties }) => `<div class="total">${
* properties.get('tile-value').reduce((a, c) => a + c, 0)
* }</div>`);
* ```
*/
elements: (collectorNames: string[], transforms?: Transform | Transform[]) => Binder<Mutator>;
/***
* Pauses chained {@link Effect}s until the expression evaluates non-nullish.
* The value of evaluated expression is bound to a property and {@link Effect}s
* are resumed. *Introduced in version 2.2.0*
*
* *Note:* `when.expression()` will resume execution for any result that is
* not `undefined` or `null`. This means falsy values such as `0`, `false`,
* `NaN`, and empty strings can be assigned to `state.properties`.
*
* @param expression An anonymous function returning any expression.
* @param transform A {@link Transform} that modifies the result of the expression.
*
* @example
* Wait for a window object and inject a value on to the page:
* ```js
* mutate('store')
* .when.expression(() => window.storeInfo?.location)
* .as('location')
* .inject(({ properties }) => `
* <h3>Store location</h3>
* <div class="location">${properties.get('location')}</div>
* `)
* .prepend()
* ```
*/
expression: (expression: () => any, transform?: Transform) => Binder<Mutator>;
};
/***
* Clone and hide an {@link Element}, mapping all events from the clone into the original elements. The clone is
* then inserted into all {@link Element}s in the specified {@link Collector}.
*
* The purpose of the functionality is to avoid disabling or otherwise negatively impacting existing event
* listeners.
*
* @param to The name of the {@link Collector} into which the cloned {@link Element} should be projected.
* @param [withStyles] Whether or not to clone styles from the original {@link Element}.
*/
project(to: string, withStyles?: boolean): Locator;
/***
* Hide all {@link Element}s in the {@link Collector}.
*
* @example
* Hide the elements in the collector:
* ```js
* mutate('additional-description').hide();
* ```
*/
hide(): Mutator;
/***
* Show all {@link Element}s in the {@link Collector}. This will only work on
* {@link Element}s that have an inline style of `display: none` set.
*
* @example
* mutate('hidden-elements').show();
*/
show(): Mutator;
/***
* Remove the specified {@link Node}s from all elements in the {@link Collector}.
* @deprecated Use {@link hide}() instead.
*
* @param nodes the {@link Node}s or a selector for {@link Element}s that should be removed.
*/
remove(nodes: Node | Node[] | string): Mutator;
/***
* Insert one or more {@link Node}s into all elements in the {@link Collector}.
*
* @deprecated Use {@link Mutator}.inject() instead.
* @param nodes The {@link Node}s to insert, or a string containing the HTML to insert.
* @param clone Whether or not the nodes should be cloned before insertion. (Default: True)
*/
insert(nodes: Node | Node[] | string, clone?: boolean): Mutator;
/***
* Inject one or more {@link Node}s into all elements in the {@link Collector} at the specified location.
*
* @param injectable The {@link Node}s to insert, or a string containing the HTML, or a {@link Computable}.
* @param [clone=false] Whether the {@link Node}s should be cloned before insertion.
* @return Locators have four public methods: `append()`, `prepend()`, `before()`, and `after()`.
*
* @example
* Append an HTML string to an {@link Element}:
* ```js
* mutate('ul').inject('<li>New list item</li>').append();
* ```
*
* @example
* Inject text after an {@link Element}:
* ```js
* mutate('title').inject('Subtitle').after();
* ```
*
* @example
* Inject a combination of text and {@link Element}s:
* ```js
* // If you want to inject a link into text you probably want there to be space
* // between the text and the link, if you include a space in the HTML string
* // before the element `inject()` will insert it as a text node along with the link.
* mutate('paragraph').inject(' <a href="/details">Details</a>').append();
* ```
*
* @example
* Inject a computed HTML string
* ```js
* mutate('title')
* .with
* .attribute('data-subtitle-text')
* .as('subtitle-text')
* .inject((state, subject) => `<h2>${state.properties.get('subtitle-text')}</h2>`)
* .after();
* ```
*
* @example
* Inject a computed {@link Element}:
* ```js
* mutate('tile').inject((state, subject) => {
* const button = document.createElement('button');
* button.textContent = 'Continue';
* button.addEventListener('click', handleClick);
* return button;
* }).append();
* ```
*
* @example
* Inject an array of {@link Element}s:
* ```js
* const reviewStars = [1, 2, 3, 4, 5].map(starIndex => {
* const star = document.createElement('button');
* star.setAttribute('data-star-index', starIndex);
* star.addEventListener('click', handleStarClick(starIndex));
* return star;
* });
*
* mutate('review').inject(reviewStars).append();
* ```
*/
inject(injectable: Injectable, clone?: boolean): Locator;
/***
* Emulate a click on all {@link Element}s in the {@link Collector}.
*
* @example
* Click a button automatically:
* ```js
* mutate('button').click();
* ```
*/
click(): Mutator;
/***
* Emulate a focus on all {@link Element}s in the {@link Collector}.
*
* @example
* Focus on a link automatically:
* ```js
* mutate('link').focus();
* ```
*/
focus(): Mutator;
/***
* Emulate a blur on all {@link Element}s in the {@link Collector}.
*
* @example
* Blur a link automatically:
* ```js
* mutate('link').blur();
* ```
*/
blur(): Mutator;
/***
* Emulate a keypress on all {@link Element}s in the {@link Collector}.
* @deprecated Use {@link keydown} or {@link keyup} instead.
*/
keypress(options: KeyboardEventInit): Mutator;
/***
* Emulate a keydown on all {@link Element}s in the {@link Collector}.
*
* @example
* Simulate a space bar keydown on a link:
* ```js
* mutate('link').keydown({keyCode: 32});
* ```
*/
keydown(options: KeyboardEventInit): Mutator;
/***
* Emulate a keyup on all {@link Element}s in the {@link Collector}.
*
* @example
* Simulate a space bar keydown on a link:
* ```js
* mutate('link').keyup({ keyCode: 32 });
* ```
*/
keyup(options: KeyboardEventInit): Mutator;
/***
* Replace the text content of the {@link Element}s matched by the {@link Collector}.
*
* @param newText The new text content.
*
* @example
* Replace the text content of a paragraph:
* ```js
* mutate('details').text('Enter your credit card details below')
* ```
*
* @example
* Replace text content of a paragraph with computed text:
* ```js
* const peopleCount = Math.round(Math.rand() * 100) + 1;
* mutate('details').text(() => `${ peopleCount } other people have this item in their cart`)
* ```
*/
text(newText: TextValue): Mutator;
/***
* Replace the HTML content of the {@link Element}s matched by the {@link Collector}.
*
* @param newHtml The new HTML or {@link Node} to replace the content with.
*
* @example
* Replace the innerHTML of an element with an HTML string:
* ```js
* mutate('collector-1').html('Are you <strong>ready</strong>?');
* ```
*
* @example
* Replace the innerHTML of an element with a node:
* ```js
* const button = document.createElement('button');
* button.textContent = 'Continue';
* button.addEventListener('click', handleClick);
* mutate('collector-1').html(button);
* ```
*
* @example
* Replace innerHTML of an element with a computed HTML string:
* ```js
* const peopleCount = Math.round(Math.rand() * 100) + 1;
* mutate('details').html(() => `<span class="synthetic-urgency">${ peopleCount } other people have this item in their cart</span>`)
* ```
*/
html(newHtml: string | Node | Computable<string>): Mutator;
/***
* Set or remove classes on the {@link Element}s in the {@link Collector}.
*
* @param classes A {@link Map} of class
* names to {@link boolean} or {@link (() => boolean)} representing their desired state.
*
* @example
* Remove a class from an element:
* ```js
* mutate('collector-1').classes({
* 'text-style-large': false
* });
* ```
*
* @example
* Add a class to an element:
* ```js
* mutate('collector-1').classes({
* new-class: true
* });
* ```
*
* @example
* Add a conditional class to an element:
* ```js
* const isExpanded = true;
* mutate('collector-1').classes({
* expanded: () => isExpanded
* });
* ```
*/
classes(classes: object | Map<string, boolean | Computable<boolean>>): Mutator;
/***
* Set styles on the {@link Element}s in the {@link Collector}.
*
* @param styles A {@link Map} of style
* names to {@link string} or {@link (() => string)} representing their desired state. Style properties can
* can be kebab case ('background-color') or camel case ('backgroundColor').
*
* @example
* Set a style of `display: flex` on an element:
* ```js
* mutate('collector-1').styles({
* display: 'flex'
* });
* ```
*
* @example
* Remove an inline style from an element:
* ```js
* mutate('collector-1').styles({
* height: null
* });
* ```
*
* @example
* Add a computed style to an element:
* ```js
* mutate('banner').styles({width: () => `${window.innerWidth}px`});
* ```
*/
styles: (styles: object | Map<string, string | Computable<string>>) => Mutator;
/***
* Set an attribute on the {@link Element}s in the {@link Collector}.
*
* @param name The name of the attribute to set.
* @param value The value of the attribute to set.
*
* @example
* Change an image source:
* ```js
* mutate('image').attribute('src', 'images/high-res-version.jpg');
* ```
*
* @example
* Update an attribute with a computed value:
* ```js
* const id = Math.floor(Math.random() * 1000);
* mutate('collector-1').attribute('data-id', () => id);
* ```
*/
attribute: (name: string, value: AttributeValue) => Mutator;
/***
* This is an alias for 'attribute' which sets an attribute on the {@link Element}s in the {@link Collector}.
*
* @param name The name of the attribute to set.
* @param value The value of the attribute to set.
*
* @example
* Assign a string value to an attribute:
* ```js
* mutate('collector-1').attr('data-attribute', 'value');
* ```
*
* @example
* Assign a computed string value to an attribute:
* ```js
* const valueString = 'value';
* mutate('collector-1').attr('data-attribute', (state, subject) => valueString);
* ```
*/
attr: (name: string, value: AttributeValue) => Mutator;
/***
* Set attributes on the {@link Element}s in the {@link Collector}.
*
* @param attributes An Object of the form <code>{attribute: value}</code> or a Map of the form
* <code>[[attribute, value]]</code>. Here <code>value</code> could be a string or a function that returns a string.
*
* @example
* Assign multiple attributes:
* ```js
* const valueString = 'value-2';
* mutate('collector-1').attributes({
* 'data-reference': 'value-1',
* 'data-attribute': () => valueString;
* });
* ```
*/
attributes: (attributes: object | Map<string, string | Computable<string>>) => Mutator;
/***
* Traverse to the parent of the element in the {@link Mutator}.
*
* @param selector A selector to filter the parent by.
*
* @example
* Set a class on the parent:
* ```js
* mutate('collector-1').parent().classes({
* 'collector-1-wrap': true
* });
* ```
*
* @example
* Set a class on a matching ancestor:
* ```js
* mutate('collector-1').parent('.container').classes({
* 'contains-collector-1': true
* });
*/
parent(selector?: string): Mutator;
/***
* Traverse to the child of the element in the {@link Mutator}.
*
* @param selector A selector to filter the child by.
*
* @example
* Hide the first child of a collected element:
* ```js
* mutate('collector-1').child().hide();
* ```
*
* @example
* Hide the first matching child of a collected element (could be nested):
* ```js
* mutate('collector-1').child('span').hide();
* ```
*/
child(selector?: string): Mutator;
/***
* Traverse to all the parents of the {@link Element} in the {@link Mutator}.
*
* @param [selector] A selector to filter the parents by.
*
* @example
* Set the width of all parents of a collected element to <code>auto</code>:
* ```js
* mutate('collector-1').parents().styles({
* width: 'auto'
* });
* ```
*
* @example
* Set the width of all matching parents of a collected element to <code>auto</code>:
* ```js
* mutate('collector-1').parents('div').styles({
* width: 'auto'
* });
* ```
*/
parents: (selector?: string) => Mutator;
/***
* Traverse to all the children of the {@link Element} in the {@link Mutator}.
*
* @param [selector] A selector to filter the children by.
*
* @example
* Set the width of all child elements of a collected element to <code>auto</code>:
* ```js
* mutate('collector-1').children().styles({
* width: 'auto'
* });
* ```
*
* @example
* Set the width of all matching child elements of a collected element to
* <code>auto</code> (including nested elements):
* ```js
* mutate('collector-1').children('*').styles({
* width: 'auto'
* });
* ```
*/
children: (selector?: string) => Mutator;
/***
* Reserved.
* @internal
*/
capture: () => Mutator;
/***
* Reserved.
* @internal
*/
root: () => Mutator;
/***
* Reserved.
* @internal
*/
swapWith: () => Mutator;
/***
* Create a custom {@link Effect} that is persistently applied.
* @param mutation The initializationfunction or configuration object
*/
custom: (mutation: CustomMutationHandler | CustomMutationOptions) => Mutator;
/***
* Create a custom {@link Effect} that is persistently applied.
* @deprecated Use {@link Mutator#custom} instead.
* @param initialize The function to be invoked to initialize the {@link Effect}.
* @param modify The function to be invoked when an {@link Effect} is modified.
* @param revert The function to be invoked when the {@link Effect} should be reverted.
*/
customMutation: (initialize?: CustomMutationHandler, modify?: CustomMutationHandler, revert?: CustomMutationHandler) => Mutator;
/***
* @deprecated Use {@link Mutator#customMutation} instead. Remove in June 2023.
*/
customEffect: (initialize: () => void, modify: () => void, revert: () => void) => Mutator;
/***
* WARNING! This function is experimental and should only be used in testing.
*
* @param transform A function that modifies the DOM of subject.
*/
transform: (transform: (subject: Element) => Element) => Mutator;
/***
* Add event listener to {Element}s in the {@link Collector}.
*
* @param event The event type to listen to.
* @param listener An event listener to be attached to elements.
*
* @example
* Add a click event to a button:
* mutate('button').on('click', handleClick);
*/
on(event: string, listener: EventListener): Mutator;
/***
* Add event listener to elements
*
* @deprecated Use {@link Mutator#on} instead. Remove in June 2023.
* @param event The event type to listen to.
* @param listener An event listener to be attached to elements.
*/
listen: (event: string, listener: EventListener) => Mutator;
/***
* Remove event listener from {Element}s in the {@link Collector}.
*
* @param event The event type to listen to.
* @param listener An event listener to be attached to elements.
*/
off(event: string, listener: EventListener): Mutator;
/***
* Revert all {@link Effect}s applied through this {@link Mutator} to all {@link Element}s in the {@link Collector}
* or the specified {@link Subject}.
*
* @param [subject] The {@link Element} to revert the {@link Effect}s on.
*
* @example
* Stop and revert all active {@link Mutator}s:
* // Pausing is important because otherwise Mutate will reapply mutations after
* // the reversion
* mutate('collector-1').pause().revert();
*/
revert: (subject?: Element) => Mutator;
/***
* Pause all {@link Effect}s that are applied through this {@link Mutator}.
*
* @example
* // Store mutator
* const mutator = mutate('collector-1').hide();
* mutator.pause();
*/
pause: () => Mutator;
/***
* Returns true if this {@link Mutator} is paused.
*
* @example
* const mutator = mutate('collector-1').hide();
* mutator.pause();
* console.log(mutator.paused()); // true
*/
paused: () => boolean;
/***
* Unpause all {@link Effect}s that are applied through this {@link Mutator}.
*
* @example
* const mutator = mutate('collector-1').hide();
* mutator.pause();
* mutator.unpause();
*/
unpause: () => Mutator;
/***
* Reinitialize all {@link Effect}s on the {@link Element}s in the {@link Collector}.
*
* @example
* mutate('collector-1').reevaluate();
*/
reevaluate: () => Mutator;
}
declare enum EventType {
ADDED = 0,
REMOVED = 1,
MODIFIED = 2
}
declare enum CollectorState {
INITIALIZED = 0,
VALIDATED = 1,
FAILED = 2,
PAUSED = 3,
UNPAUSED = 4,
DESTROYED = 5
}
type ElementListener = (action: EventType, element: Element, elementList: Element[]) => void;
type CollectorStateListener = (collectorState: CollectorState, collector: Collector) => void;
type Predicate = (element: Element) => boolean;
type Mapping = (element: Element) => Element;
/***
* Either the {@link Element} or the selector(s) to use to find the {@link Element}s for a {@link Collector}.
*/
type Subject = string | string[] | Element | Element[] | SelectorFn;
/***
* A {@link Collector} that allows you to traverse directly into a {@link Mutator}
*/
interface MutatableCollector extends Collector {
/***
* Create a {@link Mutator} associated with the current {@link Collector}.
*
* @param [variantKey] The variant key to associate with the {@link Mutator}.
*/
mutate(variantKey?: string): Mutator;
/***
* Create a {@link Collector} associated with the specified name.
*
* @param name The name of the {@link Collector} to create.
*/
as(name: string): MutatableCollector;
}
interface Collector {
/***
* The id of the {@link Collector}.
*/
id: string;
/***
* The name of the {@link Collector}.
*/
name: string | undefined;
/***
* The scope of the {@link Collector}, defined by [collect.scope()](/mutate/api-custom/collect/collect-scope).
*/
scope: string | undefined;
/***
* The outermost parent {@link Element} to observe.
*/
root: Document | Element;
/***
* All {@link Element}s the {@link Collector} has collected.
*/
elements: Element[];
/***
* A {@link MessageLog} scoped to this {@link Collector}.
*/
log: MessageLog;
/***
* All {@link Element}s the {@link Collector} is currently observing.
*/
observedTargets: Element[];
/***
* True if the {@link Collector} is paused, otherwise False.
*/
paused: boolean;
/***
* True if all validation has been satisfied, otherwise False.
*/
isValid: boolean;
/***
* True of the {@link Collector} has been destroyed, otherwise False.
*/
destroyed: boolean;
/***
* Permanently claim a collected {@link Element} for modification.
*/
claim(): Promise<Element>;
/***
* Add an {@link ElementListener} to {@link Collector} without immediately invoking.
*
* @param listener A callback to be notified about changes in the {@link Element}s.
*/
addListener(listener: ElementListener): this;
/***
* Validates the {@link Collector} and calls all attached {@link ElementListeners}
*
* @param elementAction The type of action of which to notify {@link ElementListeners}
* @param element The {@link Element} to pass to {@link ElementListeners}
*/
notifyListeners(elementAction: EventType, element: Element): void;
/***
* Subscribe to notifications about the changing of {@link Element}s within the {@link Collector}.
* Validates the {@link Collector} and immediately invokes the {@link ElementListener} when called.
*
* @param listener A callback to be notified about changes in the {@link Element}s.
*/
subscribe(listener: ElementListener): this;
/**
* Unsubscribe a previously subscribed listener from the {@link Collector}.
*
* @param listener The callback function that needs to be removed. It must match the reference of a listener previously added via `subscribe`.
* @return Returns the current instance to allow method chaining.
*/
unsubscribe(listener: ElementListener): this;
/***
* Subscribe to notifications about the changing of state within the collector.
*
* @param listener A callback to be notified when the state of the {@link Collector} changes.
*/
subscribeState(listener: CollectorStateListener): this;
/***
* Set a selector for the parent {@link Element}s to be observed for matching {@link Element}s.
*
* @param parentSelector The selector for parent {@link Element}s.
*/
observeParent(parentSelector: string): this;
/***
* Pause collection and {@link Element} related notifications for this {@link Collector}.
*/
pause(): this;
/***
* Unpause collection and {@link Element} related notifications for this {@link Collector}.
*/
unpause(): this;
/***
* Add a {@link Predicate} that the {@link Collector} must satisfy before it is considered valid and ready to
* apply {@link Effect}s.
*
* @param predicate A {@link Predicate} the collector must meet before applying {@link Effect}s.
*/
validate(predicate: Predicate): this;
/***
* Add a {@link Predicate} {@link Element}s must satisfy to be included in the {@link Collector}.
*
* @param predicate A {@link Predicate} {@link Element}s must satisfy to be included in the {@link Collector}
*/
filter(predicate: Predicate): this;
/***
* Map matched {@link Element}s to a new {@link Element} to be included in the {@link Collector}.
*
* @param mapping A {@link Mapping} to map matched {@link Element}s to a new {@link Element}.
*/
map(mapping: Mapping): this;
/***
* Specify the minimum number of {@link Element}s the {@link Collector} must find before it is considered valid.
*
* @param count The minimum number of {@link Element}s the {@link Collector} must match to be considered value.
*/
atLeast(count: number): this;
/***
* Specify the maximum number of {@link Element}s the {@link Collector} should find to be considered valid.
*
* @param count The maximum number of {@link Element}s the {@link Collector} must match to be considered value.
*/
atMost(count: number): this;
/***
* Specify the total number of {@link Element}s the {@link Collector} should find to be considered valid.
*
* @param count The total number of {@link Element}s the {@link Collector} must match to be considered value.
*/
exactly(count: number): this;
/***
* Specify the maximum amount of time in milliseconds that a {@link Collector} has to satisfy all validation criteria.
*
* @param millis The maximum amount of time the {@link Collector} has to satisfy validation criteria.
*/
within(millis: number): this;
/***
* Permanently destroy this {@link Collector}.
*/
destroy(): void;
}
declare class CollectorBuilder {
private readonly _collectors;
private readonly _mutate;
private readonly _subject;
private readonly _scope?;
private readonly _calls;
constructor(collectors: Map<string, Collector>, mutate: (collectorName: string, variantKey?: string) => Mutator, subject: Subject, scope?: string, calls?: {
fn: string;
args: any[];
}[]);
private _clone;
private _call;
atLeast(count: number): CollectorBuilder;
atMost(count: number): CollectorBuilder;
exactly(count: number): CollectorBuilder;
filter(predicate: Predicate): CollectorBuilder;
map(mapping: Mapping): CollectorBuilder;
observeParent(parentSelector: string): CollectorBuilder;
subscribe(listener: ElementListener): CollectorBuilder;
subscribeState(listener: CollectorStateListener): CollectorBuilder;
validate(predicate: Predicate): CollectorBuilder;
within(millis: number): CollectorBuilder;
mutate(variantKey?: string): Mutator;
as(name: string): Collector;
}
/***
* The entry points of the Mutate library, scoped to a given scope.
*/
interface ScopedMutate {
collect(selector: Subject, name?: string, parent?: string): Collector | CollectorBuilder;
mutate(collectorName: string, variantKey?: string): Mutator;
$mu(selector: Subject, collectorName?: string, variantKey?: string): Mutator;
}
/***
* Create a new named {@link Collector}.
*
* *Note:* If <code>parent</code> is not specified, the library will attempt
* to discover the closest non-volatile parents of any {@link Element}
* that match the selector.
*
* @param subject A CSS selector, XPath selector, or {@link Element} that the {@link Collector} should manage.
* @param [name] The name of the {@link Collector} to be accessed from {@link mutate}.
* @param [parent] An optional selector to set the nearest non-volatile parent.
* @return The {@link Collector} instance created. This {@link Collector}
* can be retrieved by name as well.
* @example
* Collect the title for future mutating.
*
* ```html
* <h1 class="page-title">Title</h1>
* ```
* ```js
* collect('.page-title', 'title')
* ```
*/
declare function collect(subject: Subject, name?: string, parent?: string): MutatableCollector | CollectorBuilder;
declare namespace collect {
var get: (name: string) => Collector | undefined;
var forEach: (callback: (collector: Collector, name: string) => void) => void;
var destroy: () => void;
var pause: () => void;
var unpause: () => void;
var scope: (scope: string) => ScopedMutate;
}
/***
* Create a {@link Mutator} associated with a named {@link Collector}.
*
* @param collectorName The name of the {@link Collector} to associate with the {@link Mutator}.
* @param [variantKey] The variant key to associate with the {@link Mutator}.
* @return The {@link Mutator} instance created.
*/
declare function mutate(collectorName: string, variantKey?: string): Mutator;
declare namespace mutate {
var get: (variantKey?: string) => Mutator[];
var pause: () => void;
var revert: () => void;
var reevaluate: () => void;
var unpause: () => void;
}
/***
* Create a new named {@link Collector} and associated {@link Mutator}. {@link $mu} is a shorthand combining
* {@link collect} and {@link mutate}.
*
* *Note:* If the <code>name</code> parameter is omitted a randomly generated key will be assigned to the collector.
*
* @param subject A CSS selector, XPath selector, or {@link Element}, or {@link SelectorFn} that the {@link Collector} should manage.
* @param [name] The name of the collector of future reference from {@link mutate}.
* @param [variantKey] The variant key to associate with the {@link Mutator}.
* @return The Mutator instance created.
*/
declare function $mu(subject: Subject, name?: string, variantKey?: string): Mutator;
interface Config {
$mu?: any;
source?: string;
collect?: {
discoveryTime?: number;
discoveryPollInterval?: number;
};
mutate?: {
observe?: boolean;
};
}
/***
* @hidden
*/
declare function bootstrap(config?: Config): void;
export { $mu, type Config, bootstrap, collect, mutate };