Fix loading and clickable div screen reader issues (#9088)

* Fix loading and clickable div screen reader issues

* Change back to default clickable to false
This commit is contained in:
Charles Gagnon
2020-02-07 13:27:29 -08:00
committed by GitHub
parent 39ac8498dc
commit fe9ffddd3b
5 changed files with 42 additions and 21 deletions

View File

@@ -68,7 +68,7 @@ export class BdcDashboardOverviewPage extends BdcDashboardPage {
this.propertiesErrorMessage = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ display: 'none', CSSStyles: { ...cssStyles.errorText } }).component(); this.propertiesErrorMessage = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ display: 'none', CSSStyles: { ...cssStyles.errorText } }).component();
rootContainer.addItem(this.propertiesErrorMessage, { flex: '0 0 auto' }); rootContainer.addItem(this.propertiesErrorMessage, { flex: '0 0 auto' });
this.propertiesContainer = view.modelBuilder.divContainer().component(); this.propertiesContainer = view.modelBuilder.divContainer().withProperties<azdata.DivContainerProperties>({ clickable: false }).component();
// Row 1 // Row 1
const row1 = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', height: '30px', alignItems: 'center' }).component(); const row1 = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', height: '30px', alignItems: 'center' }).component();
@@ -76,14 +76,20 @@ export class BdcDashboardOverviewPage extends BdcDashboardPage {
// Cluster State // Cluster State
const clusterStateLabel = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.clusterState }).component(); const clusterStateLabel = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.clusterState }).component();
const clusterStateValue = view.modelBuilder.text().component(); const clusterStateValue = view.modelBuilder.text().component();
this.clusterStateLoadingComponent = view.modelBuilder.loadingComponent().withItem(clusterStateValue).component(); this.clusterStateLoadingComponent = view.modelBuilder.loadingComponent()
.withItem(clusterStateValue)
.withProperties<azdata.LoadingComponentProperties>({ loadingCompletedText: loc.loadingClusterStateCompleted })
.component();
row1.addItem(clusterStateLabel, { CSSStyles: { 'width': `${clusterStateLabelColumnWidth}px`, 'min-width': `${clusterStateLabelColumnWidth}px`, 'user-select': 'none', 'font-weight': 'bold' } }); row1.addItem(clusterStateLabel, { CSSStyles: { 'width': `${clusterStateLabelColumnWidth}px`, 'min-width': `${clusterStateLabelColumnWidth}px`, 'user-select': 'none', 'font-weight': 'bold' } });
row1.addItem(this.clusterStateLoadingComponent, { CSSStyles: { 'width': `${clusterStateValueColumnWidth}px`, 'min-width': `${clusterStateValueColumnWidth}px` } }); row1.addItem(this.clusterStateLoadingComponent, { CSSStyles: { 'width': `${clusterStateValueColumnWidth}px`, 'min-width': `${clusterStateValueColumnWidth}px` } });
// Health Status // Health Status
const healthStatusLabel = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.healthStatusWithColon }).component(); const healthStatusLabel = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.healthStatusWithColon }).component();
const healthStatusValue = view.modelBuilder.text().component(); const healthStatusValue = view.modelBuilder.text().component();
this.clusterHealthStatusLoadingComponent = view.modelBuilder.loadingComponent().withItem(healthStatusValue).component(); this.clusterHealthStatusLoadingComponent = view.modelBuilder.loadingComponent()
.withItem(healthStatusValue)
.withProperties<azdata.LoadingComponentProperties>({ loadingCompletedText: loc.loadingHealthStatusCompleted })
.component();
row1.addItem(healthStatusLabel, { CSSStyles: { 'width': `${healthStatusColumnWidth}px`, 'min-width': `${healthStatusColumnWidth}px`, 'user-select': 'none', 'font-weight': 'bold' } }); row1.addItem(healthStatusLabel, { CSSStyles: { 'width': `${healthStatusColumnWidth}px`, 'min-width': `${healthStatusColumnWidth}px`, 'user-select': 'none', 'font-weight': 'bold' } });
row1.addItem(this.clusterHealthStatusLoadingComponent, { CSSStyles: { 'width': `${healthStatusColumnWidth}px`, 'min-width': `${healthStatusColumnWidth}px` } }); row1.addItem(this.clusterHealthStatusLoadingComponent, { CSSStyles: { 'width': `${healthStatusColumnWidth}px`, 'min-width': `${healthStatusColumnWidth}px` } });

View File

@@ -76,6 +76,8 @@ export const credentials = localize('mount.credentials.title', "Credentials");
export const credentialsInfo = localize('mount.credentials.info', "Mount credentials for authentication to remote data source for reads"); export const credentialsInfo = localize('mount.credentials.info', "Mount credentials for authentication to remote data source for reads");
export const refreshMount = localize('refreshmount.dialog.title', "Refresh Mount"); export const refreshMount = localize('refreshmount.dialog.title', "Refresh Mount");
export const deleteMount = localize('deleteMount.dialog.title', "Delete Mount"); export const deleteMount = localize('deleteMount.dialog.title', "Delete Mount");
export const loadingClusterStateCompleted = localize('bdc.dashboard.loadingClusterStateCompleted', "Loading cluster state completed");
export const loadingHealthStatusCompleted = localize('bdc.dashboard.loadingHealthStatusCompleted', "Loading health status completed");
// Errors // Errors
export const usernameRequired = localize('err.controller.username.required', "Username is required"); export const usernameRequired = localize('err.controller.username.required', "Username is required");

View File

