44 */
55
66import * as Strings from 'browser/LocalizableStrings' ;
7- import { ITerminal , IRenderDebouncer } from 'browser/Types' ;
7+ import { ITerminal , IRenderDebouncer , ReadonlyColorSet } from 'browser/Types' ;
88import { IBuffer } from 'common/buffer/Types' ;
99import { isMac } from 'common/Platform' ;
1010import { TimeBasedDebouncer } from 'browser/TimeBasedDebouncer' ;
1111import { addDisposableDomListener } from 'browser/Lifecycle' ;
1212import { Disposable , toDisposable } from 'common/Lifecycle' ;
1313import { ScreenDprMonitor } from 'browser/ScreenDprMonitor' ;
14- import { IRenderService } from 'browser/services/Services' ;
14+ import { IRenderService , IThemeService } from 'browser/services/Services' ;
15+ import { IOptionsService } from 'common/services/Services' ;
16+ import { ITerminalOptions } from 'xterm' ;
1517
1618const MAX_ROWS_TO_READ = 20 ;
1719
@@ -26,6 +28,7 @@ export class AccessibilityManager extends Disposable {
2628 private _rowElements : HTMLElement [ ] ;
2729 private _liveRegion : HTMLElement ;
2830 private _liveRegionLineCount : number = 0 ;
31+ private _accessiblityBuffer : HTMLElement ;
2932
3033 private _renderRowsDebouncer : IRenderDebouncer ;
3134 private _screenDprMonitor : ScreenDprMonitor ;
@@ -48,7 +51,9 @@ export class AccessibilityManager extends Disposable {
4851
4952 constructor (
5053 private readonly _terminal : ITerminal ,
51- private readonly _renderService : IRenderService
54+ @IOptionsService optionsService : IOptionsService ,
55+ @IRenderService private readonly _renderService : IRenderService ,
56+ @IThemeService themeService : IThemeService
5257 ) {
5358 super ( ) ;
5459 this . _accessibilityTreeRoot = document . createElement ( 'div' ) ;
@@ -83,7 +88,16 @@ export class AccessibilityManager extends Disposable {
8388 if ( ! this . _terminal . element ) {
8489 throw new Error ( 'Cannot enable accessibility before Terminal.open' ) ;
8590 }
86- this . _terminal . element . insertAdjacentElement ( 'afterbegin' , this . _accessibilityTreeRoot ) ;
91+
92+ this . _accessiblityBuffer = document . createElement ( 'div' ) ;
93+ this . _accessiblityBuffer . ariaLabel = Strings . accessibilityBuffer ;
94+ this . _accessiblityBuffer . classList . add ( 'xterm-accessibility-buffer' ) ;
95+
96+ // TODO: this is needed when content editable is false
97+ this . _refreshAccessibilityBuffer ( ) ;
98+ this . _accessiblityBuffer . addEventListener ( 'focus' , ( ) => this . _refreshAccessibilityBuffer ( ) ) ;
99+ this . _terminal . element . insertAdjacentElement ( 'afterbegin' , this . _accessiblityBuffer ) ;
100+
87101
88102 this . register ( this . _renderRowsDebouncer ) ;
89103 this . register ( this . _terminal . onResize ( e => this . _handleResize ( e . rows ) ) ) ;
@@ -97,6 +111,11 @@ export class AccessibilityManager extends Disposable {
97111 this . register ( this . _terminal . onBlur ( ( ) => this . _clearLiveRegion ( ) ) ) ;
98112 this . register ( this . _renderService . onDimensionsChange ( ( ) => this . _refreshRowsDimensions ( ) ) ) ;
99113
114+ this . _handleColorChange ( themeService . colors ) ;
115+ this . register ( themeService . onChangeColors ( e => this . _handleColorChange ( e ) ) ) ;
116+ this . _handleFontOptionChange ( optionsService . options ) ;
117+ this . register ( optionsService . onMultipleOptionChange ( [ 'fontSize' , 'fontFamily' ] , ( ) => this . _handleFontOptionChange ( optionsService . options ) ) ) ;
118+
100119 this . _screenDprMonitor = new ScreenDprMonitor ( window ) ;
101120 this . register ( this . _screenDprMonitor ) ;
102121 this . _screenDprMonitor . setListener ( ( ) => this . _refreshRowsDimensions ( ) ) ;
@@ -299,4 +318,33 @@ export class AccessibilityManager extends Disposable {
299318 this . _liveRegion . textContent += this . _charsToAnnounce ;
300319 this . _charsToAnnounce = '' ;
301320 }
321+
322+
323+ private _refreshAccessibilityBuffer ( ) : void {
324+ if ( ! this . _terminal . viewport ) {
325+ return ;
326+ }
327+
328+ const { bufferElements, cursorElement } = this . _terminal . viewport . getBufferElements ( 0 ) ;
329+ for ( const element of bufferElements ) {
330+ if ( element . textContent ) {
331+ element . textContent = element . textContent . replace ( new RegExp ( ' ' , 'g' ) , '\xA0' ) ;
332+ }
333+ }
334+ this . _accessiblityBuffer . tabIndex = 0 ;
335+ this . _accessiblityBuffer . ariaRoleDescription = 'document' ;
336+ this . _accessiblityBuffer . replaceChildren ( ...bufferElements ) ;
337+ this . _accessiblityBuffer . scrollTop = this . _accessiblityBuffer . scrollHeight ;
338+ this . _accessiblityBuffer . focus ( ) ;
339+ }
340+
341+ private _handleColorChange ( colorSet : ReadonlyColorSet ) : void {
342+ this . _accessiblityBuffer . style . backgroundColor = colorSet . background . css ;
343+ this . _accessiblityBuffer . style . color = colorSet . foreground . css ;
344+ }
345+
346+ private _handleFontOptionChange ( options : Required < ITerminalOptions > ) : void {
347+ this . _accessiblityBuffer . style . fontFamily = options . fontFamily ;
348+ this . _accessiblityBuffer . style . fontSize = `${ options . fontSize } px` ;
349+ }
302350}
0 commit comments