/************************************************************* * * 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 based on mouse events. * * @author v.sorge@mathjax.org (Volker Sorge) */ import { A11yDocument, DummyRegion, Region } from './Region.js'; import { Explorer, AbstractExplorer } from './Explorer.js'; import { ExplorerPool } from './ExplorerPool.js'; import '../sre.js'; /** * Interface for mouse explorers. Adds the necessary mouse events. * * @interface * @augments {Explorer} */ export interface MouseExplorer extends Explorer { /** * Function to be executed on mouse over. * * @param {MouseEvent} event The mouse event. */ MouseOver(event: MouseEvent): void; /** * Function to be executed on mouse out. * * @param {MouseEvent} event The mouse event. */ MouseOut(event: MouseEvent): void; } /** * @class * @augments {AbstractExplorer} * * @template T The type that is consumed by the Region of this explorer. */ export abstract class AbstractMouseExplorer extends AbstractExplorer implements MouseExplorer { /** * @override */ protected events: [string, (x: Event) => void][] = super.Events().concat([ ['mouseover', this.MouseOver.bind(this)], ['mouseout', this.MouseOut.bind(this)], ]); /** * @override */ public MouseOver(_event: MouseEvent) { this.Start(); } /** * @override */ public MouseOut(_event: MouseEvent) { this.Stop(); } } /** * Exploration via hovering. * * @class * @augments {AbstractMouseExplorer} * * @template T */ export abstract class Hoverer extends AbstractMouseExplorer { /** * @class * @augments {AbstractMouseExplorer} * * @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 {(node: HTMLElement) => boolean} nodeQuery Predicate on nodes that * will fire the hoverer. * @param {(node: HTMLElement) => T} nodeAccess Accessor to extract node value * that is passed to the region. */ protected constructor( public document: A11yDocument, public pool: ExplorerPool, public region: Region, protected node: HTMLElement, protected nodeQuery: (node: HTMLElement) => boolean, protected nodeAccess: (node: HTMLElement) => T ) { super(document, pool, region, node); } /** * @override */ public MouseOut(event: MouseEvent) { this.highlighter.unhighlight(); this.region.Hide(); super.MouseOut(event); } /** * @override */ public MouseOver(event: MouseEvent) { super.MouseOver(event); const target = event.target as HTMLElement; const [node, kind] = this.getNode(target); if (!node) { return; } this.highlighter.unhighlight(); this.highlighter.highlight([node]); this.region.Update(kind); this.region.Show(node); } /** * Retrieves the closest node on which the node query fires. Thereby closest * is defined as: * 1. The node or its ancestor on which the query is true. * 2. In case 1 does not exist the left-most child on which query is true. * 3. Otherwise fails. * * @param {HTMLElement} node The node on which the mouse event fired. * @returns {[HTMLElement, T]} Node and output pair if successful. */ public getNode(node: HTMLElement): [HTMLElement, T] { const original = node; while (node && node !== this.node) { if (this.nodeQuery(node)) { return [node, this.nodeAccess(node)]; } node = node.parentNode as HTMLElement; } node = original; while (node) { if (this.nodeQuery(node)) { return [node, this.nodeAccess(node)]; } const child = node.childNodes[0] as HTMLElement; node = child && child.tagName === 'defs' // This is for SVG. ? (node.childNodes[1] as HTMLElement) : child; } return [null, null]; } } /** * Hoverer that displays information on nodes (e.g., as tooltips). * * @class * @augments {Hoverer} */ export class ValueHoverer extends Hoverer {} /** * Hoverer that displays node content (e.g., for magnification). * * @class * @augments {Hoverer} */ export class ContentHoverer extends Hoverer {} /** * Highlights maction nodes on hovering. * * @class * @augments {Hoverer} */ export class FlameHoverer extends Hoverer { /** * @override */ protected constructor( public document: A11yDocument, public pool: ExplorerPool, _ignore: any, protected node: HTMLElement ) { super( document, pool, new DummyRegion(document), node, (x) => this.highlighter.isMactionNode(x), () => {} ); } }