site/node_modules/mathjax-full/ts/a11y/explorer/MouseExplorer.ts
2024-10-14 08:09:33 +02:00

225 lines
5.5 KiB
TypeScript

/*************************************************************
*
* Copyright (c) 2009-2022 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.
*/
/**
* @fileoverview 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 '../sre.js';
/**
* Interface for mouse explorers. Adds the necessary mouse events.
* @interface
* @extends {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;
}
/**
* @constructor
* @extends {AbstractExplorer}
*
* @template T The type that is consumed by the Region of this explorer.
*/
export abstract class AbstractMouseExplorer<T> extends AbstractExplorer<T> 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.
* @constructor
* @extends {AbstractMouseExplorer}
*/
export abstract class Hoverer<T> extends AbstractMouseExplorer<T> {
/**
* Remember the last position to avoid flickering.
* @type {[number, number]}
*/
protected coord: [number, number];
/**
* @constructor
* @extends {AbstractMouseExplorer<T>}
*
* @param {A11yDocument} document The current document.
* @param {Region<T>} 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.
*
* @template T
*/
protected constructor(public document: A11yDocument,
protected region: Region<T>,
protected node: HTMLElement,
protected nodeQuery: (node: HTMLElement) => boolean,
protected nodeAccess: (node: HTMLElement) => T) {
super(document, region, node);
}
/**
* @override
*/
public MouseOut(event: MouseEvent) {
if (event.clientX === this.coord[0] &&
event.clientY === this.coord[1]) {
return;
}
this.highlighter.unhighlight();
this.region.Hide();
super.MouseOut(event);
}
/**
* @override
*/
public MouseOver(event: MouseEvent) {
super.MouseOver(event);
let target = event.target as HTMLElement;
this.coord = [event.clientX, event.clientY];
let [node, kind] = this.getNode(target);
if (!node) {
return;
}
this.highlighter.unhighlight();
this.highlighter.highlight([node]);
this.region.Update(kind);
this.region.Show(node, this.highlighter);
}
/**
* 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.
* @return {[HTMLElement, T]} Node and output pair if successful.
*/
public getNode(node: HTMLElement): [HTMLElement, T] {
let 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)];
}
let 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).
* @constructor
* @extends {Hoverer}
*/
export class ValueHoverer extends Hoverer<string> { }
/**
* Hoverer that displays node content (e.g., for magnification).
* @constructor
* @extends {Hoverer}
*/
export class ContentHoverer extends Hoverer<HTMLElement> { }
/**
* Highlights maction nodes on hovering.
* @constructor
* @extends {Hoverer}
*/
export class FlameHoverer extends Hoverer<void> {
/**
* @override
*/
protected constructor(
public document: A11yDocument,
_ignore: any,
protected node: HTMLElement) {
super(document, new DummyRegion(document), node,
x => this.highlighter.isMactionNode(x),
() => {});
}
}