a11y: fix accessibility insights pass issues (#2420)

* fix: log entry buttons have same aria name

* a11y: add titles to html documents

* a11y: tabs with the same name should be possible to identify

* a11y: do not include list control localized name into aria label

* a11y: add log entries index for scan mode

---------

Co-authored-by: Eugene Olonov <v-eolonov@microsoft.com>
This commit is contained in:
Eugene 2023-04-03 13:53:13 -07:00 коммит произвёл GitHub
Родитель 68c7bd9f25
Коммит 0f67533462
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
12 изменённых файлов: 69 добавлений и 20 удалений

Просмотреть файл

@ -46,7 +46,7 @@
<link rel="stylesheet" data-theme-component="true" id="themeVars" href="./themes/light.css"/>
<link rel="stylesheet" data-theme-component="true" href="./css/fonts.css"/>
<link rel="stylesheet" data-theme-component="true" href="./css/redline.css"/>
<title></title>
<title>Bot Framework Emulator</title>
</head>
<body>
<noscript>

Просмотреть файл

@ -15,6 +15,7 @@
width: 100%;
}
</style>
<title>Bot Framework Emulator splash screen</title>
</head>
<body>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"

Просмотреть файл

@ -75,4 +75,16 @@
&::-webkit-scrollbar-thumb {
background-color: var(--scrollbar-color);
}
& .sr-only {
border: 0;
clip-path: inset(50%);
clip: rect(1px, 1px, 1px, 1px);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
white-space: nowrap;
width: 1px;
}
}

Просмотреть файл

@ -11,6 +11,8 @@ declare namespace LogScssNamespace {
log: string;
source: string;
spaced: string;
'sr-only': string;
srOnly: string;
srcDst: string;
timestamp: string;
}

Просмотреть файл

@ -96,6 +96,7 @@ export class Log extends React.Component<LogProps, LogState> {
currentlyInspectedActivity={this.state.currentlyInspectedActivity}
document={this.props.document}
entry={entry}
entryIndex={key + 1}
key={`entry-${key++}`}
/>
))}

Просмотреть файл

