mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Bug/accessibility 5 (#7008)
* fixing 6946 and 6796(second part) * fix for https://github.com/microsoft/azuredatastudio/issues/6726 * comments cleanup * taking PR comments * adding strong border for HC focus * convert to string template
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { escape } from 'sql/base/common/strings';
|
import { escape } from 'sql/base/common/strings';
|
||||||
|
import { localize } from 'vs/nls';
|
||||||
|
|
||||||
export class DBCellValue {
|
export class DBCellValue {
|
||||||
displayValue: string;
|
displayValue: string;
|
||||||
@@ -77,6 +78,19 @@ export function slickGridDataItemColumnValueExtractor(value: any, columnDef: any
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alternate function to provide slick grid cell with ariaLabel and plain text
|
||||||
|
* In this case, for no display value ariaLabel will be set to specific string "no data available" for accessibily support for screen readers
|
||||||
|
* Set 'no data' lable only if cell is present and has no value (so that checkbox and other custom plugins do not get 'no data' label)
|
||||||
|
*/
|
||||||
|
export function slickGridDataItemColumnValueWithNoData(value: any, columnDef: any): { text: string; ariaLabel: string; } {
|
||||||
|
let displayValue = value[columnDef.field];
|
||||||
|
return {
|
||||||
|
text: displayValue,
|
||||||
|
ariaLabel: displayValue ? escape(displayValue) : ((displayValue !== undefined) ? localize("tableCell.NoDataAvailable", "no data available") : displayValue)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/** The following code is a rewrite over the both formatter function using dom builder
|
/** The following code is a rewrite over the both formatter function using dom builder
|
||||||
* rather than string manipulation, which is a safer and easier method of achieving the same goal.
|
* rather than string manipulation, which is a safer and easier method of achieving the same goal.
|
||||||
* However, when electron is in "Run as node" mode, dom creation acts differently than normal and therefore
|
* However, when electron is in "Run as node" mode, dom creation acts differently than normal and therefore
|
||||||
|
|||||||
@@ -99,11 +99,11 @@ export class CheckboxSelectColumn<T extends Slick.SlickData> implements Slick.Pl
|
|||||||
if (!this._options.title) {
|
if (!this._options.title) {
|
||||||
if (selectedRows.length && selectedRows.length === this._grid.getDataLength()) {
|
if (selectedRows.length && selectedRows.length === this._grid.getDataLength()) {
|
||||||
this._grid.updateColumnHeader(this._options.columnId!,
|
this._grid.updateColumnHeader(this._options.columnId!,
|
||||||
strings.format(checkboxTemplate, 'checked', this._options.title),
|
strings.format(checkboxTemplate, 'checked', this.getAriaLabel(true)),
|
||||||
this._options.toolTip);
|
this._options.toolTip);
|
||||||
} else {
|
} else {
|
||||||
this._grid.updateColumnHeader(this._options.columnId!,
|
this._grid.updateColumnHeader(this._options.columnId!,
|
||||||
strings.format(checkboxTemplate, '',this._options.title),
|
strings.format(checkboxTemplate, '', this.getAriaLabel(false)),
|
||||||
this._options.toolTip);
|
this._options.toolTip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -210,12 +210,12 @@ export class CheckboxSelectColumn<T extends Slick.SlickData> implements Slick.Pl
|
|||||||
const rows = range(this._grid.getDataLength());
|
const rows = range(this._grid.getDataLength());
|
||||||
this._grid.setSelectedRows(rows);
|
this._grid.setSelectedRows(rows);
|
||||||
this._grid.updateColumnHeader(this._options.columnId!,
|
this._grid.updateColumnHeader(this._options.columnId!,
|
||||||
strings.format(checkboxTemplate, 'checked', this._options.title),
|
strings.format(checkboxTemplate, 'checked', this.getAriaLabel(true)),
|
||||||
this._options.toolTip);
|
this._options.toolTip);
|
||||||
} else {
|
} else {
|
||||||
this._grid.setSelectedRows([]);
|
this._grid.setSelectedRows([]);
|
||||||
this._grid.updateColumnHeader(this._options.columnId!,
|
this._grid.updateColumnHeader(this._options.columnId!,
|
||||||
strings.format(checkboxTemplate, '', this._options.title), this._options.toolTip);
|
strings.format(checkboxTemplate, '', this.getAriaLabel(false)), this._options.toolTip);
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.stopImmediatePropagation();
|
e.stopImmediatePropagation();
|
||||||
}
|
}
|
||||||
@@ -243,16 +243,16 @@ export class CheckboxSelectColumn<T extends Slick.SlickData> implements Slick.Pl
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this._selectedRowsLookup[row]
|
return this._selectedRowsLookup[row]
|
||||||
? strings.format(checkboxTemplate, 'checked', this._options.title)
|
? strings.format(checkboxTemplate, 'checked', this.getAriaLabel(true))
|
||||||
: strings.format(checkboxTemplate, '', this._options.title);
|
: strings.format(checkboxTemplate, '', this.getAriaLabel(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
checkboxTemplateCustom(row: number): string {
|
checkboxTemplateCustom(row: number): string {
|
||||||
// use state after toggles
|
// use state after toggles
|
||||||
if (this._useState) {
|
if (this._useState) {
|
||||||
return this._selectedCheckBoxLookup[row]
|
return this._selectedCheckBoxLookup[row]
|
||||||
? strings.format(checkboxTemplate, 'checked', this._options.title)
|
? strings.format(checkboxTemplate, 'checked', this.getAriaLabel(true))
|
||||||
: strings.format(checkboxTemplate, '', this._options.title);
|
: strings.format(checkboxTemplate, '', this.getAriaLabel(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
// use data for first time rendering
|
// use data for first time rendering
|
||||||
@@ -260,15 +260,20 @@ export class CheckboxSelectColumn<T extends Slick.SlickData> implements Slick.Pl
|
|||||||
let rowVal = (this._grid) ? this._grid.getDataItem(row) : null;
|
let rowVal = (this._grid) ? this._grid.getDataItem(row) : null;
|
||||||
if (rowVal && this._options.title && rowVal[this._options.title] === true) {
|
if (rowVal && this._options.title && rowVal[this._options.title] === true) {
|
||||||
this._selectedCheckBoxLookup[row] = true;
|
this._selectedCheckBoxLookup[row] = true;
|
||||||
return strings.format(checkboxTemplate, 'checked', this._options.title);
|
return strings.format(checkboxTemplate, 'checked', this.getAriaLabel(true));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
delete this._selectedCheckBoxLookup[row];
|
delete this._selectedCheckBoxLookup[row];
|
||||||
return strings.format(checkboxTemplate, '', this._options.title);
|
return strings.format(checkboxTemplate, '', this.getAriaLabel(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private isCustomActionRequested(): boolean {
|
private isCustomActionRequested(): boolean {
|
||||||
return (this._options.actionOnCheck === ActionOnCheck.customAction);
|
return (this._options.actionOnCheck === ActionOnCheck.customAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getAriaLabel(checked: boolean): string {
|
||||||
|
return checked ? `"${this._options.title} ${nls.localize("tableCheckboxCell.Checked", "checkbox checked")}"` :
|
||||||
|
`"${this._options.title} ${nls.localize("tableCheckboxCell.unChecked", "checkbox unchecked")}"`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -322,6 +322,7 @@ export class Table<T extends Slick.SlickData> extends Widget implements IDisposa
|
|||||||
|
|
||||||
if (styles.listFocusOutline) {
|
if (styles.listFocusOutline) {
|
||||||
content.push(`.monaco-table.${this.idPrefix}.focused .slick-row .selected { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; }`);
|
content.push(`.monaco-table.${this.idPrefix}.focused .slick-row .selected { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; }`);
|
||||||
|
content.push(`.monaco-table.${this.idPrefix}.focused .slick-row .selected.active { outline: 2px solid ${styles.listFocusOutline}; outline-offset: -1px; }`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (styles.listInactiveFocusOutline) {
|
if (styles.listInactiveFocusOutline) {
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export class DialogPane extends Disposable implements IThemable {
|
|||||||
tabContainer.style.display = 'block';
|
tabContainer.style.display = 'block';
|
||||||
},
|
},
|
||||||
layout: (dimension) => { this.getTabDimension(); },
|
layout: (dimension) => { this.getTabDimension(); },
|
||||||
focus: () => { }
|
focus: () => { this.focus(); }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -319,13 +319,27 @@ export abstract class Modal extends Disposable implements IThemable {
|
|||||||
* Set focusable elements in the modal dialog
|
* Set focusable elements in the modal dialog
|
||||||
*/
|
*/
|
||||||
public setFocusableElements() {
|
public setFocusableElements() {
|
||||||
this._focusableElements = this._bodyContainer.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]');
|
// try to find focusable element in dialog pane rather than overall container
|
||||||
|
this._focusableElements = this._modalBodySection ?
|
||||||
|
this._modalBodySection.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]') :
|
||||||
|
this._bodyContainer.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]');
|
||||||
if (this._focusableElements && this._focusableElements.length > 0) {
|
if (this._focusableElements && this._focusableElements.length > 0) {
|
||||||
this._firstFocusableElement = <HTMLElement>this._focusableElements[0];
|
this._firstFocusableElement = <HTMLElement>this._focusableElements[0];
|
||||||
this._lastFocusableElement = <HTMLElement>this._focusableElements[this._focusableElements.length - 1];
|
this._lastFocusableElement = <HTMLElement>this._focusableElements[this._focusableElements.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
this._focusedElementBeforeOpen = <HTMLElement>document.activeElement;
|
this._focusedElementBeforeOpen = <HTMLElement>document.activeElement;
|
||||||
|
this.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Focuses the modal
|
||||||
|
* Default behavior: focus the first focusable element
|
||||||
|
*/
|
||||||
|
protected focus() {
|
||||||
|
if (this._firstFocusableElement) {
|
||||||
|
this._firstFocusableElement.focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import { CheckboxSelectColumn, ICheckboxCellActionEventArgs } from 'sql/base/bro
|
|||||||
import { Emitter, Event as vsEvent } from 'vs/base/common/event';
|
import { Emitter, Event as vsEvent } from 'vs/base/common/event';
|
||||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||||
|
import { slickGridDataItemColumnValueWithNoData, textFormatter } from 'sql/base/browser/ui/table/formatters';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'modelview-table',
|
selector: 'modelview-table',
|
||||||
@@ -72,13 +73,15 @@ export default class TableComponent extends ComponentBase implements IComponent,
|
|||||||
width: col.width,
|
width: col.width,
|
||||||
cssClass: col.cssClass,
|
cssClass: col.cssClass,
|
||||||
headerCssClass: col.headerCssClass,
|
headerCssClass: col.headerCssClass,
|
||||||
toolTip: col.toolTip
|
toolTip: col.toolTip,
|
||||||
|
formatter: textFormatter,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
mycolumns.push(<Slick.Column<any>>{
|
mycolumns.push(<Slick.Column<any>>{
|
||||||
name: <string>col,
|
name: <string>col,
|
||||||
id: <string>col,
|
id: <string>col,
|
||||||
field: <string>col
|
field: <string>col,
|
||||||
|
formatter: textFormatter
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
index++;
|
index++;
|
||||||
@@ -95,8 +98,6 @@ export default class TableComponent extends ComponentBase implements IComponent,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static transformData(rows: string[][], columns: any[]): { [key: string]: string }[] {
|
public static transformData(rows: string[][], columns: any[]): { [key: string]: string }[] {
|
||||||
if (rows && columns) {
|
if (rows && columns) {
|
||||||
return rows.map(row => {
|
return rows.map(row => {
|
||||||
@@ -122,7 +123,8 @@ export default class TableComponent extends ComponentBase implements IComponent,
|
|||||||
syncColumnCellResize: true,
|
syncColumnCellResize: true,
|
||||||
enableColumnReorder: false,
|
enableColumnReorder: false,
|
||||||
enableCellNavigation: true,
|
enableCellNavigation: true,
|
||||||
forceFitColumns: true // default to true during init, actual value will be updated when setProperties() is called
|
forceFitColumns: true, // default to true during init, actual value will be updated when setProperties() is called
|
||||||
|
dataItemColumnValueExtractor: slickGridDataItemColumnValueWithNoData // must change formatter if you are changing explicit column value extractor
|
||||||
};
|
};
|
||||||
|
|
||||||
this._table = new Table<Slick.SlickData>(this._inputContainer.nativeElement, { dataProvider: this._tableData, columns: this._tableColumns }, options);
|
this._table = new Table<Slick.SlickData>(this._inputContainer.nativeElement, { dataProvider: this._tableData, columns: this._tableColumns }, options);
|
||||||
|
|||||||
Reference in New Issue
Block a user