diff --git a/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboard.ts b/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboard.ts index d3389d92f1..ff46db0509 100644 --- a/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboard.ts +++ b/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboard.ts @@ -121,12 +121,21 @@ export class BdcDashboard extends BdcDashboardPage { width: navWidth, height: '100%' } - ).component(); + ).withProperties({ + ariaRole: 'tablist' + }).component(); this.mainAreaContainer.addItem(this.navContainer, { flex: `0 0 ${navWidth}`, CSSStyles: { 'padding': '0 20px 0 20px', 'border-right': 'solid 1px #ccc' } }); // Overview nav item - this will be the initial page - const overviewNavItemDiv = modelView.modelBuilder.divContainer().withLayout({ width: navWidth, height: '30px' }).withProperties({ clickable: true }).component(); + const overviewNavItemDiv = modelView.modelBuilder + .divContainer() + .withLayout({ width: navWidth, height: '30px' }) + .withProperties({ + clickable: true, + ariaRole: 'tab', + ariaSelected: true + }).component(); const overviewNavItemText = modelView.modelBuilder.text().withProperties({ value: localize('bdc.dashboard.overviewNavTitle', "Big Data Cluster overview") }).component(); overviewNavItemText.updateCssStyles(selectedTabCss); overviewNavItemDiv.addItem(overviewNavItemText, { CSSStyles: { 'user-select': 'text' } }); @@ -139,12 +148,14 @@ export class BdcDashboard extends BdcDashboardPage { overviewNavItemDiv.onDidClick(() => { if (this.currentTab) { this.currentTab.text.updateCssStyles(unselectedTabCss); + this.currentTab.div.ariaSelected = false; } this.mainAreaContainer.removeItem(this.currentPage); this.mainAreaContainer.addItem(overviewContainer, { flex: '0 0 100%', CSSStyles: { 'margin': '0 20px 0 20px' } }); this.currentPage = overviewContainer; this.currentTab = { serviceName: undefined, div: overviewNavItemDiv, dot: undefined, text: overviewNavItemText }; this.currentTab.text.updateCssStyles(selectedTabCss); + this.currentTab.div.ariaSelected = true; }); this.navContainer.addItem(overviewNavItemDiv, { flex: '0 0 auto' }); @@ -198,12 +209,14 @@ export class BdcDashboard extends BdcDashboardPage { } if (this.currentTab) { this.currentTab.text.updateCssStyles(unselectedTabCss); + this.currentTab.div.ariaSelected = false; } this.mainAreaContainer.removeItem(this.currentPage); this.mainAreaContainer.addItem(tabPageMapping.servicePage, { CSSStyles: { 'margin': '0 20px 0 20px' } }); this.currentPage = tabPageMapping.servicePage; this.currentTab = tabPageMapping.navTab; this.currentTab.text.updateCssStyles(selectedTabCss); + this.currentTab.div.ariaSelected = true; } /** @@ -233,7 +246,15 @@ export class BdcDashboard extends BdcDashboardPage { } function createServiceNavTab(modelBuilder: azdata.ModelBuilder, serviceStatus: ServiceStatusModel): NavTab { - const div = modelBuilder.divContainer().withLayout({ width: navWidth, height: '30px', }).withProperties({ clickable: true }).component(); + const div = modelBuilder.divContainer() + .withLayout({ + width: navWidth, + height: '30px', + }) + .withProperties({ + clickable: true, + ariaRole: 'tab' + }).component(); const innerContainer = modelBuilder.flexContainer().withLayout({ width: navWidth, height: '30px', flexFlow: 'row' }).component(); const dot = modelBuilder.text().withProperties({ value: getHealthStatusDot(serviceStatus.healthStatus), CSSStyles: { 'color': 'red', 'font-size': '40px', 'width': '20px', ...cssStyles.nonSelectableText } }).component(); innerContainer.addItem(dot, { flex: '0 0 auto' }); diff --git a/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboardOverviewPage.ts b/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboardOverviewPage.ts index 90a9b9b9d9..d422c30e3f 100644 --- a/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboardOverviewPage.ts +++ b/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboardOverviewPage.ts @@ -294,7 +294,13 @@ export class BdcDashboardOverviewPage extends BdcDashboardPage { private createServiceStatusRow(container: azdata.FlexContainer, serviceStatus: ServiceStatusModel, isLastRow: boolean): void { const serviceStatusRow = this.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', alignItems: 'center', height: '30px' }).component(); - const statusIconCell = this.modelBuilder.text().withProperties({ value: getHealthStatusIcon(serviceStatus.healthStatus), CSSStyles: { 'user-select': 'none' } }).component(); + const statusIconCell = this.modelBuilder.text() + .withProperties({ + value: getHealthStatusIcon(serviceStatus.healthStatus), + ariaRole: 'img', + title: getHealthStatusDisplayText(serviceStatus.healthStatus), + CSSStyles: { 'user-select': 'none' } + }).component(); serviceStatusRow.addItem(statusIconCell, { CSSStyles: { 'width': `${overviewIconColumnWidthPx}px`, 'min-width': `${overviewIconColumnWidthPx}px` } }); const nameCell = this.modelBuilder.text().withProperties({ value: getServiceNameDisplayText(serviceStatus.serviceName), CSSStyles: { ...cssStyles.text, ...cssStyles.hyperlink } }).component(); nameCell.onDidClick(() => { diff --git a/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboardResourceStatusPage.ts b/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboardResourceStatusPage.ts index a6160dbf8b..5d39d6fb8b 100644 --- a/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboardResourceStatusPage.ts +++ b/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcDashboardResourceStatusPage.ts @@ -168,6 +168,8 @@ function createInstanceHealthStatusRow(modelBuilder: azdata.ModelBuilder, instan const statusIconCell = modelBuilder.text() .withProperties({ value: getHealthStatusIcon(instanceStatus.healthStatus), + ariaRole: 'img', + title: getHealthStatusDisplayText(instanceStatus.healthStatus), CSSStyles: { 'user-select': 'none' } }).component(); instanceHealthStatusRow.addItem(statusIconCell, { CSSStyles: { 'width': `${healthAndStatusIconColumnWidth}px`, 'min-width': `${healthAndStatusIconColumnWidth}px` } }); diff --git a/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcServiceStatusPage.ts b/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcServiceStatusPage.ts index 2421370abd..9d2cbf0611 100644 --- a/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcServiceStatusPage.ts +++ b/extensions/big-data-cluster/src/bigDataCluster/dialog/bdcServiceStatusPage.ts @@ -46,7 +46,9 @@ export class BdcServiceStatusPage extends BdcDashboardPage { width: '100%', height: '25px' } - ).component(); + ).withProperties({ + ariaRole: 'tablist' + }).component(); this.rootContainer.addItem(this.resourceHeader, { CSSStyles: { 'padding-top': '15px' } }); @@ -96,12 +98,14 @@ export class BdcServiceStatusPage extends BdcDashboardPage { } if (this.currentTab) { this.currentTab.tab.text.updateCssStyles(cssStyles.unselectedResourceHeaderTab); + this.currentTab.tab.div.ariaSelected = false; this.resourceHeader.removeItem(this.currentTab.tab.div); this.resourceHeader.insertItem(this.currentTab.tab.div, this.currentTab.index, { flex: '0 0 auto', CSSStyles: cssStyles.unselectedTabDiv }); } this.changeSelectedTabPage(resourceStatusPage); this.currentTab = { tab: resourceHeaderTab, index: currentIndex }; this.currentTab.tab.text.updateCssStyles(cssStyles.selectedResourceHeaderTab); + this.currentTab.tab.div.ariaSelected = true; this.resourceHeader.removeItem(this.currentTab.tab.div); this.resourceHeader.insertItem(this.currentTab.tab.div, this.currentTab.index, { flex: '0 0 auto', CSSStyles: cssStyles.selectedTabDiv }); }); @@ -110,6 +114,7 @@ export class BdcServiceStatusPage extends BdcDashboardPage { this.changeSelectedTabPage(resourceStatusPage); this.currentTab = { tab: resourceHeaderTab, index: currentIndex }; this.currentTab.tab.text.updateCssStyles(cssStyles.selectedResourceHeaderTab); + this.currentTab.tab.div.ariaSelected = true; this.resourceHeader.addItem(resourceHeaderTab.div, { flex: '0 0 auto', CSSStyles: cssStyles.selectedTabDiv }); } else { @@ -127,7 +132,16 @@ export class BdcServiceStatusPage extends BdcDashboardPage { * @param resourceStatus The status of the resource we're creating */ function createResourceHeaderTab(modelBuilder: azdata.ModelBuilder, resourceStatus: ResourceStatusModel): ServiceTab { - const resourceHeaderTab = modelBuilder.divContainer().withLayout({ width: '100px', height: '25px' }).withProperties({ clickable: true }).component(); + const resourceHeaderTab = modelBuilder + .divContainer() + .withLayout({ + width: '100px', + height: '25px' + }) + .withProperties({ + clickable: true, + ariaRole: 'tab' + }).component(); const innerContainer = modelBuilder.flexContainer().withLayout({ width: '100px', height: '25px', flexFlow: 'row' }).component(); const statusDot = modelBuilder.text().withProperties({ value: getHealthStatusDot(resourceStatus.healthStatus), CSSStyles: { 'color': 'red', 'font-size': '40px', 'width': '20px', 'text-align': 'right', ...cssStyles.nonSelectableText } }).component(); innerContainer.addItem(statusDot, { flex: '0 0 auto' }); diff --git a/src/sql/azdata.d.ts b/src/sql/azdata.d.ts index 29a31a783d..f2d9d7246b 100644 --- a/src/sql/azdata.d.ts +++ b/src/sql/azdata.d.ts @@ -3019,6 +3019,14 @@ declare module 'azdata' { * Corresponds to the aria-label accessibility attribute for this component */ ariaLabel?: string; + /** + * Corresponds to the role accessibility attribute for this component + */ + ariaRole?: string; + /** + * Corresponds to the aria-selected accessibility attribute for this component + */ + ariaSelected?: boolean; /** * Matches the CSS style key and its available values. */ @@ -3092,7 +3100,6 @@ declare module 'azdata' { title?: string; ariaRowCount?: number; ariaColumnCount?: number; - ariaRole?: string; updateCells?: TableCell[]; moveFocusOutWithTab?: boolean; //accessibility requirement for tables with no actionable cells } diff --git a/src/sql/workbench/api/common/extHostModelView.ts b/src/sql/workbench/api/common/extHostModelView.ts index f9ac68987a..d75ce7d7c6 100644 --- a/src/sql/workbench/api/common/extHostModelView.ts +++ b/src/sql/workbench/api/common/extHostModelView.ts @@ -566,6 +566,22 @@ class ComponentWrapper implements azdata.Component { this.setProperty('ariaLabel', v); } + public get ariaRole(): string { + return this.properties['ariaRole']; + } + + public set ariaRole(v: string) { + this.setProperty('ariaRole', v); + } + + public get ariaSelected(): boolean { + return this.properties['ariaSelected']; + } + + public set ariaSelected(v: boolean) { + this.setProperty('ariaSelected', v); + } + public get CSSStyles(): { [key: string]: string } { return this.properties['CSSStyles']; } diff --git a/src/sql/workbench/browser/modelComponents/componentBase.ts b/src/sql/workbench/browser/modelComponents/componentBase.ts index ede1ef5809..485708d104 100644 --- a/src/sql/workbench/browser/modelComponents/componentBase.ts +++ b/src/sql/workbench/browser/modelComponents/componentBase.ts @@ -184,6 +184,22 @@ export abstract class ComponentBase extends Disposable implements IComponent, On this.setPropertyFromUI((props, value) => props.ariaLabel = value, newValue); } + public get ariaRole(): string { + return this.getPropertyOrDefault((props) => props.ariaRole, ''); + } + + public set ariaRole(newValue: string) { + this.setPropertyFromUI((props, value) => props.ariaRole = value, newValue); + } + + public get ariaSelected(): boolean { + return this.getPropertyOrDefault((props) => props.ariaSelected, false); + } + + public set ariaSelected(newValue: boolean) { + this.setPropertyFromUI((props, value) => props.ariaSelected = value, newValue); + } + public get CSSStyles(): { [key: string]: string } { return this.getPropertyOrDefault((props) => props.CSSStyles, {}); } diff --git a/src/sql/workbench/browser/modelComponents/divContainer.component.ts b/src/sql/workbench/browser/modelComponents/divContainer.component.ts index 7ac08cd57c..84f8b643cc 100644 --- a/src/sql/workbench/browser/modelComponents/divContainer.component.ts +++ b/src/sql/workbench/browser/modelComponents/divContainer.component.ts @@ -23,7 +23,7 @@ class DivItem { @Component({ template: ` -
+
diff --git a/src/sql/workbench/browser/modelComponents/flexContainer.component.ts b/src/sql/workbench/browser/modelComponents/flexContainer.component.ts index aa08fb8ce5..8add3ef9f9 100644 --- a/src/sql/workbench/browser/modelComponents/flexContainer.component.ts +++ b/src/sql/workbench/browser/modelComponents/flexContainer.component.ts @@ -21,7 +21,7 @@ export class FlexItem { @Component({ template: `
+ [style.alignItems]="alignItems" [style.alignContent]="alignContent" [style.height]="height" [style.width]="width" [style.flex-wrap]="flexWrap" [attr.role]="ariaRole">
diff --git a/src/sql/workbench/browser/modelComponents/table.component.ts b/src/sql/workbench/browser/modelComponents/table.component.ts index 87586c9afd..a9c3c2f78b 100644 --- a/src/sql/workbench/browser/modelComponents/table.component.ts +++ b/src/sql/workbench/browser/modelComponents/table.component.ts @@ -352,10 +352,6 @@ export default class TableComponent extends ComponentBase implements IComponent, return this.getPropertyOrDefault((props) => props.ariaColumnCount, -1); } - public get ariaRole(): string { - return this.getPropertyOrDefault((props) => props.ariaRole, undefined); - } - public set moveFocusOutWithTab(newValue: boolean) { this.setPropertyFromUI((props, value) => props.moveFocusOutWithTab = value, newValue); } diff --git a/src/sql/workbench/browser/modelComponents/text.component.ts b/src/sql/workbench/browser/modelComponents/text.component.ts index 7e0ec10492..8ac8c17c45 100644 --- a/src/sql/workbench/browser/modelComponents/text.component.ts +++ b/src/sql/workbench/browser/modelComponents/text.component.ts @@ -26,7 +26,7 @@ import { TitledComponent } from 'sql/workbench/browser/modelComponents/titledCom
-

+

` }) export default class TextComponent extends TitledComponent implements IComponent, OnDestroy, AfterViewInit {