Skip to content

Commit 9abd74c

Browse files
committed
Fix explorer navigation to handle linebreaks, for both display and in-line math. (mathjax/MathJax#3394)
1 parent 20068cc commit 9abd74c

1 file changed

Lines changed: 55 additions & 44 deletions

File tree

ts/a11y/explorer/KeyExplorer.ts

Lines changed: 55 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ export class SpeechExplorer
577577
this.FocusOut(null);
578578
} else {
579579
this.stopEvent(event);
580-
this.refocus = this.node.querySelector(nav);
580+
this.refocus = this.firstNode(this.node);
581581
this.Start();
582582
}
583583
}
@@ -644,7 +644,7 @@ export class SpeechExplorer
644644
* Select top-level of expression
645645
*/
646646
protected homeKey() {
647-
this.setCurrent(this.node.querySelector(nav));
647+
this.setCurrent(this.firstNode(this.node));
648648
}
649649

650650
/**
@@ -656,7 +656,7 @@ export class SpeechExplorer
656656
protected moveDown(shift: boolean): boolean | void {
657657
return shift
658658
? this.moveToNeighborCell(1, 0)
659-
: this.moveTo(this.current.querySelector(nav));
659+
: this.moveTo(this.firstNode(this.current));
660660
}
661661

662662
/**
@@ -755,7 +755,7 @@ export class SpeechExplorer
755755
protected prevMark() {
756756
if (this.currentMark < 0) {
757757
if (this.marks.length === 0) {
758-
this.setCurrent(this.lastMark || this.node.querySelector(nav));
758+
this.setCurrent(this.lastMark || this.firstNode(this.node));
759759
return;
760760
}
761761
this.currentMark = this.marks.length - 1;
@@ -991,7 +991,9 @@ export class SpeechExplorer
991991
// (i.e., we are focusing out)
992992
//
993993
if (this.current) {
994-
this.current.classList.remove('mjx-selected');
994+
for (const part of this.getSplitNodes(this.current)) {
995+
part.classList.remove('mjx-selected');
996+
}
995997
this.pool.unhighlight();
996998
if (this.document.options.a11y.tabSelects === 'last') {
997999
this.refocus = this.current;
@@ -1009,8 +1011,11 @@ export class SpeechExplorer
10091011
this.current = node;
10101012
this.currentMark = -1;
10111013
if (this.current) {
1012-
this.current.classList.add('mjx-selected');
1013-
this.pool.highlight([this.current]);
1014+
const parts = this.getSplitNodes(this.current);
1015+
for (const part of parts) {
1016+
part.classList.add('mjx-selected');
1017+
}
1018+
this.pool.highlight(parts);
10141019
this.addSpeech(node, addDescription);
10151020
}
10161021
//
@@ -1019,6 +1024,20 @@ export class SpeechExplorer
10191024
this.node.removeAttribute('aria-busy');
10201025
}
10211026

1027+
/**
1028+
* Get all nodes with the same semantic id (multiple nodes if there are line breaks).
1029+
*
1030+
* @param {HTMLElement} node The node to check if it is split
1031+
* @returns {HTMLElement[]} All the nodes for the given id
1032+
*/
1033+
protected getSplitNodes(node: HTMLElement): HTMLElement[] {
1034+
const id = this.nodeId(node);
1035+
if (!id) {
1036+
return [node];
1037+
}
1038+
return Array.from(this.node.querySelectorAll(`[data-semantic-id="${id}"]`));
1039+
}
1040+
10221041
/**
10231042
* Remove the top-level speech node and create
10241043
* a temporary one for the given node.
@@ -1241,52 +1260,44 @@ export class SpeechExplorer
12411260
return cell;
12421261
}
12431262

1263+
/**
1264+
* Get an element's first speech child.
1265+
*
1266+
* @param {HTMLElement} node The parent element to get a child from
1267+
* @returns {HTMLElement} The first speech child of the node
1268+
*/
1269+
protected firstNode(node: HTMLElement): HTMLElement {
1270+
return node.querySelector(nav) as HTMLElement;
1271+
}
1272+
12441273
/**
12451274
* Navigate one step to the right on the same level.
12461275
*
1247-
* @param {HTMLElement} el The current element.
1248-
* @returns {HTMLElement} The next element.
1276+
* @param {HTMLElement} node The current element.
1277+
* @returns {HTMLElement} The next element.
12491278
*/
1250-
protected nextSibling(el: HTMLElement): HTMLElement {
1251-
if (!this.current.getAttribute('data-semantic-parent')) {
1252-
return null;
1253-
}
1254-
const sib = el.nextElementSibling as HTMLElement;
1255-
if (sib) {
1256-
if (sib.matches(nav)) {
1257-
return sib;
1258-
}
1259-
const sibChild = sib.querySelector(nav) as HTMLElement;
1260-
return sibChild ?? this.nextSibling(sib);
1261-
}
1262-
if (!isContainer(el) && !el.parentElement.matches(nav)) {
1263-
return this.nextSibling(el.parentElement);
1264-
}
1265-
return null;
1279+
protected nextSibling(node: HTMLElement): HTMLElement {
1280+
const id = this.parentId(node);
1281+
if (!id) return null;
1282+
const owns = this.getNode(id).getAttribute('data-semantic-owns')?.split(/ /);
1283+
if (!owns) return null;
1284+
const i = owns.indexOf(this.nodeId(node));
1285+
return this.getNode(owns[i + 1]);
12661286
}
12671287

12681288
/**
12691289
* Navigate one step to the left on the same level.
12701290
*
1271-
* @param {HTMLElement} el The current element.
1272-
* @returns {HTMLElement} The next element.
1291+
* @param {HTMLElement} node The current element.
1292+
* @returns {HTMLElement} The next element.
12731293
*/
1274-
protected prevSibling(el: HTMLElement): HTMLElement {
1275-
if (!this.current.getAttribute('data-semantic-parent')) {
1276-
return null;
1277-
}
1278-
const sib = el.previousElementSibling as HTMLElement;
1279-
if (sib) {
1280-
if (sib.matches(nav)) {
1281-
return sib;
1282-
}
1283-
const sibChild = sib.querySelector(nav) as HTMLElement;
1284-
return sibChild ?? this.prevSibling(sib);
1285-
}
1286-
if (!isContainer(el) && !el.parentElement.matches(nav)) {
1287-
return this.prevSibling(el.parentElement);
1288-
}
1289-
return null;
1294+
protected prevSibling(node: HTMLElement): HTMLElement {
1295+
const id = this.parentId(node);
1296+
if (!id) return null;
1297+
const owns = this.getNode(id).getAttribute('data-semantic-owns')?.split(/ /);
1298+
if (!owns) return null;
1299+
const i = owns.indexOf(this.nodeId(node));
1300+
return this.getNode(owns[i - 1]);
12901301
}
12911302

12921303
/**
@@ -1455,7 +1466,7 @@ export class SpeechExplorer
14551466
// current node (which creates the speech) and start the explorer.
14561467
//
14571468
const node = this.findStartNode();
1458-
this.setCurrent(node || this.node.querySelector(nav), !node);
1469+
this.setCurrent(node || this.firstNode(this.node), !node);
14591470
super.Start();
14601471
//
14611472
// Show any needed regions

0 commit comments

Comments
 (0)