diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index 42242fef1..44ce03e20 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -4,15 +4,20 @@ */ import * as Strings from 'browser/LocalizableStrings'; -import { ITerminal, IRenderDebouncer } from 'browser/Types'; +import { ITerminal, IRenderDebouncer, ReadonlyColorSet } from 'browser/Types'; import { isMac } from 'common/Platform'; import { TimeBasedDebouncer } from 'browser/TimeBasedDebouncer'; +import { addDisposableDomListener } from 'browser/Lifecycle'; import { Disposable, toDisposable } from 'common/Lifecycle'; +import { IRenderService, IThemeService } from 'browser/services/Services'; +import { IOptionsService } from 'common/services/Services'; +import { ITerminalOptions } from 'xterm'; const MAX_ROWS_TO_READ = 20; export class AccessibilityManager extends Disposable { private _accessibilityContainer: HTMLElement; + private _accessiblityBuffer: HTMLElement; private _liveRegion: HTMLElement; private _liveRegionLineCount: number = 0; @@ -31,8 +36,14 @@ export class AccessibilityManager extends Disposable { private _charsToAnnounce: string = ''; + private _isAccessibilityBufferActive: boolean = false; + public get isAccessibilityBufferActive(): boolean { return this._isAccessibilityBufferActive; } + constructor( - private readonly _terminal: ITerminal + private readonly _terminal: ITerminal, + @IOptionsService optionsService: IOptionsService, + @IRenderService private readonly _renderService: IRenderService, + @IThemeService themeService: IThemeService ) { super(); this._accessibilityContainer = document.createElement('div'); @@ -49,6 +60,23 @@ export class AccessibilityManager extends Disposable { } this._terminal.element.insertAdjacentElement('afterbegin', this._accessibilityContainer); + this._accessiblityBuffer = document.createElement('div'); + this._accessiblityBuffer.setAttribute('role', 'document'); + this._accessiblityBuffer.ariaRoleDescription = Strings.accessibilityBuffer; + this._accessiblityBuffer.tabIndex = 0; + this._accessibilityContainer.appendChild(this._accessiblityBuffer); + this._accessiblityBuffer.classList.add('xterm-accessibility-buffer'); + this.register(addDisposableDomListener(this._accessiblityBuffer, 'keydown', (ev: KeyboardEvent) => { + if (ev.key === 'Tab') { + this._isAccessibilityBufferActive = false; + }} + )); + this.register(addDisposableDomListener(this._accessiblityBuffer, 'focus',() => this._refreshAccessibilityBuffer())); + this.register(addDisposableDomListener(this._accessiblityBuffer, 'focusout',() => { + this._isAccessibilityBufferActive = false; + })); + + this.register(this._liveRegionDebouncer); this.register(this._terminal.onRender(e => this._refreshRows(e.start, e.end))); this.register(this._terminal.onScroll(() => this._refreshRows())); @@ -58,7 +86,16 @@ export class AccessibilityManager extends Disposable { this.register(this._terminal.onA11yTab(spaceCount => this._handleTab(spaceCount))); this.register(this._terminal.onKey(e => this._handleKey(e.key))); this.register(this._terminal.onBlur(() => this._clearLiveRegion())); - this.register(toDisposable(() => this._accessibilityContainer.remove())); + + this._handleColorChange(themeService.colors); + this.register(themeService.onChangeColors(e => this._handleColorChange(e))); + this._handleFontOptionChange(optionsService.options); + this.register(optionsService.onMultipleOptionChange(['fontSize', 'fontFamily', 'letterSpacing', 'lineHeight'], () => this._handleFontOptionChange(optionsService.options))); + + this.register(toDisposable(() => { + this._accessiblityBuffer.remove(); + this._accessibilityContainer.remove(); + })); } private _handleTab(spaceCount: number): void { @@ -119,6 +156,10 @@ export class AccessibilityManager extends Disposable { this._liveRegionDebouncer.refresh(start, end, this._terminal.rows); } + private _refreshRowDimensions(element: HTMLElement): void { + element.style.height = `${this._renderService.dimensions.css.cell.height}px`; + } + private _announceCharacters(): void { if (this._charsToAnnounce.length === 0) { return; @@ -126,4 +167,32 @@ export class AccessibilityManager extends Disposable { this._liveRegion.textContent += this._charsToAnnounce; this._charsToAnnounce = ''; } + + + private _refreshAccessibilityBuffer(): void { + if (!this._terminal.viewport) { + return; + } + this._isAccessibilityBufferActive = true; + const { bufferElements } = this._terminal.viewport.getBufferElements(0); + for (const element of bufferElements) { + if (element.textContent) { + element.textContent = element.textContent.replace(new RegExp(' ', 'g'), '\xA0'); + } + } + this._accessiblityBuffer.replaceChildren(...bufferElements); + this._accessiblityBuffer.scrollTop = this._accessiblityBuffer.scrollHeight; + } + + private _handleColorChange(colorSet: ReadonlyColorSet): void { + this._accessiblityBuffer.style.backgroundColor = colorSet.background.css; + this._accessiblityBuffer.style.color = colorSet.foreground.css; + } + + private _handleFontOptionChange(options: Required): void { + this._accessiblityBuffer.style.fontFamily = options.fontFamily; + this._accessiblityBuffer.style.fontSize = `${options.fontSize}px`; + this._accessiblityBuffer.style.lineHeight = `${options.lineHeight * this._renderService.dimensions.css.cell.height}px`; + this._accessiblityBuffer.style.letterSpacing = `${options.letterSpacing}px`; + } } diff --git a/src/browser/AccessibleBuffer.ts b/src/browser/AccessibleBuffer.ts deleted file mode 100644 index 118fc0f04..000000000 --- a/src/browser/AccessibleBuffer.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import * as Strings from 'browser/LocalizableStrings'; -import { ITerminal, IRenderDebouncer, ReadonlyColorSet } from 'browser/Types'; -import { isMac } from 'common/Platform'; -import { TimeBasedDebouncer } from 'browser/TimeBasedDebouncer'; -import { addDisposableDomListener } from 'browser/Lifecycle'; -import { Disposable, toDisposable } from 'common/Lifecycle'; -import { IRenderService, IThemeService } from 'browser/services/Services'; -import { IOptionsService } from 'common/services/Services'; -import { ITerminalOptions } from 'xterm'; - -export class AccessibleBuffer extends Disposable { - private _accessiblityBuffer: HTMLElement; - private _isAccessibilityBufferActive: boolean = false; - public get isAccessibilityBufferActive(): boolean { return this._isAccessibilityBufferActive; } - - constructor( - private readonly _terminal: ITerminal, - @IOptionsService optionsService: IOptionsService, - @IRenderService private readonly _renderService: IRenderService, - @IThemeService themeService: IThemeService - ) { - super(); - if (!this._terminal.element) { - throw new Error('Cannot enable accessibility before Terminal.open'); - } - - this._accessiblityBuffer = document.createElement('div'); - this._accessiblityBuffer.setAttribute('role', 'document'); - this._accessiblityBuffer.ariaRoleDescription = Strings.accessibilityBuffer; - this._accessiblityBuffer.tabIndex = 0; - this._accessiblityBuffer.classList.add('xterm-accessibility-buffer'); - this._terminal.element.insertAdjacentElement('afterbegin', this._accessiblityBuffer); - - this.register(addDisposableDomListener(this._accessiblityBuffer, 'keydown', (ev: KeyboardEvent) => { - if (ev.key === 'Tab') { - this._isAccessibilityBufferActive = false; - }} - )); - this.register(addDisposableDomListener(this._accessiblityBuffer, 'focus',() => this._refreshAccessibilityBuffer())); - this.register(addDisposableDomListener(this._accessiblityBuffer, 'focusout',() => this._isAccessibilityBufferActive = false)); - - this._handleColorChange(themeService.colors); - this.register(themeService.onChangeColors(e => this._handleColorChange(e))); - this._handleFontOptionChange(optionsService.options); - this.register(optionsService.onMultipleOptionChange(['fontSize', 'fontFamily', 'letterSpacing', 'lineHeight'], () => this._handleFontOptionChange(optionsService.options))); - this.register(toDisposable(() => this._accessiblityBuffer.remove())); - } - - private _refreshAccessibilityBuffer(): void { - if (!this._terminal.viewport) { - return; - } - this._isAccessibilityBufferActive = true; - const { bufferElements } = this._terminal.viewport.getBufferElements(0); - for (const element of bufferElements) { - if (element.textContent) { - element.textContent = element.textContent.replace(new RegExp(' ', 'g'), '\xA0'); - } - } - this._accessiblityBuffer.replaceChildren(...bufferElements); - this._accessiblityBuffer.scrollTop = this._accessiblityBuffer.scrollHeight; - } - - private _handleColorChange(colorSet: ReadonlyColorSet): void { - this._accessiblityBuffer.style.backgroundColor = colorSet.background.css; - this._accessiblityBuffer.style.color = colorSet.foreground.css; - } - - private _handleFontOptionChange(options: Required): void { - this._accessiblityBuffer.style.fontFamily = options.fontFamily; - this._accessiblityBuffer.style.fontSize = `${options.fontSize}px`; - this._accessiblityBuffer.style.lineHeight = `${options.lineHeight * (this._renderService.dimensions.css.cell.height)}px`; - this._accessiblityBuffer.style.letterSpacing = `${options.letterSpacing}px`; - } -} diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 92f955477..9aebd1bd1 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -33,7 +33,6 @@ import * as Browser from 'common/Platform'; import { addDisposableDomListener } from 'browser/Lifecycle'; import * as Strings from 'browser/LocalizableStrings'; import { AccessibilityManager } from './AccessibilityManager'; -import { AccessibleBuffer } from './AccessibleBuffer'; import { ITheme, IMarker, IDisposable, ILinkProvider, IDecorationOptions, IDecoration } from 'xterm'; import { DomRenderer } from 'browser/renderer/dom/DomRenderer'; import { KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent, ColorIndex, ColorRequestType } from 'common/Types'; @@ -72,7 +71,6 @@ export class Terminal extends CoreTerminal implements ITerminal { private _viewportElement: HTMLElement | undefined; private _helperContainer: HTMLElement | undefined; private _compositionView: HTMLElement | undefined; - private _accessibleBuffer: AccessibleBuffer | undefined; private _overviewRulerRenderer: OverviewRulerRenderer | undefined; @@ -578,8 +576,6 @@ export class Terminal extends CoreTerminal implements ITerminal { // Listen for mouse events and translate // them into terminal mouse protocols. this.bindMouse(); - - this._accessibleBuffer = this._instantiationService.createInstance(AccessibleBuffer, this); } private _createRenderer(): IRenderer { @@ -771,7 +767,7 @@ export class Terminal extends CoreTerminal implements ITerminal { */ this.register(addDisposableDomListener(el, 'mousedown', (ev: MouseEvent) => { ev.preventDefault(); - if (this._accessibleBuffer?.isAccessibilityBufferActive) { + if (this._accessibilityManager?.isAccessibilityBufferActive) { return; } this.focus();