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" 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/fonts.css"/>
|
||||||
<link rel="stylesheet" data-theme-component="true" href="./css/redline.css"/>
|
<link rel="stylesheet" data-theme-component="true" href="./css/redline.css"/>
|
||||||
<title></title>
|
<title>Bot Framework Emulator</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<title>Bot Framework Emulator splash screen</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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"
|
<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 {
|
&::-webkit-scrollbar-thumb {
|
||||||
background-color: var(--scrollbar-color);
|
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;
|
log: string;
|
||||||
source: string;
|
source: string;
|
||||||
spaced: string;
|
spaced: string;
|
||||||
|
'sr-only': string;
|
||||||
|
srOnly: string;
|
||||||
srcDst: string;
|
srcDst: string;
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,6 +96,7 @@ export class Log extends React.Component<LogProps, LogState> {
|
||||||
currentlyInspectedActivity={this.state.currentlyInspectedActivity}
|
currentlyInspectedActivity={this.state.currentlyInspectedActivity}
|
||||||
document={this.props.document}
|
document={this.props.document}
|
||||||
entry={entry}
|
entry={entry}
|
||||||
|
entryIndex={key + 1}
|
||||||
key={`entry-${key++}`}
|
key={`entry-${key++}`}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -55,6 +55,7 @@ import * as styles from './log.scss';
|
||||||
export interface LogEntryProps {
|
export interface LogEntryProps {
|
||||||
document: any;
|
document: any;
|
||||||
entry: ILogEntry;
|
entry: ILogEntry;
|
||||||
|
entryIndex: number;
|
||||||
currentlyInspectedActivity?: any;
|
currentlyInspectedActivity?: any;
|
||||||
launchLuisEditor?: () => void;
|
launchLuisEditor?: () => void;
|
||||||
setInspectorObjects?: (documentId: string, objs: any) => 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
|
// any rendered inspectable items will add themselves to this.inspectableObjects
|
||||||
const innerJsx = (
|
const innerJsx = (
|
||||||
<>
|
<>
|
||||||
|
<span className={styles.srOnly}>Log entry {this.props.entryIndex} </span>
|
||||||
{this.renderTimestamp(this.props.entry.timestamp)}
|
{this.renderTimestamp(this.props.entry.timestamp)}
|
||||||
{this.props.entry.items.map((item, key) => this.renderItem(item, '' + key))}
|
{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) {
|
renderExternalLinkItem(text: string, hyperlink: string, key: string) {
|
||||||
return (
|
return (
|
||||||
<span key={key} className={styles.spaced}>
|
<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}
|
{text}
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
@ -198,7 +204,11 @@ export class LogEntry extends React.Component<LogEntryProps> {
|
||||||
renderAppSettingsItem(text: string, key: string) {
|
renderAppSettingsItem(text: string, key: string) {
|
||||||
return (
|
return (
|
||||||
<span key={key} className={styles.spaced}>
|
<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}
|
{text}
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
@ -207,7 +217,7 @@ export class LogEntry extends React.Component<LogEntryProps> {
|
||||||
|
|
||||||
renderExceptionItem(err: Error, key: string) {
|
renderExceptionItem(err: Error, key: string) {
|
||||||
return (
|
return (
|
||||||
<span key={key} className={`${styles.spaced} ${styles.level3}`}>
|
<span role="alert" key={key} className={`${styles.spaced} ${styles.level3}`}>
|
||||||
{err && err.message ? err.message : ''}
|
{err && err.message ? err.message : ''}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
@ -227,7 +237,11 @@ export class LogEntry extends React.Component<LogEntryProps> {
|
||||||
return (
|
return (
|
||||||
<span key={key} onMouseOver={() => this.highlight(obj)} onMouseLeave={() => this.highlight({})}>
|
<span key={key} onMouseOver={() => this.highlight(obj)} onMouseLeave={() => this.highlight({})}>
|
||||||
<span className={`inspectable-item ${styles.spaced} ${styles.level0}`}>
|
<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}
|
{title}
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
@ -250,7 +264,11 @@ export class LogEntry extends React.Component<LogEntryProps> {
|
||||||
if (obj) {
|
if (obj) {
|
||||||
return (
|
return (
|
||||||
<span key={key} className={`network-req-item ${styles.spaced} ${styles.level0}`}>
|
<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}
|
{method}
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
@ -292,7 +310,11 @@ export class LogEntry extends React.Component<LogEntryProps> {
|
||||||
if (obj) {
|
if (obj) {
|
||||||
return (
|
return (
|
||||||
<span key={key} className={`network-res-item ${styles.spaced} ${styles.level0}`}>
|
<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}
|
{statusCode}
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
@ -310,7 +332,11 @@ export class LogEntry extends React.Component<LogEntryProps> {
|
||||||
return (
|
return (
|
||||||
<span key={key} className={`${styles.spaced} ${styles.level3}`}>
|
<span key={key} className={`${styles.spaced} ${styles.level3}`}>
|
||||||
{text + ' '}
|
{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.
|
Please reconnect.
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
@ -321,7 +347,11 @@ export class LogEntry extends React.Component<LogEntryProps> {
|
||||||
return (
|
return (
|
||||||
<span key={key} className={`text-item ${styles.spaced} ${styles.level3}`}>
|
<span key={key} className={`text-item ${styles.spaced} ${styles.level3}`}>
|
||||||
{`${text} Please `}
|
{`${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
|
connect your bot to LUIS
|
||||||
</a>
|
</a>
|
||||||
{` using the services pane.`}
|
{` using the services pane.`}
|
||||||
|
|
|
@ -125,12 +125,7 @@ export abstract class ServicePane<
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<ExpandCollapseContent>
|
<ExpandCollapseContent>
|
||||||
<ul
|
<ul className={styles.servicePaneList} ref={ul => (this.listRef = ul)} tabIndex={0} aria-label={ariaLabel}>
|
||||||
className={styles.servicePaneList}
|
|
||||||
ref={ul => (this.listRef = ul)}
|
|
||||||
tabIndex={0}
|
|
||||||
aria-label={`${ariaLabel} list`}
|
|
||||||
>
|
|
||||||
{links}
|
{links}
|
||||||
</ul>
|
</ul>
|
||||||
{additionalContent}
|
{additionalContent}
|
||||||
|
|
|
@ -87,6 +87,7 @@ export const NgrokTab = (props: NgrokTabProps) => {
|
||||||
active={props.active}
|
active={props.active}
|
||||||
dirty={props.dirty}
|
dirty={props.dirty}
|
||||||
documentId={props.documentId}
|
documentId={props.documentId}
|
||||||
|
index={props.index}
|
||||||
label={props.label}
|
label={props.label}
|
||||||
onCloseClick={props.onCloseClick}
|
onCloseClick={props.onCloseClick}
|
||||||
hideIcon={true}
|
hideIcon={true}
|
||||||
|
|
|
@ -42,6 +42,7 @@ import { DOCUMENT_ID_APP_SETTINGS, DOCUMENT_ID_MARKDOWN_PAGE, DOCUMENT_ID_WELCOM
|
||||||
import * as styles from './tab.scss';
|
import * as styles from './tab.scss';
|
||||||
|
|
||||||
export interface TabProps {
|
export interface TabProps {
|
||||||
|
index: number;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
dirty?: boolean;
|
dirty?: boolean;
|
||||||
documentId?: string;
|
documentId?: string;
|
||||||
|
@ -75,7 +76,7 @@ export class Tab extends React.Component<TabProps, TabState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const { active, label } = this.props;
|
const { active, label, index } = this.props;
|
||||||
const activeClassName = active ? styles.activeEditorTab : '';
|
const activeClassName = active ? styles.activeEditorTab : '';
|
||||||
const draggedOverClassName = this.state.draggedOver ? styles.draggedOverEditorTab : '';
|
const draggedOverClassName = this.state.draggedOver ? styles.draggedOverEditorTab : '';
|
||||||
const iconClass = this.iconClass;
|
const iconClass = this.iconClass;
|
||||||
|
@ -101,7 +102,7 @@ export class Tab extends React.Component<TabProps, TabState> {
|
||||||
className={styles.tabFocusTarget}
|
className={styles.tabFocusTarget}
|
||||||
role="tab"
|
role="tab"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
aria-label={`${label}`}
|
aria-label={`${label}. Tab ${index}`}
|
||||||
aria-selected={active}
|
aria-selected={active}
|
||||||
aria-description={isLinux() && active ? 'selected' : undefined}
|
aria-description={isLinux() && active ? 'selected' : undefined}
|
||||||
ref={this.setTabRef}
|
ref={this.setTabRef}
|
||||||
|
@ -110,7 +111,7 @@ export class Tab extends React.Component<TabProps, TabState> {
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
title={`Close ${label} tab`}
|
title={`Close ${label} tab. Tab ${index}`}
|
||||||
className={styles.editorTabClose}
|
className={styles.editorTabClose}
|
||||||
onKeyPress={this.onCloseButtonKeyPress}
|
onKeyPress={this.onCloseButtonKeyPress}
|
||||||
onClick={this.onCloseClick}
|
onClick={this.onCloseClick}
|
||||||
|
|
|
@ -36,7 +36,7 @@ import { swapTabs, toggleDraggingTab } from '@bfemulator/app-shared';
|
||||||
|
|
||||||
import { Tab, TabProps } from './tab';
|
import { Tab, TabProps } from './tab';
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch, ownProps: TabProps): TabProps => ({
|
const mapDispatchToProps = (dispatch, ownProps: TabProps): Partial<TabProps> => ({
|
||||||
toggleDraggingTab: (toggle: boolean) => dispatch(toggleDraggingTab(toggle)),
|
toggleDraggingTab: (toggle: boolean) => dispatch(toggleDraggingTab(toggle)),
|
||||||
swapTabs: (editorKey: string, owningEditor: string, tabId: string) =>
|
swapTabs: (editorKey: string, owningEditor: string, tabId: string) =>
|
||||||
dispatch(swapTabs(editorKey, owningEditor, tabId, ownProps.documentId)),
|
dispatch(swapTabs(editorKey, owningEditor, tabId, ownProps.documentId)),
|
||||||
|
|
|
@ -181,6 +181,7 @@ export class TabBar extends React.Component<TabBarProps, TabBarState> {
|
||||||
active: isActive,
|
active: isActive,
|
||||||
dirty: document.dirty,
|
dirty: document.dirty,
|
||||||
documentId: documentId,
|
documentId: documentId,
|
||||||
|
index: index + 1,
|
||||||
label: this.getTabLabel(document),
|
label: this.getTabLabel(document),
|
||||||
onCloseClick: this.props.closeTab,
|
onCloseClick: this.props.closeTab,
|
||||||
};
|
};
|
||||||
|
|
|
@ -74,7 +74,12 @@ class TabbedDocumentContentWrapperComponent extends Component<TabbedDocumentCont
|
||||||
Object.keys(this.props.primaryEditor.documents).length > 1;
|
Object.keys(this.props.primaryEditor.documents).length > 1;
|
||||||
|
|
||||||
return (
|
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}
|
{this.props.children}
|
||||||
<ContentOverlay documentId={this.props.documentId} />
|
<ContentOverlay documentId={this.props.documentId} />
|
||||||
{splittingEnabled ? (
|
{splittingEnabled ? (
|
||||||
|
|
Загрузка…
Ссылка в новой задаче