/************************************************************* * * Copyright (c) 2017-2025 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @file Implements the interface and abstract class for MathDocument objects * * @author dpvc@mathjax.org (Davide Cervone) */ import { userOptions, defaultOptions, OptionList, expandable, } from '../util/Options.js'; import { InputJax, AbstractInputJax } from './InputJax.js'; import { OutputJax, AbstractOutputJax } from './OutputJax.js'; import { MathList, AbstractMathList } from './MathList.js'; import { MathItem, AbstractMathItem, STATE } from './MathItem.js'; import { MmlNode, TextNode } from './MmlTree/MmlNode.js'; import { MmlFactory } from '../core/MmlTree/MmlFactory.js'; import { DOMAdaptor } from '../core/DOMAdaptor.js'; import { BitField, BitFieldClass } from '../util/BitField.js'; import { PrioritizedList } from '../util/PrioritizedList.js'; import { handleRetriesFor } from '../util/Retries.js'; /*****************************************************************/ /** * A function to call while rendering a document (usually calls a MathDocument method) * * @template N The HTMLElement node class * @template T The Text node class * @template D The Document class */ export type RenderDoc = (document: MathDocument) => boolean; /** * A function to call while rendering a MathItem (usually calls one of its methods) * * @template N The HTMLElement node class * @template T The Text node class * @template D The Document class */ export type RenderMath = ( math: MathItem, document: MathDocument ) => boolean; /** * The data for an action to perform during rendering or conversion * * @template N The HTMLElement node class * @template T The Text node class * @template D The Document class */ /* prettier-ignore */ export type RenderData = { id: string, // The name for the action renderDoc: RenderDoc, // The action to take during a render() call renderMath: RenderMath, // The action to take during a rerender() or convert() call convert: boolean // Whether the action is to be used during convert() }; /** * The data used to define a render action in configurations and options objects * (the key is used as the id, the number in the data below is the priority, and * the remainind data is as described below; if no boolean is given, convert = true * by default) * * @template N The HTMLElement node class * @template T The Text node class * @template D The Document class */ /* prettier-ignore */ export type RenderAction = [number] | // id (i.e., key) is method name to use [number, string] | // string is method to call [number, string, string] | // the strings are methods names for doc and math [number, RenderDoc, RenderMath] | // explicit functions for doc and math [number, boolean] | // same as first above, with boolean for convert [number, string, boolean] | // same as second above, with boolean for convert [number, string, string, boolean] | // same as third above, with boolean for convert [number, RenderDoc, RenderMath, boolean]; // same as forth above, with boolean for convert /** * An object representing a collection of rendering actions (id's tied to priority-and-method data) * * @template N The HTMLElement node class * @template T The Text node class * @template D The Document class */ export type RenderActions = { [id: string]: RenderAction }; /** * Implements a prioritized list of render actions. Extensions can add actions to the list * to make it easy to extend the normal typesetting and conversion operations. * * @template N The HTMLElement node class * @template T The Text node class * @template D The Document class */ export class RenderList extends PrioritizedList> { /** * Creates a new RenderList from an initial list of rendering actions * * @param {RenderActions} actions The list of actions to take during render(), rerender(), and convert() calls * @returns {RenderList} The newly created prioritied list */ public static create( actions: RenderActions ): RenderList { const list = new this(); for (const id of Object.keys(actions)) { const [action, priority] = this.action(id, actions[id]); if (priority) { list.add(action, priority); } } return list; } /** * Parses a RenderAction to produce the correspinding RenderData item * (e.g., turn method names into actual functions that call the method) * * @param {string} id The id of the action * @param {RenderAction} action The RenderAction defining the action * @returns {[RenderData,number]} The corresponding RenderData definition for the action and its priority */ public static action( id: string, action: RenderAction ): [RenderData, number] { let renderDoc, renderMath; let convert = true; const priority = action[0]; if (action.length === 1 || typeof action[1] === 'boolean') { if (action.length === 2) { convert = action[1] as boolean; } [renderDoc, renderMath] = this.methodActions(id); } else if (typeof action[1] === 'string') { if (typeof action[2] === 'string') { if (action.length === 4) { convert = action[3] as boolean; } const [method1, method2] = action.slice(1) as [string, string]; [renderDoc, renderMath] = this.methodActions(method1, method2); } else { if (action.length === 3) { convert = action[2] as boolean; } [renderDoc, renderMath] = this.methodActions(action[1] as string); } } else { if (action.length === 4) { convert = action[3] as boolean; } [renderDoc, renderMath] = action.slice(1) as [ RenderDoc, RenderMath, ]; } return [ { id, renderDoc, renderMath, convert } as RenderData, priority, ]; } /** * Produces the doc and math actions for the given method name(s) * (a blank name is a no-op) * * @param {string} method1 The method to use for the render() call * @param {string} method2 The method to use for the rerender() and convert() calls * * @returns {[(document: any) => boolean, (math: any, document: any) => boolean]} * Two render action methods wrapping the parameter methods. */ protected static methodActions( method1: string, method2: string = method1 ): [(document: any) => boolean, (math: any, document: any) => boolean] { return [ (document: any) => { if (method1) { document[method1](); } return false; }, (math: any, document: any) => { if (method2) { math[method2](document); } return false; }, ]; } /** * Perform the document-level rendering functions * * @param {MathDocument} document The MathDocument whose methods are to be called * @param {number=} start The state at which to start rendering (default is UNPROCESSED) */ public renderDoc( document: MathDocument, start: number = STATE.UNPROCESSED ) { for (const item of this.items) { if (item.priority >= start) { if (item.item.renderDoc(document)) return; } } } /** * Perform the MathItem-level rendering functions * * @param {MathItem} math The MathItem whose methods are to be called * @param {MathDocument} document The MathDocument to pass to the MathItem methods * @param {number=} start The state at which to start rendering (default is UNPROCESSED) */ public renderMath( math: MathItem, document: MathDocument, start: number = STATE.UNPROCESSED ) { for (const item of this.items) { if (item.priority >= start) { if (item.item.renderMath(math, document)) return; } } } /** * Perform the MathItem-level conversion functions * * @param {MathItem} math The MathItem whose methods are to be called * @param {MathDocument} document The MathDocument to pass to the MathItem methods * @param {number=} end The state at which to end rendering (default is LAST) */ public renderConvert( math: MathItem, document: MathDocument, end: number = STATE.LAST ) { for (const item of this.items) { if (item.priority > end) return; if (item.item.convert) { if (item.item.renderMath(math, document)) return; } } } /** * Find an entry in the list with a given ID * * @param {string} id The id to search for * @returns {RenderData|null} The data for the given id, if found, or null */ public findID(id: string): RenderData | null { for (const item of this.items) { if (item.item.id === id) { return item.item; } } return null; } } /*****************************************************************/ /** * The ways of specifying a container (a selector string, an actual node, * or an array of those (e.g., the result of document.getElementsByTagName()) * * @template N The HTMLElement node class */ export type ContainerList = string | N | (string | N | N[])[]; /** * The options allowed for the reset() method */ export type ResetList = { all?: boolean; processed?: boolean; inputJax?: any[]; outputJax?: any[]; }; /** * The default option list for the reset() method */ export const resetOptions: ResetList = { all: false, processed: false, inputJax: null, outputJax: null, }; /** * The option list for when all options are to be reset */ export const resetAllOptions: ResetList = { all: true, processed: true, inputJax: [], outputJax: [], }; /*****************************************************************/ /** * The MathDocument interface * * The MathDocument is created by MathJax.Document() and holds the * document, the math found in it, and so on. The methods of the * MathDocument all return the MathDocument itself, so you can * chain the method calls. E.g., * * const html = MathJax.Document('...'); * html.findMath() * .compile() * .getMetrics() * .typeset() * .updateDocument(); * * The MathDocument is the main interface for page authors to * interact with MathJax. * * @template N The HTMLElement node class * @template T The Text node class * @template D The Document class */ export interface MathDocument { /** * The document being processed (e.g., DOM document, or Markdown string) */ document: D; /** * The kind of MathDocument (e.g., "HTML") */ kind: string; /** * The options for the document */ options: OptionList; /** * The list of MathItems found in this page */ math: MathList; /** * The list of actions to take during a render() or convert() call */ renderActions: RenderList; /** * This object tracks what operations have been performed, so that (when * asynchronous operations are used), the ones that have already been * completed won't be performed again. */ processed: BitField; /** * An array of input jax to run on the document */ inputJax: InputJax[]; /** * The output jax to use for the document */ outputJax: OutputJax; /** * The DOM adaptor to use for input and output */ adaptor: DOMAdaptor; /** * The MmlFactory to be used for input jax and error processing */ mmlFactory: MmlFactory; /** * @param {string} id The id of the action to add * @param {any[]} action The RenderAction to take */ addRenderAction(id: string, ...action: any[]): void; /** * @param {string} id The id of the action to remove */ removeRenderAction(id: string): void; /** * Perform the renderActions on the document * * @returns {MathDocument} The math document instance */ render(): MathDocument; /** * Perform the renderActions on the document with retry handling * * @returns {Promise} A promise that resolves when the render is complete */ renderPromise(): Promise>; /** * Rerender the MathItems on the page * * @param {number} start The state to start rerendering at * @returns {MathDocument} The math document instance */ rerender(start?: number): MathDocument; /** * Rerender the MathItems on the page * * @param {number} start The state to start rerendering at * @returns {Promise} A promise that resolves when the rerender is complete */ rerenderPromise(start?: number): Promise>; /** * Convert a math string to the document's output format * * @param {string} math The math string to convert * @param {OptionList} options The options for the conversion (e.g., format, ex, em, etc.) * @returns {MmlNode|N} The MmlNode or N node for the converted content */ convert(math: string, options?: OptionList): MmlNode | N; /** * Convert a math string to the document's output format * * @param {string} math The math string to convert * @param {OptionList} options The options for the conversion (e.g., format, ex, em, etc.) * @returns {Promise} A promise that resolves when the conversion is complete */ convertPromise(math: string, options?: OptionList): Promise; /** * Perform an action when previous actions are complete. * (Used to chain promise-based typeset and conversion actions.) */ whenReady(action: () => any): Promise; /** * Return a promise that resolves when all of the action promises have been resolved */ actionPromises(): Promise; /** * Clear the action promises */ clearPromises(): void; /** * Save a promise in the action romises list */ savePromise(promise: Promise): void; /** * Locates the math in the document and constructs the MathList * for the document. * * @param {OptionList} options The options for locating the math * @returns {MathDocument} The math document instance */ findMath(options?: OptionList): MathDocument; /** * Calls the input jax to process the MathItems in the MathList * * @returns {MathDocument} The math document instance */ compile(): MathDocument; /** * Gets the metric information for the MathItems * * @returns {MathDocument} The math document instance */ getMetrics(): MathDocument; /** * Calls the output jax to process the compiled math in the MathList * * @returns {MathDocument} The math document instance */ typeset(): MathDocument; /** * Updates the document to include the typeset math * * @returns {MathDocument} The math document instance */ updateDocument(): MathDocument; /** * Removes the typeset math from the document * * @param {boolean} restore True if the original math should be put * back into the document as well * @returns {MathDocument} The math document instance */ removeFromDocument(restore?: boolean): MathDocument; /** * Set the state of the document (allowing you to roll back * the state to a previous one, if needed). * * @param {number} state The new state of the document * @param {boolean} restore True if the original math should be put * back into the document during the rollback * @returns {MathDocument} The math document instance */ state(state: number, restore?: boolean): MathDocument; /** * Clear the processed values so that the document can be reprocessed * * @param {ResetList} options The things to be reset * @returns {MathDocument} The math document instance */ reset(options?: ResetList): MathDocument; /** * Reset the processed values and clear the MathList (so that new math * can be processed in the document). * * @returns {MathDocument} The math document instance */ clear(): MathDocument; /** * Indicate that the MathDocument is no longer needed. */ done(): Promise; /** * Merges a MathList into the list for this document. * * @param {MathList} list The MathList to be merged into this document's list * @returns {MathDocument} The math document instance */ concat(list: MathList): MathDocument; /** * Clear the typeset MathItems that are within the given container * from the document's MathList. (E.g., when the content of the * container has been updated and you want to remove the * associated MathItems) * * @param {ContainerList} containers The container DOM elements whose math items are to be removed * @returns {MathItem[]} The removed MathItems */ clearMathItemsWithin(containers: ContainerList): MathItem[]; /** * Get the typeset MathItems that are within a given container. * * @param {ContainerList} elements The container DOM elements whose math items are to be found * @returns {MathItem[]} The list of MathItems within that container */ getMathItemsWithin(elements: ContainerList): MathItem[]; } /*****************************************************************/ /** * Defaults used when input jax isn't specified * * @template N The HTMLElement node class * @template T The Text node class * @template D The Document class */ class DefaultInputJax extends AbstractInputJax { /** * @override */ public compile(_math: MathItem) { return null as MmlNode; } } /** * Defaults used when ouput jax isn't specified * * @template N The HTMLElement node class * @template T The Text node class * @template D The Document class */ class DefaultOutputJax extends AbstractOutputJax { /** * @override */ public typeset( _math: MathItem, _document: MathDocument = null ) { return null as N; } /** * @override */ public escaped(_math: MathItem, _document?: MathDocument) { return null as N; } } /** * Default for the MathList when one isn't specified * * @template N The HTMLElement node class * @template T The Text node class * @template D The Document class */ class DefaultMathList extends AbstractMathList {} /** * Default for the Mathitem when one isn't specified * * @template N The HTMLElement node class * @template T The Text node class * @template D The Document class */ class DefaultMathItem extends AbstractMathItem {} /*****************************************************************/ /** * Implements the abstract MathDocument class * * @template N The HTMLElement node class * @template T The Text node class * @template D The Document class */ export abstract class AbstractMathDocument implements MathDocument< N, T, D > { /** * The type of MathDocument */ public static KIND: string = 'MathDocument'; /** * The default options for the document */ public static OPTIONS: OptionList = { OutputJax: null, // instance of an OutputJax for the document InputJax: null, // instance of an InputJax or an array of them MmlFactory: null, // instance of a MmlFactory for this document MathList: DefaultMathList, // constructor for a MathList to use for the document MathItem: DefaultMathItem, // constructor for a MathItem to use for the MathList compileError: ( doc: AbstractMathDocument, math: MathItem, err: Error ) => { doc.compileError(math, err); }, typesetError: ( doc: AbstractMathDocument, math: MathItem, err: Error ) => { doc.typesetError(math, err); }, renderActions: expandable({ find: [STATE.FINDMATH, 'findMath', '', false], compile: [STATE.COMPILED], metrics: [STATE.METRICS, 'getMetrics', '', false], typeset: [STATE.TYPESET], update: [STATE.INSERTED, 'updateDocument', false], }) as RenderActions, }; /** * A bit-field for the actions that have been processed */ public static ProcessBits = BitFieldClass( 'findMath', 'compile', 'getMetrics', 'typeset', 'updateDocument' ); /** * The document managed by this MathDocument */ public document: D; /** * The actual options for this document (with user-supplied ones merged in) */ public options: OptionList; /** * The list of MathItems for this document */ public math: MathList; /** * The list of render actions */ public renderActions: RenderList; /** * The render action promise list */ protected _actionPromises: Promise[]; /** * Promise for the current typeset or conversion action * (used to chain the promise-based calls so they don't * overlap). */ protected _readyPromise: Promise; /** * The bit-field used to tell what steps have been taken on the document (for retries) */ public processed: BitField; /** * The list of input jax for the document */ public inputJax: InputJax[]; /** * The output jax for the document */ public outputJax: OutputJax; /** * The DOM adaptor for the document */ public adaptor: DOMAdaptor; /** * The MathML node factory for the internal MathML representation */ public mmlFactory: MmlFactory; /** * @param {any} document The document (HTML string, parsed DOM, etc.) to be processed * @param {DOMAdaptor} adaptor The DOM adaptor for this document * @param {OptionList} options The options for this document * @class */ constructor(document: D, adaptor: DOMAdaptor, options: OptionList) { const CLASS = this.constructor as typeof AbstractMathDocument; this.document = document; this.options = userOptions(defaultOptions({}, CLASS.OPTIONS), options); this.math = new (this.options['MathList'] || DefaultMathList)(); this.renderActions = RenderList.create( this.options['renderActions'] ); this._actionPromises = []; this._readyPromise = Promise.resolve(); this.processed = new AbstractMathDocument.ProcessBits(); this.outputJax = this.options['OutputJax'] || new DefaultOutputJax(); let inputJax = this.options['InputJax'] || [new DefaultInputJax()]; if (!Array.isArray(inputJax)) { inputJax = [inputJax]; } this.inputJax = inputJax; // // Pass the DOM adaptor to the jax // this.adaptor = adaptor; this.outputJax.setAdaptor(adaptor); this.inputJax.map((jax) => jax.setAdaptor(adaptor)); // // Pass the MmlFactory to the jax // this.mmlFactory = this.options['MmlFactory'] || new MmlFactory(); this.inputJax.map((jax) => jax.setMmlFactory(this.mmlFactory)); // // Do any initialization that requires adaptors or factories // this.outputJax.initialize(); this.inputJax.map((jax) => jax.initialize()); } /** * @returns {string} The kind of document */ public get kind(): string { return (this.constructor as typeof AbstractMathDocument).KIND; } /** * @override */ public addRenderAction(id: string, ...action: any[]) { const [fn, p] = RenderList.action( id, action as RenderAction ); this.renderActions.add(fn, p); } /** * @override */ public removeRenderAction(id: string) { const action = this.renderActions.findID(id); if (action) { this.renderActions.remove(action); } } /** * @override */ public render() { this.clearPromises(); this.renderActions.renderDoc(this); return this; } /** * @override */ public renderPromise() { return this.whenReady(() => handleRetriesFor(async () => { this.render(); await this.actionPromises(); this.clearPromises(); return this; }) ); } /** * @override */ public rerender(start: number = STATE.RERENDER) { this.state(start - 1); this.render(); return this; } /** * @override */ public rerenderPromise(start: number = STATE.RERENDER) { return this.whenReady(() => handleRetriesFor(async () => { this.rerender(start); await this.actionPromises(); this.clearPromises(); return this; }) ); } /** * @override */ public convert(math: string, options: OptionList = {}) { let { format, display, end, ex, em, containerWidth, scale, family } = userOptions( { format: this.inputJax[0].name, display: true, end: STATE.LAST, em: 16, ex: 8, containerWidth: null, scale: 1, family: '', }, options ); if (containerWidth === null) { containerWidth = 80 * ex; } const jax = this.inputJax.reduce( (jax, ijax) => (ijax.name === format ? ijax : jax), null ); const mitem = new this.options.MathItem(math, jax, display); mitem.start.node = this.adaptor.body(this.document); mitem.setMetrics(em, ex, containerWidth, scale); if (family && this.outputJax.options.mtextInheritFont) { mitem.outputData.mtextFamily = family; } if (family && this.outputJax.options.merrorInheritFont) { mitem.outputData.merrorFamily = family; } this.clearPromises(); mitem.convert(this, end); return mitem.typesetRoot || mitem.root; } /** * @override */ public convertPromise(math: string, options: OptionList = {}) { return this.whenReady(() => handleRetriesFor(async () => { const node = this.convert(math, options); await this.actionPromises(); this.clearPromises(); return node; }) ); } /** * @override */ public whenReady(action: () => any): Promise { return (this._readyPromise = this._readyPromise .catch((_) => {}) .then(() => { // // Cache old _readyPromise and replace it with a resolved // promise in case action() calls whenReady(), so we don't get // a circular dependency where the action is waiting on itself. // const ready = this._readyPromise; this._readyPromise = Promise.resolve(); // // Do the action and save its result. // const result = action(); // // Get a promise that returns the result after // any new _readyPromise resolves (in case action // called whenReady() or another function that does). // const promise = this._readyPromise.then(() => result); // // Put back the original promise. // this._readyPromise = ready; // // Return promise that returns the result. The original // _readyPromise will wait on it to complete before it resolves, // since promises that return promises automatically chain. // This inserts any new _readyPromise promises into the // original _readyPromise chain at this point. // return promise; })); } /** * @override */ public actionPromises() { return Promise.all(this._actionPromises); } /** * @override */ public clearPromises() { this._actionPromises = []; } /** * @override */ public savePromise(promise: Promise) { this._actionPromises.push(promise); } /** * @override */ public findMath(_options: OptionList = null) { this.processed.set('findMath'); return this; } /** * @override */ public compile() { if (!this.processed.isSet('compile')) { // // Compile all the math in the list // const recompile = []; for (const math of this.math) { this.compileMath(math); if (math.inputData.recompile !== undefined) { recompile.push(math); } } // // If any were added to the recompile list, // compile them again // for (const math of recompile) { const data = math.inputData.recompile; math.state(data.state); math.inputData.recompile = data; this.compileMath(math); } this.processed.set('compile'); } return this; } /** * @param {MathItem} math The item to compile */ protected compileMath(math: MathItem) { try { math.compile(this); } catch (err) { if (err.retry || err.restart) { throw err; } this.options['compileError'](this, math, err); math.inputData['error'] = err; } } /** * Produce an error using MmlNodes * * @param {MathItem} math The MathItem producing the error * @param {Error} err The Error object for the error */ public compileError(math: MathItem, err: Error) { math.root = this.mmlFactory.create('math', null, [ this.mmlFactory.create( 'merror', { 'data-mjx-error': err.message, title: err.message }, [ this.mmlFactory.create('mtext', null, [ (this.mmlFactory.create('text') as TextNode).setText( 'Math input error' ), ]), ] ), ]); if (math.display) { math.root.attributes.set('display', 'block'); } math.inputData.error = err.message; } /** * @override */ public typeset() { if (!this.processed.isSet('typeset')) { for (const math of this.math) { try { math.typeset(this); } catch (err) { if (err.retry || err.restart) { throw err; } this.options['typesetError'](this, math, err); math.outputData['error'] = err; } } this.processed.set('typeset'); } return this; } /** * Produce an error using HTML * * @param {MathItem} math The MathItem producing the error * @param {Error} err The Error object for the error */ public typesetError(math: MathItem, err: Error) { math.typesetRoot = this.adaptor.node( 'mjx-container', { class: 'MathJax mjx-output-error', jax: this.outputJax.name, }, [ this.adaptor.node( 'span', { 'data-mjx-error': err.message, title: err.message, style: { color: 'red', 'background-color': 'yellow', 'line-height': 'normal', }, }, [this.adaptor.text('Math output error')] ), ] ); if (math.display) { this.adaptor.setAttributes(math.typesetRoot, { style: { display: 'block', margin: '1em 0', 'text-align': 'center', }, }); } math.outputData.error = err.message; } /** * @override */ public getMetrics() { if (!this.processed.isSet('getMetrics')) { this.outputJax.getMetrics(this); this.processed.set('getMetrics'); } return this; } /** * @override */ public updateDocument() { if (!this.processed.isSet('updateDocument')) { for (const math of this.math.reversed()) { math.updateDocument(this); } this.processed.set('updateDocument'); } return this; } /** * @override */ public removeFromDocument(_restore: boolean = false) { return this; } /** * @override */ public state(state: number, restore: boolean = false) { for (const math of this.math) { math.state(state, restore); } if (state < STATE.INSERTED) { this.processed.clear('updateDocument'); } if (state < STATE.TYPESET) { this.processed.clear('typeset'); this.processed.clear('getMetrics'); } if (state < STATE.COMPILED) { this.processed.clear('compile'); } if (state < STATE.FINDMATH) { this.processed.clear('findMath'); } return this; } /** * @override */ public reset(options: ResetList = { processed: true }) { options = userOptions(Object.assign({}, resetOptions), options); if (options.all) { Object.assign(options, resetAllOptions); } if (options.processed) { this.processed.reset(); } if (options.inputJax) { this.inputJax.forEach((jax) => jax.reset(...options.inputJax)); } if (options.outputJax) { this.outputJax.reset(...options.outputJax); } return this; } /** * @override */ public clear() { this.reset(); this.math.clear(); return this; } /** * @override */ public done() { return Promise.resolve(); } /** * @override */ public concat(list: MathList) { this.math.merge(list); return this; } /** * @override */ public clearMathItemsWithin(containers: ContainerList) { const items = this.getMathItemsWithin(containers); for (const item of items.slice(0).reverse()) { item.clear(); } this.math.remove(...items); return items; } /** * @override */ public getMathItemsWithin(elements: ContainerList) { if (!Array.isArray(elements)) { elements = [elements]; } const adaptor = this.adaptor; const items = [] as MathItem[]; const containers = adaptor.getElements(elements, this.document); ITEMS: for (const item of this.math) { for (const container of containers) { if (item.start.node && adaptor.contains(container, item.start.node)) { items.push(item); continue ITEMS; } } } return items; } } /** * The constructor type for a MathDocument * * @template D The MathDocument type this constructor is for */ export interface MathDocumentConstructor< D extends MathDocument, > { KIND: string; OPTIONS: OptionList; ProcessBits: typeof BitField; new (...args: any[]): D; }