diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 41931477..4b23fcf4 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -40,11 +40,13 @@ export class AccessibilityManager implements IDisposable { this._addTerminalEventListener('resize', data => this._onResize(data.cols, data.rows)); this._addTerminalEventListener('refresh', data => this._refreshRows(data.start, data.end)); // Line feed is an issue as the prompt won't be read out after a command is run - // this._terminal.on('lineFeed', () => this._onLineFeed()); this._addTerminalEventListener('a11y.char', (char) => this._onChar(char)); this._addTerminalEventListener('lineFeed', () => this._onChar('\n')); + // Ensure \t is covered, if not a line of output from `ls` is read as one word + this._addTerminalEventListener('a11y.tab', () => this._onChar(' ')); this._addTerminalEventListener('charsizechanged', () => this._refreshRowsDimensions()); this._addTerminalEventListener('key', keyChar => this._onKey(keyChar)); + this._addTerminalEventListener('blur', () => this._clearLiveRegion()); } private _addTerminalEventListener(type: string, listener: (...args: any[]) => any): void { @@ -86,6 +88,10 @@ export class AccessibilityManager implements IDisposable { } else { this._liveRegion.textContent += char; } + + if (this._liveRegion.textContent.length > 0 && !this._liveRegion.parentNode) { + this._accessibilityTreeRoot.appendChild(this._liveRegion); + } // TODO: Clear at some point // TOOD: Handle heaps of data @@ -93,26 +99,18 @@ export class AccessibilityManager implements IDisposable { this._refreshRows(); } - private _onKey(keyChar: string): void { - this._charsToConsume.push(keyChar); + private _clearLiveRegion(): void { + if (this._liveRegion.parentNode) { + this._accessibilityTreeRoot.removeChild(this._liveRegion); + } + this._liveRegion.textContent = ''; } - // private _onLineFeed(): void { - // const buffer: IBuffer = (this._terminal.buffer); - // const newLine = buffer.lines.get(buffer.ybase + buffer.y); - // // Only use the data when the new line is ready - // if (!(newLine).isWrapped) { - // this._accessibilityTreeRoot.textContent += `${this._getWrappedLineData(buffer, buffer.ybase + buffer.y - 1)}\n`; - // } - // } - - // private _getWrappedLineData(buffer: IBuffer, lineIndex: number): string { - // let lineData = buffer.translateBufferLineToString(lineIndex, true); - // while (lineIndex >= 0 && (buffer.lines.get(lineIndex--)).isWrapped) { - // lineData = buffer.translateBufferLineToString(lineIndex, true) + lineData; - // } - // return lineData; - // } + private _onKey(keyChar: string): void { + console.log('key event', keyChar); + this._clearLiveRegion(); + this._charsToConsume.push(keyChar); + } // TODO: Hook up to refresh when the renderer refreshes the range? Slower to prevent layout thrashing? private _refreshRows(start?: number, end?: number): void { diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 46479236..40dde4a4 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -167,6 +167,9 @@ export class InputHandler implements IInputHandler { */ public tab(): void { this._terminal.buffer.x = this._terminal.buffer.nextStop(); + if (this._terminal.options.screenReaderMode) { + this._terminal.emit('a11y.tab'); + } } /** diff --git a/src/Terminal.ts b/src/Terminal.ts index a1e5586f..d45b12af 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -470,6 +470,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * Binds the desired blur behavior on a given terminal object. */ private _onTextAreaBlur(): void { + // Text can safely be removed on blur. Doing it earlier could interfere with + // screen readers reading it out. + this.textarea.value = ''; this.refresh(this.buffer.y, this.buffer.y); if (this.sendFocus) { this.send(C0.ESC + '[O'); @@ -550,16 +553,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } }, true); - on(this.textarea, 'keydown', (ev: KeyboardEvent) => { - this._keyDown(ev); - }, true); - - on(this.textarea, 'keypress', (ev: KeyboardEvent) => { - this._keyPress(ev); - // Truncate the textarea's value, since it is not needed - this.textarea.value = ''; - }, true); - + on(this.textarea, 'keydown', (ev: KeyboardEvent) => this._keyDown(ev), true); + on(this.textarea, 'keypress', (ev: KeyboardEvent) => this._keyPress(ev), true); on(this.textarea, 'compositionstart', () => this.compositionHelper.compositionstart()); on(this.textarea, 'compositionupdate', (e: CompositionEvent) => this.compositionHelper.compositionupdate(e)); on(this.textarea, 'compositionend', () => this.compositionHelper.compositionend());