@ -55,6 +55,7 @@ import * as styles from './log.scss';
export interface LogEntryProps {
document: any;
entry: ILogEntry;
entryIndex: number;
currentlyInspectedActivity?: any;
launchLuisEditor?: () => void;
setInspectorObjects?: (documentId: string, objs: any) => void;
@ -96,6 +97,7 @@ export class LogEntry extends React.Component<LogEntryProps> {
// any rendered inspectable items will add themselves to this.inspectableObjects
const innerJsx = (
<>
<span className={styles.srOnly}>Log entry {this.props.entryIndex} </span>
{this.renderTimestamp(this.props.entry.timestamp)}
{this.props.entry.items.map((item, key) => this.renderItem(item, '' + key))}
</>
@ -188,7 +190,11 @@ export class LogEntry extends React.Component<LogEntryProps> {
renderExternalLinkItem(text: string, hyperlink: string, key: string) {
return (
<span key={key} className={styles.spaced}>
<button className={styles.link} onClick={() => window.open(hyperlink, '_blank')}>
<button
aria-label={`${text}. Log entry ${this.props.entryIndex}`}
className={styles.link}
onClick={() => window.open(hyperlink, '_blank')}
>
{text}
</button>
</span>
@ -198,7 +204,11 @@ export class LogEntry extends React.Component<LogEntryProps> {
renderAppSettingsItem(text: string, key: string) {
return (
<span key={key} className={styles.spaced}>
<button className={styles.link} onClick={() => this.props.showAppSettings()}>
<button
aria-label={`${text}. Log entry ${this.props.entryIndex}`}
className={styles.link}
onClick={() => this.props.showAppSettings()}
>
{text}
</button>
</span>
@ -207,7 +217,7 @@ export class LogEntry extends React.Component<LogEntryProps> {
renderExceptionItem(err: Error, key: string) {
return (
<span key={key} className={`${styles.spaced} ${styles.level3}`}>
<span role="alert" key={key} className={`${styles.spaced} ${styles.level3}`}>
{err && err.message ? err.message : ''}
</span>
);
@ -227,7 +237,11 @@ export class LogEntry extends React.Component<LogEntryProps> {
return (
<span key={key} onMouseOver={() => this.highlight(obj)} onMouseLeave={() => this.highlight({})}>
<span className={`inspectable-item ${styles.spaced} ${styles.level0}`}>
<button className={styles.link} onClick={() => this.inspectAndHighlightInWebchat(obj)}>
<button
aria-label={`${title}. Log entry ${this.props.entryIndex}`}
className={styles.link}
onClick={() => this.inspectAndHighlightInWebchat(obj)}
>
{title}
</button>
</span>
@ -250,7 +264,11 @@ export class LogEntry extends React.Component<LogEntryProps> {
if (obj) {
return (
<span key={key} className={`network-req-item ${styles.spaced} ${styles.level0}`}>
<button className={styles.link} onClick={() => this.inspect(obj)}>
<button
aria-label={`${method} request. Log entry ${this.props.entryIndex}`}
className={styles.link}
onClick={() => this.inspect(obj)}
>
{method}
</button>
</span>
@ -292,7 +310,11 @@ export class LogEntry extends React.Component<LogEntryProps> {
if (obj) {
return (
<span key={key} className={`network-res-item ${styles.spaced} ${styles.level0}`}>
<button className={styles.link} onClick={() => this.inspect(obj)}>
<button
aria-label={`${statusCode} response. Log entry ${this.props.entryIndex}`}
className={styles.link}
onClick={() => this.inspect(obj)}
>
{statusCode}
</button>
</span>
@ -310,7 +332,11 @@ export class LogEntry extends React.Component<LogEntryProps> {
return (
<span key={key} className={`${styles.spaced} ${styles.level3}`}>
{text + ' '}
<button className={styles.link} onClick={() => this.props.reconnectNgrok()}>
<button
aria-label={`Please recoonect, Ngrok connection expired. Log entry ${this.props.entryIndex}`}
className={styles.link}
onClick={() => this.props.reconnectNgrok()}
>
Please reconnect.
</button>
</span>
@ -321,7 +347,11 @@ export class LogEntry extends React.Component<LogEntryProps> {
return (
<span key={key} className={`text-item ${styles.spaced} ${styles.level3}`}>
{`${text} Please `}
<a className={styles.link} onClick={() => this.props.launchLuisEditor()}>
<a
aria-label={`Connect your bot to LUIS. Log entry ${this.props.entryIndex}`}
className={styles.link}
onClick={() => this.props.launchLuisEditor()}
>
connect your bot to LUIS
</a>
{` using the services pane.`}

Просмотреть файл

@ -125,12 +125,7 @@ export abstract class ServicePane<
}
return (
<ExpandCollapseContent>
<ul
className={styles.servicePaneList}
ref={ul => (this.listRef = ul)}
tabIndex={0}
aria-label={`${ariaLabel} list`}
>
<ul className={styles.servicePaneList} ref={ul => (this.listRef = ul)} tabIndex={0} aria-label={ariaLabel}>
{links}
</ul>
{additionalContent}

Просмотреть файл

@ -87,6 +87,7 @@ export const NgrokTab = (props: NgrokTabProps) => {
active={props.active}
dirty={props.dirty}
documentId={props.documentId}
index={props.index}
label={props.label}
onCloseClick={props.onCloseClick}
hideIcon={true}

Просмотреть файл

@ -42,6 +42,7 @@ import { DOCUMENT_ID_APP_SETTINGS, DOCUMENT_ID_MARKDOWN_PAGE, DOCUMENT_ID_WELCOM
import * as styles from './tab.scss';
export interface TabProps {
index: number;
active?: boolean;
dirty?: boolean;
documentId?: string;
@ -75,7 +76,7 @@ export class Tab extends React.Component<TabProps, TabState> {
}
public render() {
const { active, label } = this.props;
const { active, label, index } = this.props;
const activeClassName = active ? styles.activeEditorTab : '';
const draggedOverClassName = this.state.draggedOver ? styles.draggedOverEditorTab : '';
const iconClass = this.iconClass;
@ -101,7 +102,7 @@ export class Tab extends React.Component<TabProps, TabState> {
className={styles.tabFocusTarget}
role="tab"
tabIndex={0}
aria-label={`${label}`}
aria-label={`${label}. Tab ${index}`}
aria-selected={active}
aria-description={isLinux() && active ? 'selected' : undefined}
ref={this.setTabRef}
@ -110,7 +111,7 @@ export class Tab extends React.Component<TabProps, TabState> {
</div>
<button
type="button"
title={`Close ${label} tab`}
title={`Close ${label} tab. Tab ${index}`}
className={styles.editorTabClose}
onKeyPress={this.onCloseButtonKeyPress}
onClick={this.onCloseClick}

Просмотреть файл

@ -36,7 +36,7 @@ import { swapTabs, toggleDraggingTab } from '@bfemulator/app-shared';
import { Tab, TabProps } from './tab';
const mapDispatchToProps = (dispatch, ownProps: TabProps): TabProps => ({
const mapDispatchToProps = (dispatch, ownProps: TabProps): Partial<TabProps> => ({
toggleDraggingTab: (toggle: boolean) => dispatch(toggleDraggingTab(toggle)),
swapTabs: (editorKey: string, owningEditor: string, tabId: string) =>
dispatch(swapTabs(editorKey, owningEditor, tabId, ownProps.documentId)),

Просмотреть файл

@ -181,6 +181,7 @@ export class TabBar extends React.Component<TabBarProps, TabBarState> {
active: isActive,
dirty: document.dirty,
documentId: documentId,
index: index + 1,
label: this.getTabLabel(document),
onCloseClick: this.props.closeTab,
};

Просмотреть файл

@ -74,7 +74,12 @@ class TabbedDocumentContentWrapperComponent extends Component<TabbedDocumentCont
Object.keys(this.props.primaryEditor.documents).length > 1;
return (
<div className={styles.contentWrapper} hidden={this.props.hidden} onClickCapture={this.onClick}>
<div
className={styles.contentWrapper}
hidden={this.props.hidden}
aria-hidden={this.props.hidden}
onClickCapture={this.onClick}
>
{this.props.children}
<ContentOverlay documentId={this.props.documentId} />
{splittingEnabled ? (