UNPKG

@evolv/mutate

Version:

A library of standard DOM mutations by Evolv AI.

1,305 lines (1,285 loc) 46.9 kB
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 };