Skip to content
Merged
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# v10.0.0-rc.2 (2020-10-28)
* **grid** differentiate between user sort and programmatic sort events
* **grid** a11y: announce only user `sort` events
* **grid** a11y: expose translateable aria-label for checkboxes
* **grid** added `matTooltip` for checkboxes
* **suggest** a11y: fixes to title, specify `role` attributes for list
* **suggest** a11y: announce current `option` on open
* **suggest** announce "no results" msg if empty

# v10.0.0-rc.1 (2020-10-15)
* **grid** fix multiple row selection with shift

Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "angular-components",
"version": "10.0.0-rc.1",
"version": "10.0.0-rc.2",
"author": {
"name": "UiPath Inc",
"url": "https://uipath.com"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
<mat-icon *ngIf="!search.value"
[matTooltip]="searchTooltip"
[matTooltipDisabled]="tooltipDisabled"
svgIcon="uipath:search"
matSuffix
class="svg-icon">
<svg width="100%"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,19 @@ describe('Component: UiGrid', () => {
expect(manager.columns).toBe(columns);
});

describe('Event: sort change', () => {
it('should update sort event if columns change', () => {
const cols = generateColumnList('random');
cols.forEach(column => {
column.sortable = true;
column.sort = 'asc';
});

manager.columns = cols;
expect(Object.keys(manager.sort$.getValue()).length).toBe(4);
expect(manager.sort$.getValue().userEvent).toBeFalse();
});

describe('Event: user sort change', () => {
it(`should cycle column sort from '' to 'asc'`, () => {
const column = faker.helpers.randomize(columns);
column.sort = '';
Expand Down Expand Up @@ -110,6 +122,7 @@ describe('Component: UiGrid', () => {
).subscribe(sort => {
expect(sort.field).toEqual(column.property!);
expect(sort.direction).toEqual(column.sort);
expect(sort.userEvent).toBeTrue();
});

manager.changeSort(column);
Expand All @@ -129,6 +142,7 @@ describe('Component: UiGrid', () => {
).subscribe(sort => {
expect(sort.field).toEqual(first.property!);
expect(sort.direction).toEqual(first.sort);
expect(sort.userEvent).toBeTrue();
});

manager.sort$
Expand All @@ -139,6 +153,7 @@ describe('Component: UiGrid', () => {
).subscribe(sort => {
expect(sort.field).toEqual(second.property!);
expect(sort.direction).toEqual(second.sort);
expect(sort.userEvent).toBeTrue();
});

manager.changeSort(first);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ export class SortManager<T> {

this._emitSort(sortedColumn);
}

/**
* Sort based on user action on column header
*/
public changeSort(column: UiGridColumnDirective<T>) {
if (!column.sortable) { return; }

Expand All @@ -58,18 +60,19 @@ export class SortManager<T> {

column.sort = SORT_CYCLE_MAP[column.sort];

this._emitSort(column);
this._emitSort(column, true);
}

public destroy() {
this.sort$.complete();
}

private _emitSort(column: UiGridColumnDirective<T>) {
private _emitSort(column: UiGridColumnDirective<T>, userEvent = false) {
const updatedSort = {
direction: column.sort,
field: column.property,
title: column.title,
userEvent,
} as ISortModel<T>;

if (isEqual(this.sort$.getValue(), updatedSort)) { return; }
Expand Down
5 changes: 5 additions & 0 deletions projects/angular/components/ui-grid/src/models/sortModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,9 @@ export interface ISortModel<T> {
*
*/
title: string;
/**
* Sort event as a result of direct user intent to sort by a column.
*
*/
userEvent?: boolean;
}
10 changes: 6 additions & 4 deletions projects/angular/components/ui-grid/src/ui-grid.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@
[disabled]="!dataManager.length"
[checked]="isEveryVisibleRowChecked"
[indeterminate]="hasValueOnVisiblePage && !isEveryVisibleRowChecked"
[aria-label]="checkboxLabel()"
[matTooltip]="checkboxTooltip()"
[aria-label]="checkboxTooltip()"
tabindex="0">
</mat-checkbox>
</div>
Expand Down Expand Up @@ -172,7 +173,6 @@
</div>

<div *ngIf="column.resizeable && !last"
[class.is-resizing]="column.isResizing"
(mousedown)="resizeManager.startResize($event, column)"
class="ui-grid-resize-anchor">
<mat-icon>
Expand Down Expand Up @@ -291,7 +291,8 @@
(keyup.space)="checkShift($event)"
(change)="handleSelection(index, row)"
[checked]="selectionManager.isSelected(row)"
[aria-label]="checkboxLabel(row)"
[matTooltip]="checkboxTooltip(row)"
[aria-label]="checkboxTooltip(row)"
tabindex="0">
</mat-checkbox>
</div>
Expand All @@ -314,7 +315,8 @@
(keyup.space)="checkShift($event)"
(change)="handleSelection(index, row)"
[checked]="selectionManager.isSelected(row)"
[aria-label]="checkboxLabel(row)"
[matTooltip]="checkboxTooltip(row)"
[aria-label]="checkboxTooltip(row)"
tabindex="0">
</mat-checkbox>
</div>
Expand Down
32 changes: 28 additions & 4 deletions projects/angular/components/ui-grid/src/ui-grid.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,16 +345,34 @@ describe('Component: UiGrid', () => {
});

describe('Feature: checkbox', () => {
it('should check all rows if the header checkbox is clicked', () => {
it('should have ariaLabel set correctly for toggle selection', () => {
const checkboxHeader = fixture.debugElement.query(By.css('.ui-grid-header-cell.ui-grid-checkbox-cell'));
const matCheckbox = checkboxHeader.query(By.css('mat-checkbox')).componentInstance as MatCheckbox;

expect(matCheckbox.checked).toEqual(false);
expect(matCheckbox.ariaLabel).toEqual('Select all');

const checkboxInput = checkboxHeader.query(By.css('input'));
checkboxInput.nativeElement.dispatchEvent(EventGenerator.click);

fixture.detectChanges();


expect(matCheckbox.checked).toEqual(true);
expect(matCheckbox.ariaLabel).toEqual('Deselect all');
});

it('should check all rows if the header checkbox is clicked', () => {
const checkboxHeader = fixture.debugElement.query(By.css('.ui-grid-header-cell.ui-grid-checkbox-cell'));
const matCheckbox = checkboxHeader.query(By.css('mat-checkbox')).componentInstance as MatCheckbox;

expect(matCheckbox.checked).toEqual(false);

const checkboxInput = checkboxHeader.query(By.css('input'));
checkboxInput.nativeElement.dispatchEvent(EventGenerator.click);

fixture.detectChanges();

expect(matCheckbox.checked).toEqual(true);

const rowCheckboxList = fixture.debugElement
Expand Down Expand Up @@ -388,7 +406,10 @@ describe('Component: UiGrid', () => {

expect(rowCheckboxList.length).toEqual(data.length);

rowCheckboxList.forEach(checkbox => expect(checkbox.checked).toEqual(true));
rowCheckboxList.forEach((checkbox, i) => {
expect(checkbox.checked).toEqual(true);
expect(checkbox.ariaLabel).toEqual(`Deselect row ${i}`);
});

expect(grid.selectionManager.selected.length).toEqual(data.length);
expect(grid.selectionManager.selected).toEqual(data);
Expand All @@ -398,7 +419,10 @@ describe('Component: UiGrid', () => {
fixture.detectChanges();

expect(matCheckbox.checked).toEqual(false);
rowCheckboxList.forEach(checkbox => expect(checkbox.checked).toEqual(false));
rowCheckboxList.forEach((checkbox, i) => {
expect(checkbox.checked).toEqual(false);
expect(checkbox.ariaLabel).toEqual(`Select row ${i}`);
});
expect(grid.hasValueOnVisiblePage).toEqual(false);
});

Expand Down Expand Up @@ -478,7 +502,7 @@ describe('Component: UiGrid', () => {

rowCheckboxInputList[10].nativeElement.dispatchEvent(EventGenerator.click);

for (let i = 0; i <= 10; i ++) {
for (let i = 0; i <= 10; i++) {
expect(grid.selectionManager.isSelected(data[i])).toBeTrue();
}
});
Expand Down
23 changes: 19 additions & 4 deletions projects/angular/components/ui-grid/src/ui-grid.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ import {
import {
debounceTime,
distinctUntilChanged,
filter,
map,
observeOn,
skip,
switchMap,
take,
takeUntil,
Expand Down Expand Up @@ -114,7 +114,7 @@ export class UiGridComponent<T extends IGridDataEntry> extends ResizableGrid<T>
*
*/
public get isEveryVisibleRowChecked() {
return this.dataManager.length &&
return !!this.dataManager.length &&
this.dataManager.every(row => this.selectionManager.isSelected(row!));
}

Expand Down Expand Up @@ -499,7 +499,9 @@ export class UiGridComponent<T extends IGridDataEntry> extends ResizableGrid<T>
msg => this._queuedAnnouncer.enqueue(msg),
this.intl,
this.dataManager.data$,
this.sortManager.sort$.pipe(skip(1)),
this.sortManager.sort$.pipe(
filter(({ userEvent }) => !!userEvent),
),
this.refresh,
this.footer && this.footer.pageChange,
);
Expand Down Expand Up @@ -638,10 +640,23 @@ export class UiGridComponent<T extends IGridDataEntry> extends ResizableGrid<T>
}

/**
* Determines the `checkbox` `aria-label`.
* Determines the `checkbox` `matToolTip`.
*
* @param [row] The row for which the label is computed.
*/
public checkboxTooltip(row?: T): string {
if (!row) {
return this.intl.checkboxTooltip(this.isEveryVisibleRowChecked);
}

return this.intl.checkboxTooltip(this.selectionManager.isSelected(row), this.dataManager.indexOf(row));
}

/**
* Determines the `checkbox` aria-label`.
* **DEPRECATED**
* @param [row] The row for which the label is computed.
*/
public checkboxLabel(row?: T): string {
if (!row) {
return `${this.isEveryVisibleRowChecked ? 'select' : 'deselect'} all`;
Expand Down
12 changes: 12 additions & 0 deletions projects/angular/components/ui-grid/src/ui-grid.intl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,18 @@ export class UiGridIntl implements OnDestroy {
*/
public descending = 'descending';

/**
* Determines the `checkbox` `matToolTip`.
*
* @param [rowIndex] The rowIndex for which the label is computed.
*/
public checkboxTooltip(selected: boolean, rowIndex?: number): string {
if (rowIndex == null) {
return `${selected ? 'Deselect' : 'Select'} all`;
}
return `${selected ? 'Deselect' : 'Select'} row ${rowIndex}`;
}


/**
* Generates a selection label for the given count.
Expand Down
1 change: 1 addition & 0 deletions projects/angular/components/ui-suggest/src/test/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './suggestionItem';
export * from './ui-suggest-assert';
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';

import { UiSuggestComponent } from '../ui-suggest.component';

export class UiSuggestAssert {
constructor(
private _root: DebugElement,
private _suggest: UiSuggestComponent,
) { }

public isOpen(): void {
this._assertOpenState('open');
}

public isClosed(): void {
this._assertOpenState('closed');
}

public isDisabled(): void {
this._assertDisableState('disabled');
}

public isEnabled(): void {
this._assertDisableState('enabled');
}

private _assertOpenState(expected: 'open' | 'closed'): void {
const expectedIsOpen = expected === 'open';

expect(this._suggest.isOpen).toBe(expectedIsOpen);

const combo = this._root.query(By.css('[role=combobox]')).nativeElement;
expect(combo).toHaveAttr('aria-expanded', expectedIsOpen.toString());

const itemList = this._root.query(By.css('.item-list-container'));
const itemListClasses = itemList.nativeElement.classList;
expect(itemListClasses.contains('item-list-container-state-open')).toBe(expectedIsOpen);
expect(itemListClasses.contains('item-list-container-state-closed')).toBe(!expectedIsOpen);
}

private _assertDisableState(expected: 'enabled' | 'disabled'): void {
const expectedIsDisabled = expected === 'disabled';

expect(this._suggest.disabled).toBe(expectedIsDisabled);

const combo = this._root.query(By.css('[role=combobox]')).nativeElement;
expect(combo).toHaveAttr('aria-disabled', expectedIsDisabled.toString());
}
}
Loading