fix(a11y): enable screen reader support for python terminal (#60154)

This commit is contained in:
Huyen Nguyen 2025-06-06 19:28:28 +07:00 committed by GitHub
parent ed2bdfcf96
commit 46c008c112
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 26 additions and 3 deletions

View File

@ -849,7 +849,8 @@
"opens-new-window": "Opens in new window",
"rsa-checkbox": "I have tried the Read-Search-Ask method",
"similar-questions-checkbox": "I have searched for similar questions that have already been answered on the forum",
"edit-my-profile": "Edit my profile"
"edit-my-profile": "Edit my profile",
"terminal-output": "Terminal output"
},
"flash": {
"no-email-in-userinfo": "We could not retrieve an email from your chosen provider. Please try another provider or use the 'Continue with Email' option.",

View File

@ -1,6 +1,7 @@
import React, { MutableRefObject, useEffect, useRef } from 'react';
import type { IDisposable, Terminal } from 'xterm';
import type { FitAddon } from 'xterm-addon-fit';
import { useTranslation } from 'react-i18next';
import { registerTerminal } from '../utils/python-worker-handler';
@ -23,6 +24,7 @@ export const XtermTerminal = ({
dimensions?: { height: number; width: number };
}) => {
const termContainerRef = useRef<HTMLDivElement | null>(null);
const { t } = useTranslation();
useEffect(() => {
void registerServiceWorker();
@ -43,7 +45,25 @@ export const XtermTerminal = ({
if (termContainerRef.current) term.open(termContainerRef.current);
fitAddon.fit();
const print = (text?: string) => term?.writeln(`${text ?? ''}`);
// xterm does provide a11y support via the `screenReaderMode` option.
// However, the mode only works best if the user interacts with the terminal directly.
// Since we feed the content to xterm, it's better to control the output a11y ourselves.
const termContainerDiv =
termContainerRef.current?.querySelector('.xterm');
const outputForScreenReader = document.createElement('div');
outputForScreenReader.setAttribute('role', 'region');
outputForScreenReader.setAttribute(
'aria-label',
t('aria.terminal-output')
);
outputForScreenReader.classList.add('sr-only');
termContainerDiv?.appendChild(outputForScreenReader);
const print = (text?: string) => {
term?.writeln(`${text ?? ''}`);
outputForScreenReader.textContent = text ?? '';
};
// TODO: prevent user from moving cursor outside the current input line and
// handle insertion and deletion properly. While backspace and delete don't
@ -94,6 +114,8 @@ export const XtermTerminal = ({
term?.write('\x1bc');
disposables.forEach(disposable => disposable.dispose());
disposables.length = 0;
outputForScreenReader.textContent = '';
};
registerTerminal({ print, input, reset });
}
@ -103,7 +125,7 @@ export const XtermTerminal = ({
return () => {
term?.dispose();
};
}, [xtermFitRef]);
}, [xtermFitRef, t]);
useEffect(() => {
if (xtermFitRef.current) xtermFitRef.current.fit();