Skip to content

Commit 940ad9a

Browse files
authored
Merge pull request #1316 from mathjax/issue3394
Fix explorer navigation to handle linebreaks, for both display and in-line math. (mathjax/MathJax#3394)
2 parents 64a1d96 + 9abd74c commit 940ad9a

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
@@ -582,7 +582,7 @@ export class SpeechExplorer
582582
this.FocusOut(null);
583583
} else {
584584
this.stopEvent(event);
585-
this.refocus = this.node.querySelector(nav);
585+
this.refocus = this.firstNode(this.node);
586586
this.Start();
587587
}
588588
}
@@ -649,7 +649,7 @@ export class SpeechExplorer
649649
* Select top-level of expression
650650
*/
651651
protected homeKey() {
652-
this.setCurrent(this.node.querySelector(nav));
652+
this.setCurrent(this.firstNode(this.node));
653653
}
654654

655655
/**
@@ -661,7 +661,7 @@ export class SpeechExplorer
661661
protected moveDown(shift: boolean): boolean | void {
662662
return shift
663663
? this.moveToNeighborCell(1, 0)
664-
: this.moveTo(this.current.querySelector(nav));
664+
: this.moveTo(this.firstNode(this.current));
665665
}
666666

667667
/**
@@ -760,7 +760,7 @@ export class SpeechExplorer
760760
protected prevMark() {
761761
if (this.currentMark < 0) {
762762
if (this.marks.length === 0) {
763-
this.setCurrent(this.lastMark || this.node.querySelector(nav));
763+
this.setCurrent(this.lastMark || this.firstNode(this.node));
764764
return;
765765
}
766766
this.currentMark = this.marks.length - 1;
@@ -996,7 +996,9 @@ export class SpeechExplorer
996996
// (i.e., we are focusing out)
997997
//
998998
if (this.current) {
999-
this.current.classList.remove('mjx-selected');
999+
for (const part of this.getSplitNodes(this.current)) {
1000+
part.classList.remove('mjx-selected');
1001+
}
10001002
this.pool.unhighlight();
10011003
if (this.document.options.a11y.tabSelects === 'last') {
10021004
this.refocus = this.current;
@@ -1014,8 +1016,11 @@ export class SpeechExplorer
10141016
this.current = node;
10151017
this.currentMark = -1;
10161018
if (this.current) {
1017-
this.current.classList.add('mjx-selected');
1018-
this.pool.highlight([this.current]);
1019+
const parts = this.getSplitNodes(this.current);
1020+
for (const part of parts) {
1021+
part.classList.add('mjx-selected');
1022+
}
1023+
this.pool.highlight(parts);
10191024
this.addSpeech(node, addDescription);
10201025
}
10211026
//
@@ -1024,6 +1029,20 @@ export class SpeechExplorer
10241029
this.node.removeAttribute('aria-busy');
10251030
}
10261031

1032+
/**
1033+
* Get all nodes with the same semantic id (multiple nodes if there are line breaks).
1034+
*
1035+
* @param {HTMLElement} node The node to check if it is split
1036+
* @returns {HTMLElement[]} All the nodes for the given id
1037+
*/
1038+
protected getSplitNodes(node: HTMLElement): HTMLElement[] {
1039+
const id = this.nodeId(node);
1040+
if (!id) {
1041+
return [node];
1042+
}
1043+
return Array.from(this.node.querySelectorAll(`[data-semantic-id="${id}"]`));
1044+
}
1045+
10271046
/**
10281047
* Remove the top-level speech node and create
10291048
* a temporary one for the given node.
@@ -1273,52 +1292,44 @@ export class SpeechExplorer
12731292
return cell;
12741293
}
12751294

1295+
/**
1296+
* Get an element's first speech child.
1297+
*
1298+
* @param {HTMLElement} node The parent element to get a child from
1299+
* @returns {HTMLElement} The first speech child of the node
1300+
*/
1301+
protected firstNode(node: HTMLElement): HTMLElement {
1302+
return node.querySelector(nav) as HTMLElement;
1303+
}
1304+
12761305
/**
12771306
* Navigate one step to the right on the same level.
12781307
*
1279-
* @param {HTMLElement} el The current element.
1280-
* @returns {HTMLElement} The next element.
1308+
* @param {HTMLElement} node The current element.
1309+
* @returns {HTMLElement} The next element.
12811310
*/
1282-
protected nextSibling(el: HTMLElement): HTMLElement {
1283-
if (!this.current.getAttribute('data-semantic-parent')) {
1284-
return null;
1285-
}
1286-
const sib = el.nextElementSibling as HTMLElement;
1287-
if (sib) {
1288-
if (sib.matches(nav)) {
1289-
return sib;
1290-
}
1291-
const sibChild = sib.querySelector(nav) as HTMLElement;
1292-
return sibChild ?? this.nextSibling(sib);
1293-
}
1294-
if (!isContainer(el) && !el.parentElement.matches(nav)) {
1295-
return this.nextSibling(el.parentElement);
1296-
}
1297-
return null;
1311+
protected nextSibling(node: HTMLElement): HTMLElement {
1312+
const id = this.parentId(node);
1313+
if (!id) return null;
1314+
const owns = this.getNode(id).getAttribute('data-semantic-owns')?.split(/ /);
1315+
if (!owns) return null;
1316+
const i = owns.indexOf(this.nodeId(node));
1317+
return this.getNode(owns[i + 1]);
12981318
}
12991319

13001320
/**
13011321
* Navigate one step to the left on the same level.
13021322
*
1303-
* @param {HTMLElement} el The current element.
1304-
* @returns {HTMLElement} The next element.
1323+
* @param {HTMLElement} node The current element.
1324+
* @returns {HTMLElement} The next element.
13051325
*/
1306-
protected prevSibling(el: HTMLElement): HTMLElement {
1307-
if (!this.current.getAttribute('data-semantic-parent')) {
1308-
return null;
1309-
}
1310-
const sib = el.previousElementSibling as HTMLElement;
1311-
if (sib) {
1312-
if (sib.matches(nav)) {
1313-
return sib;
1314-
}
1315-
const sibChild = sib.querySelector(nav) as HTMLElement;
1316-
return sibChild ?? this.prevSibling(sib);
1317-
}
1318-
if (!isContainer(el) && !el.parentElement.matches(nav)) {
1319-
return this.prevSibling(el.parentElement);
1320-
}
1321-
return null;
1326+
protected prevSibling(node: HTMLElement): HTMLElement {
1327+
const id = this.parentId(node);
1328+
if (!id) return null;
1329+
const owns = this.getNode(id).getAttribute('data-semantic-owns')?.split(/ /);
1330+
if (!owns) return null;
1331+
const i = owns.indexOf(this.nodeId(node));
1332+
return this.getNode(owns[i - 1]);
13221333
}
13231334

13241335
/**
@@ -1487,7 +1498,7 @@ export class SpeechExplorer
14871498
// current node (which creates the speech) and start the explorer.
14881499
//
14891500
const node = this.findStartNode();
1490-
this.setCurrent(node || this.node.querySelector(nav), !node);
1501+
this.setCurrent(node || this.firstNode(this.node), !node);
14911502
super.Start();
14921503
//
14931504
// Show any needed regions

0 commit comments

Comments
 (0)