/************************************************************* * * Copyright (c) 2009-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 Explorers for A11Y purposes. * * @author v.sorge@mathjax.org (Volker Sorge) */ import { A11yDocument, Region } from './Region.js'; import { Highlighter } from './Highlighter.js'; import type { ExplorerPool } from './ExplorerPool.js'; /** * A11y explorers. * * @interface */ export interface Explorer { /** * Flag indicating if the explorer is active. * * @type {boolean} */ active: boolean; /** * Flag indicating if event bubbling is stopped. * * @type {boolean} */ stoppable: boolean; /** * The pool the explorer belongs to. * * @type {ExplorerPool} */ pool: ExplorerPool; /** * Attaches navigator and its event handlers to a node. */ Attach(): void; /** * Detaches navigator and its event handlers to a node. */ Detach(): void; /** * Starts the explorer. */ Start(): void; /** * Stops the explorer. */ Stop(): void; /** * Adds the events of the explorer to the node's event listener. */ AddEvents(): void; /** * Removes the events of the explorer from the node's event listener. */ RemoveEvents(): void; /** * Update the explorer after state changes. * * @param {boolean=} force Forces the update in any case. (E.g., even if * explorer is inactive.) */ Update(force?: boolean): void; } /** * Abstract class implementing the very basic explorer functionality. * * Explorers use creator pattern to ensure they automatically attach themselves * to their node. This class provides the create method and is consequently not * declared abstract. * * @class * @implements {Explorer} * * @template T The type that is consumed by the Region of this explorer. */ export class AbstractExplorer implements Explorer { /** * @override */ public stoppable: boolean = true; /** * Named events and their functions. * * @type {[string, (x: Event) => void][]} */ protected events: [string, (x: Event) => void][] = []; /** * @returns {Highlighter} The Sre highlighter associated with the walker. */ protected get highlighter(): Highlighter { return this.pool.highlighter; } /** * Flag if explorer is active. * * @type {boolean} */ private _active: boolean = false; /** * Stops event bubbling. * * @param {Event} event The event that is stopped. */ protected static stopEvent(event: Event) { if (event.preventDefault) { event.preventDefault(); } else { event.returnValue = false; } if (event.stopImmediatePropagation) { event.stopImmediatePropagation(); } else if (event.stopPropagation) { event.stopPropagation(); } event.cancelBubble = true; } /** * Creator pattern for explorers. * * @param {A11yDocument} document The current document. * @param {ExplorerPool} pool The explorer pool. * @param {Region} region A region to display results. * @param {HTMLElement} node The node on which the explorer works. * @param {any[]} rest Remaining information. * @returns {Explorer} An object of the particular explorer class. * * @template T */ public static create( document: A11yDocument, pool: ExplorerPool, region: Region, node: HTMLElement, ...rest: any[] ): Explorer { const explorer = new this(document, pool, region, node, ...rest); return explorer; } /** * @class * @param {A11yDocument} document The current document. * @param {ExplorerPool} pool The explorer pool. * @param {Region} region A region to display results. * @param {HTMLElement} node The node on which the explorer works. * @param {any[]} _rest Remaining information. */ protected constructor( public document: A11yDocument, public pool: ExplorerPool, public region: Region, protected node: HTMLElement, ..._rest: any[] ) {} /** * @returns {[string, (x: Event) => void][]} The events associated with this * explorer. */ protected Events(): [string, (x: Event) => void][] { return this.events; } /** * @override */ public get active(): boolean { return this._active; } /** * @override */ public set active(flag: boolean) { this._active = flag; } /** * @override */ public Attach() { this.AddEvents(); } /** * @override */ public Detach() { this.RemoveEvents(); } /** * @override */ public Start() { this.active = true; } /** * @override */ public Stop() { if (this.active) { this.region.Clear(); this.region.Hide(); this.active = false; } } /** * @override */ public AddEvents() { for (const [eventkind, eventfunc] of this.events) { this.node.addEventListener(eventkind, eventfunc); } } /** * @override */ public RemoveEvents() { for (const [eventkind, eventfunc] of this.events) { this.node.removeEventListener(eventkind, eventfunc); } } /** * @override */ public Update(_force: boolean = false): void {} /** * Stops the events of this explorer from bubbling. * * @param {Event} event The event to stop. */ protected stopEvent(event: Event) { if (this.stoppable) { AbstractExplorer.stopEvent(event); } } }