Add basic navigation mode support for current viewport

This commit is contained in:
Daniel Imms
2018-01-11 22:56:19 -08:00
parent bbc9a010b9
commit 2311d7d66b
3 changed files with 100 additions and 6 deletions
+87 -4
View File
@@ -8,8 +8,13 @@ import { isMac } from './utils/Browser';
import { RenderDebouncer } from './utils/RenderDebouncer';
const MAX_ROWS_TO_READ = 20;
const ACTIVE_ITEM_ID_PREFIX = 'xterm-active-item-';
export class AccessibilityManager implements IDisposable {
private _activeItemId: string;
private _isNavigationModeActive: boolean = false;
private _navigationModeFocusedRow: number;
private _accessibilityTreeRoot: HTMLElement;
private _rowContainer: HTMLElement;
private _rowElements: HTMLElement[] = [];
@@ -32,12 +37,13 @@ export class AccessibilityManager implements IDisposable {
private _charsToConsume: string[] = [];
constructor(private _terminal: ITerminal) {
this._activeItemId = ACTIVE_ITEM_ID_PREFIX + Math.floor((Math.random() * 100000));
this._accessibilityTreeRoot = document.createElement('div');
this._accessibilityTreeRoot.classList.add('accessibility');
this._accessibilityTreeRoot.classList.add('xterm-accessibility');
this._rowContainer = document.createElement('div');
this._rowContainer.classList.add('accessibility-tree');
this._rowContainer.classList.add('xterm-accessibility-tree');
for (let i = 0; i < this._terminal.rows; i++) {
this._rowElements[i] = document.createElement('div');
this._rowElements[i] = this._createAccessibilityTreeNode();
this._rowContainer.appendChild(this._rowElements[i]);
}
this._refreshRowsDimensions();
@@ -73,6 +79,41 @@ export class AccessibilityManager implements IDisposable {
// TODO: Dispose of this listener when disposed
// TODO: Listen instead to when devicePixelRatio changed (depends on PR #1172)
window.addEventListener('resize', () => this._refreshRowsDimensions());
this._rowContainer.addEventListener('keyup', e => {
switch (e.keyCode) {
case 27: // Escape
this.leaveNavigationMode();
break;
// TODO: Jump up/down to next non-blank row
case 38: /*ArrowUp*/
this._navigateToElement(this._navigationModeFocusedRow - 1);
break;
case 40: /*ArrowDown*/
this._navigateToElement(this._navigationModeFocusedRow + 1);
break;
}
this._rowContainer.focus();
console.log('keydown2', e);
e.preventDefault();
e.stopPropagation();
return true;
// no handler
//return false;
});
this._rowContainer.addEventListener('keydown', e => {
if (this._isNavigationModeActive) {
e.preventDefault();
e.stopPropagation();
return true;
}
return false;
});
}
public get isNavigationModeActive(): boolean {
return this._isNavigationModeActive;
}
private _addTerminalEventListener(type: string, listener: (...args: any[]) => any): void {
@@ -99,7 +140,7 @@ export class AccessibilityManager implements IDisposable {
private _onResize(cols: number, rows: number): void {
// Grow rows as required
for (let i = this._rowContainer.children.length; i < this._terminal.rows; i++) {
this._rowElements[i] = document.createElement('div');
this._rowElements[i] = this._createAccessibilityTreeNode();
this._rowContainer.appendChild(this._rowElements[i]);
}
// Shrink rows as required
@@ -110,6 +151,12 @@ export class AccessibilityManager implements IDisposable {
this._refreshRowsDimensions();
}
private _createAccessibilityTreeNode(): HTMLElement {
const element = document.createElement('div');
element.setAttribute('role', 'menuitem');
return element;
}
private _onChar(char: string): void {
if (this._liveRegionLineCount < MAX_ROWS_TO_READ + 1) {
// \n needs to be printed as a space, otherwise it will be collapsed to
@@ -179,6 +226,7 @@ export class AccessibilityManager implements IDisposable {
for (let i = start; i <= end; i++) {
const lineData = buffer.translateBufferLineToString(buffer.ybase + i, true);
this._rowElements[i].textContent = lineData;
this._rowElements[i].setAttribute('aria-label', lineData);
}
}
@@ -189,4 +237,39 @@ export class AccessibilityManager implements IDisposable {
this._rowElements[i].style.height = `${dimensions.actualCellHeight}px`;
}
}
public enterNavigationMode(): void {
this._isNavigationModeActive = true;
this._clearLiveRegion();
this._liveRegion.textContent += 'Entered line navigation mode';
this._rowContainer.tabIndex = 0;
this._rowContainer.setAttribute('role', 'menu');
this._rowContainer.setAttribute('aria-activedescendant', this._activeItemId);
this._rowContainer.focus();
this._navigateToElement(this._terminal.buffer.y);
}
public leaveNavigationMode(): void {
this._isNavigationModeActive = false;
this._liveRegion.textContent += 'Left line navigation mode';
this._rowContainer.removeAttribute('tabindex');
this._rowContainer.removeAttribute('aria-activedescendant');
this._rowContainer.removeAttribute('role');
const selected = document.querySelector('#' + this._activeItemId);
if (selected) {
selected.removeAttribute('id');
}
this._terminal.textarea.focus();
}
private _navigateToElement(row: number): void {
// TODO: Store this state
const selected = document.querySelector('#' + this._activeItemId);
if (selected) {
selected.removeAttribute('id');
}
this._navigationModeFocusedRow = row;
const selectedElement = this._rowElements[row];
selectedElement.id = this._activeItemId;
}
}
+7
View File
@@ -454,6 +454,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* Binds the desired focus behavior on a given terminal object.
*/
private _onTextAreaFocus(): void {
console.log('textarea.focus');
if (this.sendFocus) {
this.send(C0.ESC + '[I');
}
@@ -1374,6 +1375,12 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
* @param {KeyboardEvent} ev The keydown event to be handled.
*/
protected _keyDown(ev: KeyboardEvent): boolean {
console.log('a');
if (this._accessibilityManager && this._accessibilityManager.isNavigationModeActive) {
console.log('b');
return;
}
if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) {
return false;
}
+6 -2
View File
@@ -124,7 +124,7 @@
cursor: text;
}
.xterm .accessibility {
.xterm .xterm-accessibility {
position: absolute;
left: 0;
top: 0;
@@ -133,10 +133,14 @@
z-index: 100;
}
.xterm .accessibility-tree {
.xterm .xterm-accessibility-tree {
color: transparent;
}
.xterm .xterm-accessibility-tree:focus [id^="xterm-active-item-"] {
outline: 1px solid #F80;
}
.xterm .live-region {
position: absolute;
left: -9999px;