Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# v14.8.1 (2023-05-29)
* **grid** add tooltips & disable state for radio selection
* **grid** add tests for radio selection
* **grid** add singleSelectable input
* **grid** add radio btn select

# v14.8.0 (2023-05-29)
* **suggest** changed in EventEmitter
* **suggest** added item selected output
Expand Down
4 changes: 2 additions & 2 deletions 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": "14.8.0",
"version": "14.8.1",
"author": {
"name": "UiPath Inc",
"url": "https://uipath.com"
Expand Down
109 changes: 63 additions & 46 deletions projects/angular/components/ui-grid/src/ui-grid.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@
<div *ngIf="showHeaderRow"
class="ui-grid-header-row"
role="row">
<div *ngIf="selectable"
<div *ngIf="selectable && !singleSelectable"
class="ui-grid-header-cell ui-grid-checkbox-cell ui-grid-feature-cell"
role="columnheader">
<mat-checkbox #selectAvailableRowsCheckbox
Expand Down Expand Up @@ -165,7 +165,8 @@
[matTooltipDisabled]="resizeManager.isResizing"
[attr.aria-label]="column.title + (column.description ? ('. ' + column.description) : '') + (column.sortable && intl.sortableMessage ? '. ' + intl.sortableMessage : '')"
matTooltipClass="preserve-whitespace">
{{ column.title }}</p>
{{ column.title }}
</p>

<mat-icon *ngIf="column.description"
[matTooltip]="column.description"
Expand Down Expand Up @@ -258,7 +259,7 @@
let last = last;
let index = index;
trackBy: dataManager.hashTrack">
<ng-container *ngTemplateOutlet="rowTemplate; context: {
<ng-container *ngTemplateOutlet="rowTemplate; context: {
data: row,
index: index,
expanded: expandedEntries,
Expand Down Expand Up @@ -290,7 +291,7 @@
<ng-container *ngFor="let row of dataManager.data$ | async;
let index = index;
let last = last;">
<ng-container *ngTemplateOutlet="rowTemplate; context: {
<ng-container *ngTemplateOutlet="rowTemplate; context: {
data: row,
index: index,
expanded: expandedEntries,
Expand Down Expand Up @@ -332,21 +333,30 @@
<div *ngIf="isProjected"
class="ui-grid-cell ui-grid-feature-cell ui-grid-mobile-feature-cell"
role="gridcell">
<div *ngIf="selectable"
class="ui-grid-mobile-feature-container ui-grid-mobile-refresh-container">
<mat-checkbox *ngLet="disableSelectionByEntry(row) as disabledReason"
(click)="checkShift($event)"
(keyup.shift.space)="checkShift($event)"
(keyup.space)="checkShift($event)"
(change)="handleSelection(index, row)"
[checked]="selectionManager.isSelected(row)"
[indeterminate]="selectionManager.isIndeterminate(row)"
[matTooltip]="disabledReason || checkboxTooltip(row)"
[aria-label]="disabledReason || checkboxTooltip(row)"
[disabled]="!!disabledReason"
tabindex="0">
</mat-checkbox>
</div>
<ng-container *ngLet="disableSelectionByEntry(row) as disabledReason">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can I have both on?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, solved

<div *ngIf="selectable && !singleSelectable"
class="ui-grid-mobile-feature-container ui-grid-mobile-refresh-container">
<mat-checkbox (click)="checkShift($event)"
(keyup.shift.space)="checkShift($event)"
(keyup.space)="checkShift($event)"
(change)="handleSelection(index, row)"
[checked]="selectionManager.isSelected(row)"
[indeterminate]="selectionManager.isIndeterminate(row)"
[matTooltip]="disabledReason || checkboxTooltip(row)"
[aria-label]="disabledReason || checkboxTooltip(row)"
[disabled]="!!disabledReason"
tabindex="0">
</mat-checkbox>
</div>
<div *ngIf="singleSelectable"
class="ui-grid-mobile-feature-container ui-grid-mobile-refresh-container">
<mat-radio-button [checked]="selectionManager.isSelected(row)"
[disabled]="disabledReason"
[matTooltip]="disabledReason || checkboxTooltip(row)"
[aria-label]="disabledReason || checkboxTooltip(row)"
(change)="rowSelected(row)"></mat-radio-button>
</div>
</ng-container>
<div *ngIf="!!actions"
role="gridcell"
class="ui-grid-mobile-feature-container ui-grid-mobile-action-container">
Expand All @@ -358,22 +368,30 @@
</div>
</div>

<div *ngIf="!isProjected &&
selectable"
<div *ngIf="!isProjected && (selectable || singleSelectable)"
class="ui-grid-cell ui-grid-checkbox-cell ui-grid-feature-cell"
role="gridcell">
<mat-checkbox *ngLet="disableSelectionByEntry(row) as disabledReason"
(click)="checkShift($event)"
(keyup.shift.space)="checkShift($event)"
(keyup.space)="checkShift($event)"
(change)="handleSelection(index, row)"
[checked]="selectionManager.isSelected(row)"
[indeterminate]="selectionManager.isIndeterminate(row)"
[matTooltip]="disabledReason || checkboxTooltip(row)"
[aria-label]="disabledReason || checkboxTooltip(row)"
[disabled]="disabledReason"
tabindex="0">
</mat-checkbox>
<ng-container *ngLet="disableSelectionByEntry(row) as disabledReason">
<mat-radio-button *ngIf="singleSelectable; else multiSelectable"
[checked]="selectionManager.isSelected(row)"
[disabled]="disabledReason"
[matTooltip]="disabledReason || checkboxTooltip(row)"
[aria-label]="disabledReason || checkboxTooltip(row)"
(change)="rowSelected(row)"></mat-radio-button>
<ng-template #multiSelectable>
<mat-checkbox (click)="checkShift($event)"
(keyup.shift.space)="checkShift($event)"
(keyup.space)="checkShift($event)"
(change)="handleSelection(index, row)"
[checked]="selectionManager.isSelected(row)"
[indeterminate]="selectionManager.isIndeterminate(row)"
[matTooltip]="disabledReason || checkboxTooltip(row)"
[aria-label]="disabledReason || checkboxTooltip(row)"
[disabled]="disabledReason"
tabindex="0">
</mat-checkbox>
</ng-template>
</ng-container>
</div>

<ng-container *ngFor="let column of renderedColumns$ | async">
Expand Down Expand Up @@ -444,18 +462,17 @@
</ng-template>

<ng-template #rowCardTemplate
let-row="data"
let-expanded="expanded"
let-last="last"
let-index="index">
let-row="data"
let-expanded="expanded"
let-last="last"
let-index="index">
<div cdkMonitorSubtreeFocus
class="ui-grid-card-wrapper"
[ngClass]="rowConfig?.ngClassFn(row) ?? ''"
[tabIndex]="0"
[attr.data-row-index]="index"
(click)="onRowClick($event, row)"
(keyup.enter)="onRowClick($event, row)"
>
class="ui-grid-card-wrapper"
[ngClass]="rowConfig?.ngClassFn(row) ?? ''"
[tabIndex]="0"
[attr.data-row-index]="index"
(click)="onRowClick($event, row)"
(keyup.enter)="onRowClick($event, row)">
<ng-container *ngIf="cardTemplate?.html; else defaultCardTemplate">
<ng-container *ngTemplateOutlet="cardTemplate?.html || defaultCardTemplate;context: {
data: row,
Expand All @@ -468,8 +485,8 @@
</ng-template>

<ng-template #defaultCardTemplate
let-row="data"
let-index="index">
let-row="data"
let-index="index">
<div class="ui-grid-card-default">
<ng-container *ngFor="let column of renderedColumns$ | async">
<div [class.ui-grid-primary]="column.directive.primary"
Expand Down
89 changes: 86 additions & 3 deletions projects/angular/components/ui-grid/src/ui-grid.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ describe('Component: UiGrid', () => {
[disableSelectionByEntry]="disableSelectionByEntry"
[refreshable]="refreshable"
[selectable]="selectable"
[singleSelectable]="singleSelectable"
[showHeaderRow]="showHeaderRow"
[virtualScroll]="virtualScroll">
<ui-grid-column [property]="'myNumber'"
Expand Down Expand Up @@ -111,6 +112,7 @@ describe('Component: UiGrid', () => {
isColumnVisible = true;
isFilterVisible = true;
selectable?: boolean;
singleSelectable?: boolean;
refreshable?: boolean;
showHeaderRow = true;
virtualScroll = false;
Expand Down Expand Up @@ -483,6 +485,89 @@ describe('Component: UiGrid', () => {
});
});

describe('Feature: radio button selection', () => {
beforeEach(() => {
component.selectable = false;
component.singleSelectable = true;
fixture.detectChanges();
});

it('should have correct selection', () => {
const radioBtns = fixture.debugElement.queryAll(By.css('.mat-radio-label'));
expect(radioBtns.length).toEqual(50);

const firstRadioBtn = radioBtns[0];
firstRadioBtn.nativeElement.dispatchEvent(EventGenerator.click);
fixture.detectChanges();

expect(component.grid.selectionManager.selected[0].id).toEqual(component.data[0].id);
});

it('should have only one selection', () => {
const radioBtns = fixture.debugElement.queryAll(By.css('.mat-radio-label'));

const firstRadioBtn = radioBtns[0];
firstRadioBtn.nativeElement.dispatchEvent(EventGenerator.click);
fixture.detectChanges();

const secondRadioBtn = radioBtns[1];
secondRadioBtn.nativeElement.dispatchEvent(EventGenerator.click);
fixture.detectChanges();

expect(component.grid.selectionManager.selected[0].id).toEqual(component.data[1].id);
expect(component.grid.selectionManager.selected.length).toEqual(1);
});

it('should have preselected option displayed', () => {
const randomIdx = faker.random.number({ max: 49 });
grid.selectionManager.select(data[randomIdx]);
fixture.detectChanges();
const radioBtns = fixture.debugElement.queryAll(By.css('[role="gridcell"] mat-radio-button'));
const checkedRadioBtn = radioBtns[randomIdx];
expect(checkedRadioBtn.nativeElement.classList.contains('mat-radio-checked')).toBeTruthy();
});

it('should display Select / Deselect according to button state', () => {
const checkedBtnIdx = faker.random.number({ max: 25 });
const uncheckedBtnIdx = faker.random.number({
min: 26,
max: 49,
});
grid.selectionManager.select(data[checkedBtnIdx]);
fixture.detectChanges();
const radioBtns = fixture.debugElement.queryAll(By.css('[role="gridcell"] mat-radio-button'));
const checkedRadioBtn = radioBtns[checkedBtnIdx].nativeElement;
const uncheckedRadioBtn = radioBtns[uncheckedBtnIdx].nativeElement;
expect(checkedRadioBtn.getAttribute('ng-reflect-message')).toEqual(`Deselect row ${checkedBtnIdx}`);
expect(uncheckedRadioBtn.getAttribute('ng-reflect-message')).toEqual(`Select row ${uncheckedBtnIdx}`);
});

it('should disable radio btn according to disableSelectionByEntry', () => {
const selectableBtnIdx = faker.random.number({ max: 25 });
const disabledBtnIdx = faker.random.number({
min: 26,
max: 49,
});
const disableSelectionByEntry = (entry?: ITestEntity) => entry && entry.id === data[disabledBtnIdx].id
? 'unselectable'
: null;

component.disableSelectionByEntry = disableSelectionByEntry;
grid.selectionManager.disableSelectionByEntry = disableSelectionByEntry;
fixture.detectChanges();

const radioBtns = fixture.debugElement.queryAll(By.css('[role="gridcell"] mat-radio-button'));
const selectableRadioBtn = radioBtns[selectableBtnIdx].nativeElement;
const disabledRadioBtn = radioBtns[disabledBtnIdx].nativeElement;

expect(selectableRadioBtn.getAttribute('ng-reflect-message')).toEqual(`Select row ${selectableBtnIdx}`);
expect(selectableRadioBtn.classList.contains('mat-radio-disabled')).toBeFalsy();

expect(disabledRadioBtn.getAttribute('ng-reflect-message')).toEqual(`unselectable`);
expect(disabledRadioBtn.classList.contains('mat-radio-disabled')).toBeTruthy();
});
});

describe('Feature: checkbox', () => {
it('should have ariaLabel set correctly for toggle selection', () => {
const checkboxHeader = fixture.debugElement.query(By.css('.ui-grid-header-cell.ui-grid-checkbox-cell'));
Expand Down Expand Up @@ -795,7 +880,7 @@ describe('Component: UiGrid', () => {
expect(matCheckbox.checked).toEqual(false);
});

it('should unselect heade checkbox if all grid rows are unselected', () => {
it('should unselect header checkbox if all grid rows are unselected', () => {
const disableSelectionByEntry = (entry?: ITestEntity) => entry && entry.id % 2 === 1 ? 'unselectable' : null;

component.disableSelectionByEntry = disableSelectionByEntry;
Expand Down Expand Up @@ -4338,10 +4423,8 @@ describe('Component: UiGrid', () => {
});

it('should render provided card template', () => {

const cardContainer = fixture.debugElement.query(By.css('.expanded-row'));

console.log(cardContainer);
expect(cardContainer).toBeDefined();
expect(cardContainer.nativeElement.querySelector('[data-property="myNumber"]')).toBeDefined();
expect(cardContainer.nativeElement.querySelector('[data-property="myBool"]')).toBeDefined();
Expand Down
12 changes: 12 additions & 0 deletions projects/angular/components/ui-grid/src/ui-grid.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,13 @@ export class UiGridComponent<T extends IGridDataEntry> extends ResizableGrid<T>
@Input()
selectable = true;

/**
* Configure if the grid allows radio button selection for its items.
*
*/
@Input()
singleSelectable = false;

/**
* Option to select an alternate layout for footer pagination.
*
Expand Down Expand Up @@ -1068,6 +1075,11 @@ export class UiGridComponent<T extends IGridDataEntry> extends ResizableGrid<T>
activeItem?.focus();
}

rowSelected(row: T) {
this.selectionManager.clear();
this.selectionManager.select(row);
}

private _announceGridHeaderActions() {
this._queuedAnnouncer.enqueue(this.intl.gridHeaderActionsNotice);
}
Expand Down
4 changes: 3 additions & 1 deletion projects/angular/components/ui-grid/src/ui-grid.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select';
import { MatTooltipModule } from '@angular/material/tooltip';
import { UiAutoAccessibleLabelModule } from '@uipath/angular/a11y';
Expand All @@ -21,8 +22,8 @@ import { UiGridExpandedRowDirective } from './body/ui-grid-expanded-row.directiv
import { UiGridLoadingDirective } from './body/ui-grid-loading.directive';
import { UiGridNoContentDirective } from './body/ui-grid-no-content.directive';
import { UiGridRowActionDirective } from './body/ui-grid-row-action.directive';
import { UiGridRowConfigDirective } from './body/ui-grid-row-config.directive';
import { UiGridRowCardViewDirective } from './body/ui-grid-row-card-view.directive';
import { UiGridRowConfigDirective } from './body/ui-grid-row-config.directive';
import { UiGridCustomPaginatorModule } from './components/ui-grid-custom-paginator/ui-grid-custom-paginator.module';
import { UiGridSearchModule } from './components/ui-grid-search/ui-grid-search.module';
import { UiGridToggleColumnsModule } from './components/ui-grid-toggle-columns/ui-grid-toggle-columns.module';
Expand All @@ -44,6 +45,7 @@ import { UiGridComponent } from './ui-grid.component';
MatSelectModule,
MatTooltipModule,
MatProgressBarModule,
MatRadioModule,
ScrollingModule,
UiGridSearchModule,
UiGridToggleColumnsModule,
Expand Down
2 changes: 1 addition & 1 deletion projects/angular/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@uipath/angular",
"version": "14.8.0",
"version": "14.8.1",
"license": "MIT",
"author": {
"name": "UiPath Inc",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
[loading]="inputs.loading"
[disabled]="inputs.disabled"
[selectable]="inputs.selectable"
[singleSelectable]="inputs.singleSelectable"
[toggleColumns]="inputs.toggleColumns"
[multiPageSelect]="inputs.multiPageSelect"
[refreshable]="inputs.refreshable"
Expand Down
Loading