From b13826441cdeb4cb539a022808d1d8932b2e8283 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 27 Jan 2018 18:14:52 -0800 Subject: [PATCH] Hook up scroll APIs to scroll and focus when navigating --- src/AccessibilityManager.ts | 74 ++++++++++++++++++++++++++++++++++--- src/Terminal.ts | 20 ++++++++-- 2 files changed, 85 insertions(+), 9 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 286c1f98..338f5a80 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -103,10 +103,20 @@ export class AccessibilityManager implements IDisposable { this._rowElements = null; } + public get isNavigatingRows(): boolean { + return this._rowElements.indexOf(document.activeElement) >= 0; + } + private _onBoundaryFocus(e: FocusEvent, position: BoundaryPosition): void { const boundaryElement = e.target; const beforeBoundaryElement = this._rowElements[position === BoundaryPosition.Top ? 1 : this._rowElements.length - 2]; + // Don't scroll when the last focused item was not the second row (focus is going the other + // direction) + if (e.relatedTarget !== beforeBoundaryElement) { + return; + } + // Don't scroll if the buffer top has reached the end in that direction const posInSet = boundaryElement.getAttribute('aria-posinset'); const lastRowPos = position === BoundaryPosition.Top ? '1' : `${this._terminal.buffer.lines.length}`; @@ -114,12 +124,6 @@ export class AccessibilityManager implements IDisposable { return; } - // Don't scroll when the last focused item was not the second row (focus is going the other - // direction) - if (e.relatedTarget !== beforeBoundaryElement) { - return; - } - // Remove old boundary element from array let topBoundaryElement: HTMLElement; let bottomBoundaryElement: HTMLElement; @@ -163,6 +167,64 @@ export class AccessibilityManager implements IDisposable { e.stopImmediatePropagation(); } + /** + * Moves the focus of the terminal relatively by a number of rows. + * @param amount The amount of rows to scroll + */ + public moveRowFocus(amount: number): void { + console.log('moveRowFocus', amount); + // Do nothing if not navigating rows or the amount to navigate is 0 + if (!this.isNavigatingRows || amount === 0) { + return; + } + + // Find and validate the new absolute row position + const buffer = this._terminal.buffer; + const oldRelativeRow = this._rowElements.indexOf(document.activeElement); + const oldAbsoluteRow = buffer.ydisp + oldRelativeRow; + const newAbsoluteRow = Math.max(Math.min(oldAbsoluteRow + amount, buffer.lines.length - 1), 0); + if (oldAbsoluteRow === newAbsoluteRow) { + return; + } + + // Find the new relative row, this cannot be on a boundary element unless the focused row is the + // top-most or bottom-most row in the buffer. + let newRelativeRow: number; + if (newAbsoluteRow === 0) { + newRelativeRow = 0; + } else if (newAbsoluteRow === buffer.lines.length - 1) { + newRelativeRow = this._terminal.rows - 1; + } else { + newRelativeRow = Math.max(Math.min(oldRelativeRow + amount, this._terminal.rows - 2), 1); + } + + // Find the new viewport position + let newYDisp: number; + if (newAbsoluteRow === 0) { + // Start of buffer + newYDisp = 0; + } else if (newAbsoluteRow === buffer.lines.length - 1) { + // End of buffer + newYDisp = buffer.lines.length - 1 - this._terminal.rows; + } else if (newAbsoluteRow > buffer.ydisp && newAbsoluteRow < buffer.ydisp + this._terminal.rows - 1) { + // No scrolling necessary + newYDisp = buffer.ydisp; + } else if (newAbsoluteRow < oldAbsoluteRow) { + // Scrolling up needed + newYDisp = newAbsoluteRow - 1; + } else { // if (newAbsoluteRow > oldAbsoluteRow) + // Scrolling down needed + newYDisp = newAbsoluteRow + this._terminal.rows - 2; + } + + // Perform scroll + const scrollAmount = newYDisp - buffer.ydisp; + this._terminal.scrollLines(scrollAmount); + + // Focus new row + this._rowElements[newRelativeRow].focus(); + } + private _onResize(cols: number, rows: number): void { // Remove bottom boundary listener this._rowElements[this._rowElements.length - 1].removeEventListener('focus', this._bottomBoundaryFocusListener); diff --git a/src/Terminal.ts b/src/Terminal.ts index 4eafc7a0..d0389048 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1187,26 +1187,40 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.refresh(0, this.rows - 1); } + /** + * Scrolls the viewport by a number of rows when not navigating rows. When + * navigating rows, move focus relatively by a number of rows. + * @param amount The number of lines to scroll. + */ + private _accessibilityAwareScrollLines(amount: number): void { + console.log('_accessibilityAwareScrollLines'); + if (this.options.screenReaderMode && this._accessibilityManager.isNavigatingRows) { + this._accessibilityManager.moveRowFocus(amount); + } else { + this.scrollLines(amount); + } + } + /** * Scroll the display of the terminal by a number of pages. * @param {number} pageCount The number of pages to scroll (negative scrolls up). */ public scrollPages(pageCount: number): void { - this.scrollLines(pageCount * (this.rows - 1)); + this._accessibilityAwareScrollLines(pageCount * (this.rows - 1)); } /** * Scrolls the display of the terminal to the top. */ public scrollToTop(): void { - this.scrollLines(-this.buffer.ydisp); + this._accessibilityAwareScrollLines(-this.buffer.ydisp); } /** * Scrolls the display of the terminal to the bottom. */ public scrollToBottom(): void { - this.scrollLines(this.buffer.ybase - this.buffer.ydisp); + this._accessibilityAwareScrollLines(this.buffer.ybase - this.buffer.ydisp); } /**