@@ -6,7 +6,7 @@ import 'vs/css!./media/divContainer';
import { import {
Component, Input, Inject, ChangeDetectorRef, forwardRef, Component, Input, Inject, ChangeDetectorRef, forwardRef,
ViewChild, ElementRef, OnDestroy, ViewChild, ElementRef, OnDestroy, Renderer2, AfterViewInit
} from '@angular/core'; } from '@angular/core';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
@@ -24,7 +24,7 @@ class DivItem {
@Component({ @Component({
template: ` template: `
<div #divContainer *ngIf="items" class="divContainer" [style.height]="height" [style.width]="width" [style.display]="display" (click)="onClick()" (keyup)="onKey($event)" [attr.role]="ariaRole" [attr.aria-selected]="ariaSelected"> <div #divContainer *ngIf="items" class="divContainer" [style.height]="height" [style.width]="width" [style.display]="display" (keyup)="onKey($event)" [attr.role]="ariaRole" [attr.aria-selected]="ariaSelected">
<div *ngFor="let item of items" [style.order]="getItemOrder(item)" [ngStyle]="getItemStyles(item)"> <div *ngFor="let item of items" [style.order]="getItemOrder(item)" [ngStyle]="getItemStyles(item)">
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore"> <model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore">
</model-component-wrapper> </model-component-wrapper>
@@ -32,22 +32,30 @@ class DivItem {
</div> </div>
` `
}) })
export default class DivContainer extends ContainerBase<azdata.DivItemLayout> implements IComponent, OnDestroy { export default class DivContainer extends ContainerBase<azdata.DivItemLayout> implements IComponent, OnDestroy, AfterViewInit {
@Input() descriptor: IComponentDescriptor; @Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore; @Input() modelStore: IModelStore;
@ViewChild('divContainer', { read: ElementRef }) divContainer; @ViewChild('divContainer', { read: ElementRef }) divContainer;
private _height: string; private _height: string;
private _width: string; private _width: string;
private _overflowY: string; private _overflowY: string;
private viewInitialized: boolean;
private cancelClick: Function;
constructor( constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef, @Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef @Inject(forwardRef(() => ElementRef)) el: ElementRef,
@Inject(forwardRef(() => Renderer2)) private renderer: Renderer2
) { ) {
super(changeRef, el); super(changeRef, el);
this._overflowY = ''; // default this._overflowY = ''; // default
} }
ngAfterViewInit() {
this.viewInitialized = true;
this.updateClickListener();
}
ngOnInit(): void { ngOnInit(): void {
this.baseInit(); this.baseInit();
} }
@@ -97,6 +105,7 @@ export default class DivContainer extends ContainerBase<azdata.DivItemLayout> im
element.removeAttribute('tabIndex'); element.removeAttribute('tabIndex');
element.style.cursor = 'default'; element.style.cursor = 'default';
} }
this.updateClickListener();
} }
private onClick() { private onClick() {
@@ -148,4 +157,17 @@ export default class DivContainer extends ContainerBase<azdata.DivItemLayout> im
public getItemStyles(item: DivItem): { [key: string]: string } { public getItemStyles(item: DivItem): { [key: string]: string } {
return item.config && item.config.CSSStyles ? item.config.CSSStyles : {}; return item.config && item.config.CSSStyles ? item.config.CSSStyles : {};
} }
private updateClickListener(): void {
// We can't hook into the listener until the view is initialized
if (!this.viewInitialized) {
return;
}
if (this.clickable && !this.cancelClick) {
this.cancelClick = this.renderer.listen(this.divContainer.nativeElement, 'click', () => this.onClick());
} else if (!this.clickable) {
this.cancelClick();
this.cancelClick = undefined;
}
}
} }

View File

@@ -13,13 +13,11 @@ import * as azdata from 'azdata';
import { ComponentBase } from 'sql/workbench/browser/modelComponents/componentBase'; import { ComponentBase } from 'sql/workbench/browser/modelComponents/componentBase';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { IComponent, IComponentDescriptor, IModelStore } from 'sql/platform/dashboard/browser/interfaces'; import { IComponent, IComponentDescriptor, IModelStore } from 'sql/platform/dashboard/browser/interfaces';
import { status } from 'vs/base/browser/ui/aria/aria';
@Component({ @Component({
selector: 'modelview-loadingComponent', selector: 'modelview-loadingComponent',
template: ` template: `
<div role="status" aria-live="polite" class="modelview-loading-component-status-message">
{{getStatusText()}}
</div>
<div class="modelview-loadingComponent-container" aria-busy="true" *ngIf="loading"> <div class="modelview-loadingComponent-container" aria-busy="true" *ngIf="loading">
<div class="modelview-loadingComponent-spinner" [title]="getStatusText()" #spinnerElement></div> <div class="modelview-loadingComponent-spinner" [title]="getStatusText()" #spinnerElement></div>
<div *ngIf="showText" class="modelview-loadingComponent-status-text">{{getStatusText()}}</div> <div *ngIf="showText" class="modelview-loadingComponent-status-text">{{getStatusText()}}</div>
@@ -66,7 +64,11 @@ export default class LoadingComponent extends ComponentBase implements IComponen
} }
public setProperties(properties: { [key: string]: any; }): void { public setProperties(properties: { [key: string]: any; }): void {
const wasLoading = this.loading;
super.setProperties(properties); super.setProperties(properties);
if (wasLoading && !this.loading) {
status(this.getStatusText());
}
} }
public get loading(): boolean { public get loading(): boolean {

View File

@@ -32,14 +32,3 @@
.modelview-loadingComponent-content-loading { .modelview-loadingComponent-content-loading {
display: none; display: none;
} }
/* We want to make this on DOM but not visible so that screen reader can read the status message */
.modelview-loading-component-status-message {
top: -1;
left: -1px;
width: 1px;
height: 1px;
position: absolute;
overflow: hidden;
}