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:
Родитель
68c7bd9f25
Коммит
0f67533462
|
@ -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 ? (
|
||||
|
|
Загрузка…
Ссылка в новой задаче