diff --git a/mathjax3-ts/a11y/explorer.ts b/mathjax3-ts/a11y/explorer.ts index d44c44157..562837d73 100644 --- a/mathjax3-ts/a11y/explorer.ts +++ b/mathjax3-ts/a11y/explorer.ts @@ -32,8 +32,9 @@ import {BitField} from '../util/BitField.js'; import {SerializedMmlVisitor} from '../core/MmlTree/SerializedMmlVisitor.js'; import {Explorer} from './explorer/Explorer.js'; -import {AbstractKeyExplorer, Magnifier, SpeechExplorer} from './explorer/KeyExplorer.js'; -import {FlameHoverer} from './explorer/MouseExplorer.js'; +import * as ke from './explorer/KeyExplorer.js'; +import * as me from './explorer/MouseExplorer.js'; +import {TreeColorer, FlameColorer} from './explorer/TreeExplorer.js'; import {LiveRegion, ToolTip, HoverRegion} from './explorer/Region.js'; /** @@ -66,6 +67,7 @@ export interface ExplorerMathItem extends HTMLMATHITEM { */ explorable(document: HTMLDOCUMENT): void; + attachExplorers(document: HTMLDOCUMENT): void; } /** @@ -87,7 +89,12 @@ export function ExplorerMathItemMixin>( /** * The Explorer objects for this math item */ - protected explorers: Explorer[] = []; + protected explorers: {[key: string]: Explorer} = {}; + + /** + * The currently attached explorers + */ + protected attached: Explorer[] = []; /** * True when a rerendered element should restart the explorer @@ -117,41 +124,38 @@ export function ExplorerMathItemMixin>( this.typesetRoot.setAttribute('sre-explorer-id', this.savedId); this.savedId = null; } - this.addExplorers( - SpeechExplorer.create(document, document.explorerObjects.region, node, mml), - SpeechExplorer.create(document, document.explorerObjects.region2, node, mml), - Magnifier.create(document, document.explorerObjects.magnifier, node, mml), - FlameHoverer.create(document, null, node) - ); + // Init explorers: + this.explorers = initExplorers(document, node, mml); + this.attachExplorers(document); this.state(STATE.EXPLORER); } - /** - * Adds a list of explorers and makes sure the right one stops propagating. - * @param {Explorer[]} ...explorers + * Attaches the explorers that are currently meant to be active given + * the document options. Detaches all others. + * @param {ExplorerMathDocument} document The current document. */ - private addExplorers(...explorers: Explorer[]) { - this.explorers = explorers; - if (explorers.length <= 1) return; - let lastKeyExplorer = null; - for (let explorer of this.explorers) { - if (!(explorer instanceof AbstractKeyExplorer)) continue; - if (lastKeyExplorer) { - lastKeyExplorer.stoppable = false; + public attachExplorers(document: ExplorerMathDocument) { + this.attached = []; + for (let key of Object.keys(this.explorers)) { + let explorer = this.explorers[key]; + if (document.options.a11y[key]) { + explorer.Attach(); + this.attached.push(explorer); + } else { + explorer.Detach(); } - lastKeyExplorer = explorer; } + this.addExplorers(this.attached); } - /** * @override */ public rerender(document: ExplorerMathDocument, start: number = STATE.RERENDER) { this.savedId = this.typesetRoot.getAttribute('sre-explorer-id'); this.refocus = (window.document.activeElement === this.typesetRoot); - for (let explorer of this.explorers) { + for (let explorer of this.attached) { if (explorer.active) { this.restart = true; explorer.Stop(); @@ -166,26 +170,29 @@ export function ExplorerMathItemMixin>( public updateDocument(document: ExplorerMathDocument) { super.updateDocument(document); this.refocus && this.typesetRoot.focus(); - this.restart && this.explorers.forEach(x => x.Start()); + this.restart && this.attached.forEach(x => x.Start()); this.refocus = this.restart = false; } - }; - -} + /** + * Adds a list of explorers and makes sure the right one stops propagating. + * @param {Explorer[]} explorers The active explorers to be added. + */ + private addExplorers(explorers: Explorer[]) { + if (explorers.length <= 1) return; + let lastKeyExplorer = null; + for (let explorer of this.attached) { + if (!(explorer instanceof ke.AbstractKeyExplorer)) continue; + if (lastKeyExplorer) { + lastKeyExplorer.stoppable = false; + } + lastKeyExplorer = explorer; + lastKeyExplorer.stoppable = true; + } + } -/*==========================================================================*/ + }; -/** - * The objects needed for the explorer - */ -export type ExplorerObjects = { - region?: LiveRegion, - region2?: LiveRegion, - tooltip?: ToolTip, - tooltip2?: ToolTip, - tooltip3?: ToolTip, - magnifier?: HoverRegion } /** @@ -196,7 +203,7 @@ export interface ExplorerMathDocument extends HTMLDOCUMENT { /** * The objects needed for the explorer */ - explorerObjects: ExplorerObjects; + explorerRegions: ExplorerRegions; /** * Add the Explorer to the MathItems in the MathDocument @@ -225,21 +232,34 @@ export function ExplorerMathDocumentMixin visitor.visitTree(node)); this.options.MathItem = ExplorerMathItemMixin(this.options.MathItem, toMathML); - this.explorerObjects = { - region: new LiveRegion(this), - region2: new LiveRegion(this), - tooltip: new ToolTip(this), - tooltip2: new ToolTip(this), - tooltip3: new ToolTip(this), - magnifier: new HoverRegion(this) - }; + this.explorerRegions = initExplorerRegions(this); } /** @@ -313,3 +326,180 @@ export function ExplorerHandler(handler: HANDLER, MmlJax: MATHML = null) { handler.documentClass = ExplorerMathDocumentMixin(handler.documentClass as any); return handler; } + + +/*==========================================================================*/ + +/** + * The regions objects needed for the explorers. + */ +export type ExplorerRegions = { + speechRegion?: LiveRegion, + brailleRegion?: LiveRegion, + magnifier?: HoverRegion, + tooltip1?: ToolTip, + tooltip2?: ToolTip, + tooltip3?: ToolTip +} + + +/** + * Initializes the regions needed for a document. + * @param {ExplorerMathDocument} document The current document. + */ +function initExplorerRegions(document: ExplorerMathDocument) { + return { + speechRegion: new LiveRegion(document), + brailleRegion: new LiveRegion(document), + magnifier: new HoverRegion(document), + tooltip1: new ToolTip(document), + tooltip2: new ToolTip(document), + tooltip3: new ToolTip(document) + }; +} + + + +/** + * Type of explorer initialization methods. + * @type {(ExplorerMathDocument, HTMLElement, any[]): Explorer} +*/ +type ExplorerInit = (doc: ExplorerMathDocument, + node: HTMLElement, ...rest: any[]) => Explorer; + +/** + * Generation methods for all MathJax explorers available via option settings. + */ +let allExplorers: {[options: string]: ExplorerInit} = { + speech: (doc: ExplorerMathDocument, node: HTMLElement, ...rest: any[]) => { + let explorer = ke.SpeechExplorer.create( + doc, doc.explorerRegions.speechRegion, node, ...rest) as ke.SpeechExplorer; + explorer.speechGenerator.setOptions({locale: 'en', domain: 'mathspeak', + style: 'default', modality: 'speech'}); + explorer.showRegion = 'subtitles'; + return explorer; + }, + braille: (doc: ExplorerMathDocument, node: HTMLElement, ...rest: any[]) => { + let explorer = ke.SpeechExplorer.create( + doc, doc.explorerRegions.brailleRegion, node, ...rest) as ke.SpeechExplorer; + explorer.speechGenerator.setOptions({locale: 'nemeth', domain: 'default', + style: 'default', modality: 'braille'}); + explorer.showRegion = 'viewbraille'; + return explorer; + }, + keymagnifier: (doc: ExplorerMathDocument, node: HTMLElement, ...rest: any[]) => + ke.Magnifier.create(doc, doc.explorerRegions.magnifier, node, ...rest), + mousemagnifier: (doc: ExplorerMathDocument, node: HTMLElement, ...rest: any[]) => + me.ContentHoverer.create(doc, doc.explorerRegions.magnifier, node, + (x: HTMLElement) => x.hasAttribute('data-semantic-type'), + (x: HTMLElement) => x), + hover: (doc: ExplorerMathDocument, node: HTMLElement, ...rest: any[]) => + me.FlameHoverer.create(doc, null, node), + infoType: (doc: ExplorerMathDocument, node: HTMLElement, ...rest: any[]) => + me.ValueHoverer.create(doc, doc.explorerRegions.tooltip1, node, + (x: HTMLElement) => x.hasAttribute('data-semantic-type'), + (x: HTMLElement) => x.getAttribute('data-semantic-type')), + infoRole: (doc: ExplorerMathDocument, node: HTMLElement, ...rest: any[]) => + me.ValueHoverer.create(doc, doc.explorerRegions.tooltip2, node, + (x: HTMLElement) => x.hasAttribute('data-semantic-role'), + (x: HTMLElement) => x.getAttribute('data-semantic-role')), + infoPrefix: (doc: ExplorerMathDocument, node: HTMLElement, ...rest: any[]) => + me.ValueHoverer.create(doc, doc.explorerRegions.tooltip3, node, + (x: HTMLElement) => x.hasAttribute('data-semantic-prefix'), + (x: HTMLElement) => x.getAttribute('data-semantic-prefix')), + flame: (doc: ExplorerMathDocument, node: HTMLElement, ...rest: any[]) => + FlameColorer.create(doc, null, node), + treecoloring: (doc: ExplorerMathDocument, node: HTMLElement, ...rest: any[]) => + TreeColorer.create(doc, null, node, ...rest) +}; + + +/** + * Initialises explorers for a document. + * @param {ExplorerMathDocument} document The target document. + * @param {HTMLElement} node The node explorers will be attached to. + * @param {string} mml The corresponding Mathml node as a string. + * @return {Explorer[]} A list of initialised explorers. + */ +function initExplorers(document: ExplorerMathDocument, node: HTMLElement, mml: string): {[key: string]: Explorer} { + let explorers: {[key: string]: Explorer} = {}; + for (let key of Object.keys(allExplorers)) { + explorers[key] = allExplorers[key](document, node, mml); + } + return explorers; +} + + +/* Context Menu Interactions */ + +/** + * Sets a list of a11y options for a given document. + * @param {HTMLDOCUMENT} document The current document. + * @param {{[key: string]: any}} options Association list for a11y option value pairs. + */ +export function setA11yOptions(document: HTMLDOCUMENT, options: {[key: string]: any}) { + for (let key in options) { + if (document.options.a11y[key] !== undefined) { + setA11yOption(document, key, options[key]); + } + } + // Reinit explorers + for (let item of document.math ) { + (item as ExplorerMathItem).attachExplorers(document as ExplorerMathDocument); + } +} + + +/** + * Sets a single a11y option for a menu name. + * @param {HTMLDOCUMENT} document The current document. + * @param {string} option The option name in the menu. + * @param {string|boolean} value The new value. + */ +export function setA11yOption(document: HTMLDOCUMENT, option: string, value: string|boolean) { + switch (option) { + case 'magnification': + switch (value) { + case 'None': + document.options.a11y.magnifier = value; + document.options.a11y.keymagnifier = false; + document.options.a11y.mousemagnifier = false; + break; + case 'Keyboard': + document.options.a11y.magnifier = value; + document.options.a11y.keymagnifier = true; + document.options.a11y.mousemagnifier = false; + break; + case 'Mouse': + document.options.a11y.magnifier = value; + document.options.a11y.keymagnifier = false; + document.options.a11y.mousemagnifier = true; + break; + } + break; + case 'highlight': + switch (value) { + case 'None': + document.options.a11y.highlight = value; + document.options.a11y.hover = false; + document.options.a11y.flame = false; + break; + case 'Hover': + document.options.a11y.highlight = value; + document.options.a11y.hover = true; + document.options.a11y.flame = false; + break; + case 'Flame': + document.options.a11y.highlight = value; + document.options.a11y.hover = false; + document.options.a11y.flame = true; + break; + } + break; + case 'magnify': + document.options.a11y.magnify = parseFloat(value as string); + break; + default: + document.options.a11y[option] = value; + } +} diff --git a/mathjax3-ts/a11y/explorer/Explorer.ts b/mathjax3-ts/a11y/explorer/Explorer.ts index fcfa8ffb8..38939b9a7 100644 --- a/mathjax3-ts/a11y/explorer/Explorer.ts +++ b/mathjax3-ts/a11y/explorer/Explorer.ts @@ -155,7 +155,6 @@ export class AbstractExplorer implements Explorer { region: Region, node: HTMLElement, ...rest: any[]): Explorer { let explorer = new this(document, region, node, ...rest); - explorer.Attach(); return explorer; } diff --git a/mathjax3-ts/a11y/explorer/KeyExplorer.ts b/mathjax3-ts/a11y/explorer/KeyExplorer.ts index 878f67617..70ceb1936 100644 --- a/mathjax3-ts/a11y/explorer/KeyExplorer.ts +++ b/mathjax3-ts/a11y/explorer/KeyExplorer.ts @@ -157,7 +157,9 @@ export class SpeechExplorer extends AbstractKeyExplorer { * The SRE speech generator associated with the walker. * @type {sre.SpeechGenerator} */ - protected speechGenerator: sre.SpeechGenerator; + public speechGenerator: sre.SpeechGenerator; + + public showRegion: string = 'subtitles'; /** * Flag in case the start method is triggered before the walker is fully @@ -185,12 +187,14 @@ export class SpeechExplorer extends AbstractKeyExplorer { */ public Start() { super.Start(); - this.speechGenerator = new sre.DirectSpeechGenerator(); + let options = this.speechGenerator.getOptions(); + this.speechGenerator = sre.SpeechGeneratorFactory.generator('Direct'); + this.speechGenerator.setOptions(options); this.walker = sre.WalkerFactory.walker('table', this.node, this.speechGenerator, this.highlighter, this.mml); this.walker.activate(); this.Update(); - if (this.document.options.a11y.subtitles) { + if (this.document.options.a11y[this.showRegion]) { this.region.Show(this.node, this.highlighter); } this.restarted = true; @@ -215,7 +219,7 @@ export class SpeechExplorer extends AbstractKeyExplorer { let speech = walker.speech(); this.node.setAttribute('hasspeech', 'true'); this.Update(); - if (this.restarted && this.document.options.a11y.subtitles) { + if (this.restarted && this.document.options.a11y[this.showRegion]) { this.region.Show(this.node, this.highlighter); } }).catch((error: Error) => console.log(error.message)); @@ -237,7 +241,7 @@ export class SpeechExplorer extends AbstractKeyExplorer { this.stopEvent(event); return; } - if (code === 32 && event.shiftKey) { + if (code === 32 && event.shiftKey || code === 13) { this.Start(); this.stopEvent(event); } @@ -256,9 +260,10 @@ export class SpeechExplorer extends AbstractKeyExplorer { * Initialises the SRE walker. */ private initWalker() { - this.speechGenerator = new sre.TreeSpeechGenerator(); - let dummy = sre.WalkerFactory.walker('dummy', - this.node, this.speechGenerator, this.highlighter, this.mml); + this.speechGenerator = sre.SpeechGeneratorFactory.generator('Tree'); + let dummy = sre.WalkerFactory.walker( + 'dummy', this.node, this.speechGenerator, this.highlighter, this.mml); + this.walker = dummy; this.Speech(dummy); } @@ -281,8 +286,9 @@ export class Magnifier extends AbstractKeyExplorer { protected node: HTMLElement, private mml: HTMLElement) { super(document, region, node); - this.walker = sre.WalkerFactory.walker('table', - this.node, new sre.DummySpeechGenerator(), this.highlighter, this.mml); + this.walker = sre.WalkerFactory.walker( + 'table', this.node, sre.SpeechGeneratorFactory.generator('Dummy'), + this.highlighter, this.mml); } /** @@ -340,7 +346,7 @@ export class Magnifier extends AbstractKeyExplorer { this.stopEvent(event); return; } - if (code === 32 && event.shiftKey) { + if (code === 32 && event.shiftKey || code === 13) { this.Start(); this.stopEvent(event); } diff --git a/mathjax3-ts/a11y/explorer/Region.ts b/mathjax3-ts/a11y/explorer/Region.ts index 12180abfd..6f5f1b5bf 100644 --- a/mathjax3-ts/a11y/explorer/Region.ts +++ b/mathjax3-ts/a11y/explorer/Region.ts @@ -376,7 +376,6 @@ export class HoverRegion extends AbstractRegion { */ constructor(public document: A11yDocument) { super(document); - this.div.style.fontSize = document.options.a11y.magnify + '%'; this.inner.style.lineHeight = '0'; } @@ -430,6 +429,7 @@ export class HoverRegion extends AbstractRegion { * @override */ public Show(node: HTMLElement, highlighter: sre.Highlighter) { + this.div.style.fontSize = this.document.options.a11y.magnify + '%'; this.Update(node); super.Show(node, highlighter); } diff --git a/mathjax3-ts/a11y/explorer/TreeExplorer.ts b/mathjax3-ts/a11y/explorer/TreeExplorer.ts new file mode 100644 index 000000000..cab819a2d --- /dev/null +++ b/mathjax3-ts/a11y/explorer/TreeExplorer.ts @@ -0,0 +1,124 @@ +/************************************************************* + * + * Copyright (c) 2009-2019 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 Tree Explorers allow to switch on effects on the entire + * expression tree. + * + * @author v.sorge@mathjax.org (Volker Sorge) + */ + + +import {A11yDocument, Region} from './Region.js'; +import {Explorer, AbstractExplorer} from './Explorer.js'; +import {sreReady} from '../sre.js'; + + +export interface TreeExplorer extends Explorer { + +} + + +export class AbstractTreeExplorer extends AbstractExplorer { + + /** + * @override + */ + protected constructor(public document: A11yDocument, + protected region: Region, + protected node: HTMLElement, + protected mml: HTMLElement) { + super(document, null, node); + } + + /** + * @override + */ + readonly stoppable = false; + + + /** + * @override + */ + public Attach() { + super.Attach(); + this.Start(); + } + + /** + * @override + */ + public Detach() { + this.Stop(); + super.Detach(); + } + +} + + +export class FlameColorer extends AbstractTreeExplorer { + + /** + * @override + */ + public Start() { + if (this.active) return; + this.active = true; + this.highlighter.highlightAll(this.node); + } + + /** + * @override + */ + public Stop() { + if (this.active) { + this.highlighter.unhighlightAll(this.node); + } + this.active = false; + } + +} + + +export class TreeColorer extends AbstractTreeExplorer { + + /** + * @override + */ + public Start() { + if (this.active) return; + this.active = true; + let generator = sre.SpeechGeneratorFactory.generator('Color'); + if (!this.node.hasAttribute('hasforegroundcolor')) { + generator.generateSpeech(this.node, this.mml); + this.node.setAttribute('hasforegroundcolor', 'true'); + } + this.highlighter.colorizeAll(this.node); + } + + /** + * @override + */ + public Stop() { + if (this.active) { + this.highlighter.uncolorizeAll(this.node); + } + this.active = false; + } + +} diff --git a/mathjax3-ts/a11y/sre_browser.d.ts b/mathjax3-ts/a11y/sre_browser.d.ts index e6c48c4a5..a4ff968f0 100644 --- a/mathjax3-ts/a11y/sre_browser.d.ts +++ b/mathjax3-ts/a11y/sre_browser.d.ts @@ -1,35 +1,30 @@ - declare namespace sre { export type colorType = {color: string, alpha: number}; export type colorString = {foreground: string, background: string}; interface SpeechGenerator { + generateSpeech(node: HTMLElement, xml: HTMLElement): string; speech(): string; + setOptions(options: Object): void; + getOptions(): Object; } - class AbstractSpeechGenerator implements SpeechGenerator { - speech(): string; - } - - class TreeSpeechGenerator extends AbstractSpeechGenerator { } - - class DirectSpeechGenerator extends AbstractSpeechGenerator { } - - class DummySpeechGenerator extends AbstractSpeechGenerator { } - - interface Highlighter { highlight(nodes: Node[]): void; unhighlight(): void; + highlightAll(node: Node): void; + unhighlightAll(node: Node): void; colorString(): colorString; isMactionNode(node: Node): boolean; + colorizeAll(node: Node): void; + uncolorizeAll(node: Node): void; } interface Focus { getNodes(): Node[]; } - + interface Walker { activate(): void; deactivate(): void; @@ -46,7 +41,10 @@ declare namespace sre.WalkerFactory { generator: SpeechGenerator, highlighter: Highlighter, mml: Node): Walker; - +} + +declare namespace sre.SpeechGeneratorFactory { + export function generator(kind: string): sre.SpeechGenerator; } declare namespace sre.Engine { diff --git a/mathjax3-ts/ui/menu/Menu.ts b/mathjax3-ts/ui/menu/Menu.ts index 6f64d538b..6623313b2 100644 --- a/mathjax3-ts/ui/menu/Menu.ts +++ b/mathjax3-ts/ui/menu/Menu.ts @@ -66,16 +66,25 @@ export interface MenuSettings { ctrl: boolean; shift: boolean; scale: string; + autocollapse: boolean; + collapsible: boolean; + inTabOrder: boolean; + // A11y settings + backgroundColor: string; + braille: boolean; explorer: boolean; + foregroundColor: string; highlight: string; - background: string; - foreground: string; + infoPrefix: boolean; + infoRole: boolean; + infoType: boolean; + magnification: string; + magnify: string; speech: boolean; - subtitles: boolean; speechrules: string; - autocollapse: boolean; - collapsible: boolean; - inTabOrder: boolean; + subtitles: boolean; + treecoloring: boolean; + viewbraille: boolean; } export type HTMLMATHITEM = MathItem; @@ -108,16 +117,10 @@ export class Menu { ctrl: false, shift: false, scale: 1, - explorer: false, - highlight: 'None', - background: 'Blue', - foreground: 'Black', - speech: true, - subtitles: false, - speechrules: 'mathspeak-default', autocollapse: false, collapsible: false, - inTabOrder: true, + inTabOrder: true + explorer: false, }, jax: { CHTML: null, @@ -360,6 +363,9 @@ export class Menu { const jax = this.document.outputJax; this.jax[jax.name] = jax; this.settings.renderer = jax.name; + if (window.MathJax._.a11y && window.MathJax._.a11y.explorer) { + Object.assign(this.settings, this.document.options.a11y); + } this.settings.scale = jax.options.scale; this.defaultSettings = Object.assign({}, this.settings); } @@ -383,15 +389,34 @@ export class Menu { this.variable('shift'), this.variable ('scale', (scale: string) => this.setScale(scale)), this.variable('explorer', (explore: boolean) => this.setExplorer(explore)), - this.variable ('highlight'), - this.variable ('background', (background: string) => - this.document.options.a11y.backgroundColor = background), - this.variable ('foreground', (foreground: string) => - this.document.options.a11y.foregroundColor = foreground), - this.variable('speech'), + this.variable ('highlight', (highlight: string) => + this.setA11y({'highlight': highlight})), + this.variable ('backgroundColor', (background: string) => + this.setA11y({'backgroundColor': background})), + this.variable ('foregroundColor', (foreground: string) => + this.setA11y({'foregroundColor': foreground})), + this.variable('speech', (speech: boolean) => + this.setA11y({'speech': speech})), this.variable('subtitles', (subtitles: boolean) => - this.document.options.a11y.subtitles = subtitles), - this.variable ('speechrules'), + this.setA11y({'subtitles': subtitles})), + this.variable('braille', (braille: boolean) => + this.setA11y({'braille': braille})), + this.variable('viewbraille', (viewbraille: boolean) => + this.setA11y({'viewbraille': viewbraille})), + this.variable ('speechrules', (speechrules: string) => + this.setA11y({'speechrules': speechrules})), + this.variable ('magnification', (magnification: string) => + this.setA11y({'magnification': magnification})), + this.variable ('magnify', (magnify: string) => + this.setA11y({'magnify': magnify})), + this.variable('treecoloring', (treecoloring: boolean) => + this.setA11y({'treecoloring': treecoloring})), + this.variable('infoType', (infoType: boolean) => + this.setA11y({'infoType': infoType})), + this.variable('infoRole', (infoRole: boolean) => + this.setA11y({'infoRole': infoRole})), + this.variable('infoPrefix', (infoPrefix: boolean) => + this.setA11y({'infoPrefix': infoPrefix})), this.variable('autocollapse'), this.variable('collapsible', (collapse: boolean) => this.setCollapsible(collapse)), this.variable('inTabOrder', (tab: boolean) => this.setTabOrder(tab)) @@ -436,33 +461,55 @@ export class Menu { this.command('Reset', 'Reset to defaults', () => this.resetDefaults()) ]), this.submenu('Accessibility', 'Accessibility', [ - this.submenu('Explorer', 'Explorer', [ - this.checkbox('Active', 'Active', 'explorer'), - this.rule(), - this.submenu('Highlight', 'Highlight', this.radioGroup('highlight', [ - ['None'], ['Hover'], ['Flame'] - ]), true), - this.submenu('Background', 'Background', this.radioGroup('background', [ - ['Blue'], ['Red'], ['Green'], ['Yellow'], ['Cyan'], ['Magenta'], ['White'], ['Black'] - ])), - this.submenu('Foreground', 'Foreground', this.radioGroup('foreground', [ - ['Black'], ['White'], ['Magenta'], ['Cyan'], ['Yellow'], ['Green'], ['Red'], ['Blue'] - ])), - this.rule(), + this.checkbox('Activate', 'Activate', 'explorer'), + this.submenu('Speech', 'Speech', [ this.checkbox('Speech', 'Speech Output', 'speech'), - this.checkbox('Subtitles', 'Subtitles', 'subtitles'), + this.checkbox('Subtitles', 'Subtities', 'subtitles'), + this.checkbox('Braille', 'Braille', 'braille'), + this.checkbox('View Braille', 'View Braille', 'viewbraille'), this.rule(), this.submenu('Mathspeak', 'Mathspeak Rules', this.radioGroup('speechrules', [ ['mathspeak-default', 'Verbose'], ['mathspeak-brief', 'Brief'], ['mathspeak-sbrief', 'Superbrief'] ])), + this.submenu('Clearspeak', 'Clearspeak Rules', this.radioGroup('speechrules', [ + ['clearspeak-default', 'Standard'] + ])), this.submenu('ChromeVox', 'ChromeVox Rules', this.radioGroup('speechrules', [ ['chromvox-default', 'Verbose'], ['chromevox-short', 'Short'], ['chromevox-alternative', 'Alternative'] ])) ]), + this.submenu('Highlight', 'Highlight', [ + this.submenu('Background', 'Background', this.radioGroup('backgroundColor', [ + ['Blue'], ['Red'], ['Green'], ['Yellow'], ['Cyan'], ['Magenta'], ['White'], ['Black'] + ])), + this.submenu('Foreground', 'Foreground', this.radioGroup('foregroundColor', [ + ['Black'], ['White'], ['Magenta'], ['Cyan'], ['Yellow'], ['Green'], ['Red'], ['Blue'] + ])), + this.rule(), + this.radioGroup('highlight', [ + ['None'], ['Hover'], ['Flame'] + ]), + this.rule(), + this.checkbox('TreeColoring', 'Tree Coloring', 'treecoloring') + ]), + this.submenu('Magnification', 'Magnification', [ + this.radioGroup('magnification', [ + ['None'], ['Keyboard'], ['Mouse'] + ]), + this.rule(), + this.radioGroup('magnify', [ + ['200%'], ['300%'], ['400%'], ['500%'] + ]) + ]), + this.submenu('Semantic Info', 'Semantic Info', [ + this.checkbox('Type', 'Type', 'infoType'), + this.checkbox('Role', 'Role', 'infoRole'), + this.checkbox('Prefix', 'Prefix', 'infoPrefix') + ], true), this.rule(), this.checkbox('Collapsible', 'Collapsible Math', 'collapsible'), this.checkbox('AutoCollapse', 'Auto Collapse', 'autocollapse', {disabled: true}), @@ -526,11 +573,10 @@ export class Menu { * @param {boolean} enable True to enable, false to disable */ protected enableExplorerItems(enable: boolean) { - const menu = (this.menu.findID('Accessibility', 'Explorer') as ContextMenu.Submenu).getSubmenu(); - for (const item of menu.getItems().slice(3)) { - if (!(item instanceof ContextMenu.Rule)) { - enable ? item.enable() : item.disable(); - } + const menu = (this.menu.findID('Accessibility', 'Activate') as ContextMenu.Submenu).getMenu(); + for (const item of menu.getItems().slice(1)) { + if (item instanceof ContextMenu.Rule) break; + enable ? item.enable() : item.disable(); } } @@ -545,6 +591,7 @@ export class Menu { const settings = localStorage.getItem(Menu.MENU_STORAGE); if (!settings) return; Object.assign(this.settings, JSON.parse(settings)); + this.setA11y(this.settings); } catch (err) { console.log('MathJax localStorage error: ' + err.message); } @@ -571,6 +618,16 @@ export class Menu { } } + /** + * Merge menu settings into the a11y document options. + * @param {[key: string]: any} options The options. + */ + protected setA11y(options: {[key: string]: any}) { + if (window.MathJax._.a11y && window.MathJax._.a11y.explorer) { + window.MathJax._.a11y.explorer_ts.setA11yOptions(this.document, options); + } + } + /*======================================================================*/ /** @@ -760,6 +817,7 @@ export class Menu { }); } + /** * @param {MenuMathDocument} document The original document whose list is to be transferred */ @@ -975,7 +1033,7 @@ export class Menu { * * @param {string} variable The (pool) variable to attach to each radio button * @param {string[][]} radios An array of [string] or [string, string], giving the id and content - * for each radio button (if only one string is given it is ued for both) + * for each radio button (if only one string is given it is used for both) * @returns {Object[]} An array of JSON objects for radion buttons */ public radioGroup(variable: string, radios: string[][]) { @@ -1018,4 +1076,3 @@ export class Menu { /*======================================================================*/ } -