Support nav mode pgup/pgdown/end/home, check boundaries
This commit is contained in:
+40
-23
@@ -211,7 +211,7 @@ export class AccessibilityManager implements IDisposable {
|
||||
class NavigationMode implements IDisposable {
|
||||
private _activeItemId: string;
|
||||
private _isNavigationModeActive: boolean = false;
|
||||
private _navigationModeFocusedRow: number;
|
||||
private _absoluteFocusedRow: number;
|
||||
|
||||
private _disposables: IDisposable[] = [];
|
||||
|
||||
@@ -243,13 +243,14 @@ class NavigationMode implements IDisposable {
|
||||
}
|
||||
|
||||
public enter(): void {
|
||||
// TODO: Should entering navigation mode send ydisp to ybase?
|
||||
this._isNavigationModeActive = true;
|
||||
this._accessibilityManager.announce('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);
|
||||
this._navigateToElement(this._terminal.buffer.ydisp + this._terminal.buffer.y);
|
||||
}
|
||||
|
||||
public leave(): void {
|
||||
@@ -280,10 +281,16 @@ class NavigationMode implements IDisposable {
|
||||
|
||||
public onKeyUp(e: KeyboardEvent): boolean {
|
||||
return this._onKey(e, e => {
|
||||
switch (e.keyCode) {
|
||||
case 27: return this._onEscape(e);
|
||||
case 38: return this._onArrowUp(e);
|
||||
case 40: return this._onArrowDown(e);
|
||||
if (this._isNavigationModeActive) {
|
||||
switch (e.keyCode) {
|
||||
case 27: return this._onEscape(e);
|
||||
case 33: return this._onPageUp(e);
|
||||
case 34: return this._onPageDown(e);
|
||||
case 35: return this._onEnd(e);
|
||||
case 36: return this._onHome(e);
|
||||
case 38: return this._onArrowUp(e);
|
||||
case 40: return this._onArrowDown(e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
@@ -302,35 +309,45 @@ class NavigationMode implements IDisposable {
|
||||
}
|
||||
|
||||
private _onArrowUp(e: KeyboardEvent): boolean {
|
||||
// TODO: Jump up/down to next non-blank row
|
||||
this._navigateToElement(this._navigationModeFocusedRow - 1);
|
||||
this._rowContainer.focus();
|
||||
return true;
|
||||
return this._focusRow(this._absoluteFocusedRow - 1);
|
||||
}
|
||||
|
||||
private _onArrowDown(e: KeyboardEvent): boolean {
|
||||
this._navigateToElement(this._navigationModeFocusedRow + 1);
|
||||
return this._focusRow(this._absoluteFocusedRow + 1);
|
||||
}
|
||||
|
||||
private _onPageUp(e: KeyboardEvent): boolean {
|
||||
return this._focusRow(this._absoluteFocusedRow - this._terminal.rows);
|
||||
}
|
||||
|
||||
private _onPageDown(e: KeyboardEvent): boolean {
|
||||
return this._focusRow(this._absoluteFocusedRow + this._terminal.rows);
|
||||
}
|
||||
|
||||
private _onHome(e: KeyboardEvent): boolean {
|
||||
return this._focusRow(0);
|
||||
}
|
||||
|
||||
private _onEnd(e: KeyboardEvent): boolean {
|
||||
return this._focusRow(this._terminal.buffer.lines.length - 1);
|
||||
}
|
||||
|
||||
private _focusRow(row: number): boolean {
|
||||
this._navigateToElement(row);
|
||||
this._rowContainer.focus();
|
||||
return true;
|
||||
}
|
||||
|
||||
private _navigateToElement(row: number): void {
|
||||
if (row < 0) {
|
||||
if (this._terminal.buffer.ydisp > 0) {
|
||||
this._terminal.scrollLines(-1);
|
||||
row = 0;
|
||||
// TODO: This doesn't reliably read the element, maybe a new row needs to be inserted at the top?
|
||||
// Exit early since the same element is focused
|
||||
return;
|
||||
}
|
||||
}
|
||||
private _navigateToElement(absoluteRow: number): void {
|
||||
absoluteRow = this._terminal.scrollToRow(absoluteRow);
|
||||
|
||||
// TODO: Store this state
|
||||
const selected = document.querySelector('#' + this._activeItemId);
|
||||
if (selected) {
|
||||
selected.removeAttribute('id');
|
||||
}
|
||||
this._navigationModeFocusedRow = row;
|
||||
const selectedElement = this._rowElements[row];
|
||||
this._absoluteFocusedRow = absoluteRow;
|
||||
const selectedElement = this._rowElements[absoluteRow - this._terminal.buffer.ydisp];
|
||||
selectedElement.id = this._activeItemId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ export interface ITerminal extends ILinkifierAccessor, IBufferAccessor, IElement
|
||||
*/
|
||||
handler(data: string): void;
|
||||
scrollLines(disp: number, suppressScrollEvent?: boolean): void;
|
||||
scrollToRow(row: number): number;
|
||||
cancel(ev: Event, force?: boolean): boolean | void;
|
||||
log(text: string): void;
|
||||
reset(): void;
|
||||
|
||||
@@ -1169,6 +1169,28 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
|
||||
this.refresh(0, this.rows - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the viewport to an absolute row in the buffer.
|
||||
* @param absoluteRow The absolute row in the buffer to scroll to.
|
||||
* @returns The actual absolute row that was scrolled to (including boundary checked).
|
||||
*/
|
||||
public scrollToRow(absoluteRow: number): number {
|
||||
// Ensure value is valid
|
||||
absoluteRow = Math.max(Math.min(absoluteRow, this.buffer.lines.length - 1), 0);
|
||||
|
||||
// Move viewport as necessary
|
||||
const relativeRow = absoluteRow - this.buffer.ydisp;
|
||||
let scrollAmount = 0;
|
||||
if (relativeRow < 0) {
|
||||
scrollAmount = relativeRow;
|
||||
} else if (relativeRow >= this.rows) {
|
||||
scrollAmount = relativeRow - this.rows + 1;
|
||||
}
|
||||
this.scrollLines(scrollAmount);
|
||||
|
||||
return absoluteRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the display of the terminal by a number of pages.
|
||||
* @param {number} pageCount The number of pages to scroll (negative scrolls up).
|
||||
|
||||
@@ -48,6 +48,9 @@ export class MockTerminal implements ITerminal {
|
||||
scrollLines(disp: number, suppressScrollEvent: boolean): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
scrollToRow(absoluteRow: number): number {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
cancel(ev: Event, force?: boolean): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user