[ACS-8113] UX bug - Checkbox selections checked state should be Hyland blue not green- edit summary (#9861)

This commit is contained in:
jacekpluta 2024-06-30 23:11:29 +02:00 committed by GitHub
parent 3cc571ee32
commit a48611eb92
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 168 additions and 64 deletions

View File

@ -23,15 +23,16 @@ Indicates the current position within a navigation hierarchy.
### Properties ### Properties
| Name | Type | Default value | Description | | Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- | |-----------------------|-----------------------------------------------------------------------------------------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| folderNode | `Node` | null | Active node, builds UI based on folderNode.path.elements collection. | | folderNode | `Node` | null | Active node, builds UI based on folderNode.path.elements collection. |
| maxItems | `number` | | Maximum number of nodes to display before wrapping them with a dropdown element. | | maxItems | `number` | | Maximum number of nodes to display before wrapping them with a dropdown element. |
| readOnly | `boolean` | false | If true, prevents the user from navigating away from the active node. | | readOnly | `boolean` | false | If true, prevents the user from navigating away from the active node. |
| root | `string` | null | (optional) Name of the root element of the breadcrumb. You can use this property to rename "Company Home" to "Personal Files" for example. You can use an i18n resource key for the property value. | | root | `string` | null | (optional) Name of the root element of the breadcrumb. You can use this property to rename "Company Home" to "Personal Files" for example. You can use an i18n resource key for the property value. |
| rootId | `string` | null | (optional) The id of the root element. You can use this property to set a custom element the breadcrumb should start with. | | rootId | `string` | null | (optional) The id of the root element. You can use this property to set a custom element the breadcrumb should start with. |
| target | [`DocumentListComponent`](../../content-services/components/document-list.component.md) | | (optional) [Document List component](../../content-services/components/document-list.component.md) to operate with. The list will update when the breadcrumb is clicked. | | target | [`DocumentListComponent`](../../content-services/components/document-list.component.md) | | (optional) [Document List component](../../content-services/components/document-list.component.md) to operate with. The list will update when the breadcrumb is clicked. |
| transform | `Function` | | Transformation to be performed on the chosen/folder node before building the breadcrumb UI. Can be useful when custom formatting is needed for the breadcrumb. You can change the path elements from the node that are used to build the breadcrumb using this function. | | transform | `Function` | | Transformation to be performed on the chosen/folder node before building the breadcrumb UI. Can be useful when custom formatting is needed for the breadcrumb. You can change the path elements from the node that are used to build the breadcrumb using this function. |
| selectedRowItemsCount | `number` | 0 | Number of table rows that are currently selected. |
### Events ### Events

View File

@ -98,19 +98,20 @@ Displays the documents from a repository.
### Events ### Events
| Name | Type | Description | | Name | Type | Description |
| ---- | ---- | ----------- | |---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| error | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<any>` | Emitted when the API fails to get the Document List data | | error | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<any>` | Emitted when the API fails to get the Document List data |
| filterSelection | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FilterSearch`](../../../lib/content-services/src/lib/search/models/filter-search.interface.ts)`[]>` | Emitted when a filter value is selected | | filterSelection | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FilterSearch`](../../../lib/content-services/src/lib/search/models/filter-search.interface.ts)`[]>` | Emitted when a filter value is selected |
| folderChange | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`NodeEntryEvent`](../../../lib/content-services/src/lib/document-list/components/node.event.ts)`>` | Emitted when the current display folder changes | | folderChange | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`NodeEntryEvent`](../../../lib/content-services/src/lib/document-list/components/node.event.ts)`>` | Emitted when the current display folder changes |
| nodeClick | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`NodeEntityEvent`](../../../lib/content-services/src/lib/document-list/components/node.event.ts)`>` | Emitted when the user clicks a list node | | nodeClick | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`NodeEntityEvent`](../../../lib/content-services/src/lib/document-list/components/node.event.ts)`>` | Emitted when the user clicks a list node |
| nodeDblClick | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`NodeEntityEvent`](../../../lib/content-services/src/lib/document-list/components/node.event.ts)`>` | Emitted when the user double-clicks a list node | | nodeDblClick | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`NodeEntityEvent`](../../../lib/content-services/src/lib/document-list/components/node.event.ts)`>` | Emitted when the user double-clicks a list node |
| nodeSelected | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`NodeEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeEntry.md)`[]>` | Emitted when the node selection change | | nodeSelected | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`NodeEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeEntry.md)`[]>` | Emitted when the node selection change |
| preview | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`NodeEntityEvent`](../../../lib/content-services/src/lib/document-list/components/node.event.ts)`>` | Emitted when the user acts upon files with either single or double click (depends on `navigation-mode`). Useful for integration with the [Viewer component](../../core/components/viewer.component.md). | | preview | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`NodeEntityEvent`](../../../lib/content-services/src/lib/document-list/components/node.event.ts)`>` | Emitted when the user acts upon files with either single or double click (depends on `navigation-mode`). Useful for integration with the [Viewer component](../../core/components/viewer.component.md). |
| ready | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`NodePaging`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/NodePaging.md)`>` | Emitted when the Document List has loaded all items and is ready for use | | ready | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`NodePaging`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/NodePaging.md)`>` | Emitted when the Document List has loaded all items and is ready for use |
| columnsVisibilityChanged | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<{ [columnId: string]: boolean } \| undefined>` | Emitted when columns visibility change | | columnsVisibilityChanged | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<{ [columnId: string]: boolean } \| undefined>` | Emitted when columns visibility change |
| columnsWidthChanged | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<{ [columnId: string]: number } \| undefined>` | Emitted when columns width change | | columnsWidthChanged | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<{ [columnId: string]: number } \| undefined>` | Emitted when columns width change |
| columnsOrderChanged | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`< string[] \| undefined>` | Emitted when columns order change | | columnsOrderChanged | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`< string[] \| undefined>` | Emitted when columns order change |
| selectedItemsCountChanged | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`< number \| undefined>` | Emitted when items row count change |
## Details ## Details

View File

@ -448,15 +448,16 @@ Learn more about styling your datatable: [Customizing the component's styles](#c
### Events ### Events
| Name | Type | Description | | Name | Type | Description |
|---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| |---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------|
| columnOrderChanged | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`DataColumn`](../../../lib/core/src/lib/datatable/data/data-column.model.ts)`<>[]>` | Emitted when the column order is changed. | | columnOrderChanged | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`DataColumn`](../../../lib/core/src/lib/datatable/data/data-column.model.ts)`<>[]>` | Emitted when the column order is changed. |
| columnsWidthChanged | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`DataColumn`](../../../lib/core/src/lib/datatable/data/data-column.model.ts)`<>[]>` | Emitted when the column width is changed. | | columnsWidthChanged | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`DataColumn`](../../../lib/core/src/lib/datatable/data/data-column.model.ts)`<>[]>` | Emitted when the column width is changed. |
| executeRowAction | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`DataRowActionEvent`](../../../lib/core/src/lib/datatable/components/data-row-action.event.ts)`>` | Emitted when the user executes a row action. | | selectedItemsCountChanged | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<number>` | Emitted when the item row count is changed. |
| rowClick | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`DataRowEvent`](../../../lib/core/src/lib/datatable/data/data-row-event.model.ts)`>` | Emitted when the user clicks a row. | | executeRowAction | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`DataRowActionEvent`](../../../lib/core/src/lib/datatable/components/data-row-action.event.ts)`>` | Emitted when the user executes a row action. |
| rowDblClick | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`DataRowEvent`](../../../lib/core/src/lib/datatable/data/data-row-event.model.ts)`>` | Emitted when the user double-clicks a row. | | rowClick | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`DataRowEvent`](../../../lib/core/src/lib/datatable/data/data-row-event.model.ts)`>` | Emitted when the user clicks a row. |
| showRowActionsMenu | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`DataCellEvent`](../../../lib/core/src/lib/datatable/components/data-cell.event.ts)`>` | Emitted before the actions menu is displayed for a row. | | rowDblClick | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`DataRowEvent`](../../../lib/core/src/lib/datatable/data/data-row-event.model.ts)`>` | Emitted when the user double-clicks a row. |
| showRowContextMenu | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`DataCellEvent`](../../../lib/core/src/lib/datatable/components/data-cell.event.ts)`>` | Emitted before the context menu is displayed for a row. | | showRowActionsMenu | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`DataCellEvent`](../../../lib/core/src/lib/datatable/components/data-cell.event.ts)`>` | Emitted before the actions menu is displayed for a row. |
| showRowContextMenu | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`DataCellEvent`](../../../lib/core/src/lib/datatable/components/data-cell.event.ts)`>` | Emitted before the context menu is displayed for a row. |
## Details ## Details

View File

@ -57,7 +57,7 @@
</a> </a>
<div *ngSwitchDefault class="adf-breadcrumb-item-current" aria-current="location"> <div *ngSwitchDefault class="adf-breadcrumb-item-current" aria-current="location">
{{ item.name | translate }} {{ (selectedRowItemsCount < 1 ? item.name : 'BREADCRUMB.HEADER.SELECTED') | translate: { count: selectedRowItemsCount } }}
</div> </div>
<mat-icon class="adf-breadcrumb-item-chevron" *ngIf="!last"> <mat-icon class="adf-breadcrumb-item-chevron" *ngIf="!last">

View File

@ -33,6 +33,9 @@ describe('Breadcrumb', () => {
}); });
let documentListComponent: DocumentListComponent; let documentListComponent: DocumentListComponent;
const getBreadcrumbActionText = (): string =>
fixture.debugElement.nativeElement.querySelector('.adf-breadcrumb-item-current').textContent.trim();
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ContentTestingModule], imports: [ContentTestingModule],
@ -300,4 +303,26 @@ describe('Breadcrumb', () => {
expect(component.route[3].id).toBe('test-id'); expect(component.route[3].id).toBe('test-id');
expect(component.route[3].name).toBe('test-name'); expect(component.route[3].name).toBe('test-name');
}); });
it('should set title based on selectedRowItemsCount', () => {
component.transform = (transformNode) => {
transformNode.id = 'test-id';
transformNode.name = 'test-name';
return transformNode;
};
component.folderNode = {
path: { elements: [{ id: 'element-1-id', name: 'element-1-name' }] }
} as Node;
component.ngOnChanges();
fixture.detectChanges();
expect(getBreadcrumbActionText()).toEqual('test-name');
component.selectedRowItemsCount = 2;
fixture.detectChanges();
expect(getBreadcrumbActionText()).toEqual('BREADCRUMB.HEADER.SELECTED');
});
}); });

View File

@ -72,6 +72,10 @@ export class BreadcrumbComponent implements OnInit, OnChanges, OnDestroy {
@Input() @Input()
maxItems: number; maxItems: number;
/** Number of table rows that are currently selected. */
@Input()
selectedRowItemsCount = 0;
previousNodes: PathElement[]; previousNodes: PathElement[];
lastNodes: PathElement[]; lastNodes: PathElement[];

View File

@ -22,6 +22,7 @@
(executeRowAction)="onExecuteRowAction($event)" (executeRowAction)="onExecuteRowAction($event)"
(columnsWidthChanged)="onColumnsWidthChange($event)" (columnsWidthChanged)="onColumnsWidthChange($event)"
(columnOrderChanged)="onColumnOrderChange($event)" (columnOrderChanged)="onColumnOrderChange($event)"
(selectedItemsCountChanged)="onSelectedItemsCountChanged($event)"
(rowClick)="onNodeClick($event.value?.node)" (rowClick)="onNodeClick($event.value?.node)"
(rowDblClick)="onNodeDblClick($event.value?.node)" (rowDblClick)="onNodeDblClick($event.value?.node)"
(row-select)="onNodeSelect($any($event).detail)" (row-select)="onNodeSelect($any($event).detail)"

View File

@ -1468,6 +1468,12 @@ describe('DocumentList', () => {
expect(documentList.reload).not.toHaveBeenCalled(); expect(documentList.reload).not.toHaveBeenCalled();
}); });
it('should emit custom date range on date picker closed', () => {
const selectedItemsCountChangedSpy = spyOn(documentList.selectedItemsCountChanged, 'emit');
documentList.onSelectedItemsCountChanged(1);
expect(selectedItemsCountChangedSpy).toHaveBeenCalledWith(1);
});
it('should add includeFields in the server request when present', () => { it('should add includeFields in the server request when present', () => {
documentList.includeFields = ['test-include']; documentList.includeFields = ['test-include'];
documentList.currentFolderId = 'fake-id'; documentList.currentFolderId = 'fake-id';

View File

@ -388,6 +388,10 @@ export class DocumentListComponent extends DataTableSchema implements OnInit, On
@Output() @Output()
columnsOrderChanged = new EventEmitter<string[] | undefined>(); columnsOrderChanged = new EventEmitter<string[] | undefined>();
/** Emitted when the selected row items count in the table changed. */
@Output()
selectedItemsCountChanged = new EventEmitter<number | undefined>();
@ViewChild('dataTable', { static: true }) @ViewChild('dataTable', { static: true })
dataTable: DataTableComponent; dataTable: DataTableComponent;
@ -832,6 +836,10 @@ export class DocumentListComponent extends DataTableSchema implements OnInit, On
this.columnsWidthChanged.emit(this.columnsWidths); this.columnsWidthChanged.emit(this.columnsWidths);
} }
onSelectedItemsCountChanged(count: number) {
this.selectedItemsCountChanged.emit(count);
}
onNodeClick(nodeEntry: NodeEntry) { onNodeClick(nodeEntry: NodeEntry) {
const domEvent = new CustomEvent('node-click', { const domEvent = new CustomEvent('node-click', {
detail: { detail: {

View File

@ -662,6 +662,9 @@
"ARIA-LABEL": { "ARIA-LABEL": {
"BREADCRUMB": "Breadcrumb", "BREADCRUMB": "Breadcrumb",
"DROPDOWN": "Dropdown" "DROPDOWN": "Dropdown"
},
"HEADER": {
"SELECTED": "Selected ({{ count }})"
} }
}, },
"NAME_COLUMN_LINK": { "NAME_COLUMN_LINK": {

View File

@ -21,7 +21,7 @@
</div> </div>
<!-- Columns --> <!-- Columns -->
<div *ngIf="multiselect" class="adf-datatable-cell-header adf-datatable-checkbox"> <div *ngIf="multiselect" class="adf-datatable-cell-header adf-datatable-checkbox-cell">
<mat-checkbox [indeterminate]="isSelectAllIndeterminate" [checked]="isSelectAllChecked" (change)="onSelectAllClick($event)" class="adf-checkbox-sr-only">{{ 'ADF-DATATABLE.ACCESSIBILITY.SELECT_ALL' | translate }}</mat-checkbox> <mat-checkbox [indeterminate]="isSelectAllIndeterminate" [checked]="isSelectAllChecked" (change)="onSelectAllClick($event)" class="adf-checkbox-sr-only">{{ 'ADF-DATATABLE.ACCESSIBILITY.SELECT_ALL' | translate }}</mat-checkbox>
</div> </div>
@ -182,9 +182,10 @@
</mat-menu> </mat-menu>
</div> </div>
<label *ngIf="multiselect" [for]="'select-file-' + idx" class="adf-datatable-cell adf-datatable-checkbox"> <label *ngIf="multiselect" [for]="'select-file-' + idx" class="adf-datatable-cell adf-datatable-checkbox-cell adf-datatable-checkbox-single">
<mat-checkbox <mat-checkbox
[id]="'select-file-' + idx" [id]="'select-file-' + idx"
[class.adf-datatable-checkbox-selected]="row.isSelected"
[checked]="row.isSelected" [checked]="row.isSelected"
[attr.aria-checked]="row.isSelected" [attr.aria-checked]="row.isSelected"
role="checkbox" role="checkbox"

View File

@ -158,14 +158,30 @@ $data-table-cell-min-width-file-size: $data-table-cell-min-width-1 !default;
display: flex; display: flex;
align-items: center; align-items: center;
.adf-datatable-checkbox { &:hover {
.adf-datatable-checkbox-cell {
&.adf-datatable-checkbox-single {
visibility: visible;
}
}
}
.adf-datatable-checkbox-single {
visibility: hidden;
&:has(.adf-datatable-checkbox-selected) {
visibility: visible;
}
}
.adf-datatable-checkbox-cell {
max-width: $data-table-thumbnail-width; max-width: $data-table-thumbnail-width;
width: $data-table-thumbnail-width; width: $data-table-thumbnail-width;
} }
/* query for Microsoft IE 11 */ /* query for Microsoft IE 11 */
@media screen and (-ms-high-contrast: active), screen and (-ms-high-contrast: none) { @media screen and (-ms-high-contrast: active), screen and (-ms-high-contrast: none) {
.adf-datatable-checkbox { .adf-datatable-checkbox-cell {
padding-top: 15px; padding-top: 15px;
} }
} }
@ -283,6 +299,11 @@ $data-table-cell-min-width-file-size: $data-table-cell-min-width-1 !default;
padding-right: 15px; padding-right: 15px;
} }
.adf-datatable-checkbox-cell {
display: flex;
justify-content: center;
}
.adf-cell-value { .adf-cell-value {
display: flex; display: flex;
min-height: inherit; min-height: inherit;

View File

@ -154,6 +154,7 @@ describe('DataTable', () => {
}); });
it('should preserve the historical selection order', () => { it('should preserve the historical selection order', () => {
spyOn(dataTable.selectedItemsCountChanged, 'emit');
dataTable.data = new ObjectDataTableAdapter([{ id: 0 }, { id: 1 }, { id: 2 }], [new ObjectDataColumn({ key: 'id' })]); dataTable.data = new ObjectDataTableAdapter([{ id: 0 }, { id: 1 }, { id: 2 }], [new ObjectDataColumn({ key: 'id' })]);
const rows = dataTable.data.getRows(); const rows = dataTable.data.getRows();
@ -166,6 +167,25 @@ describe('DataTable', () => {
expect(selection[0].getValue('id')).toBe(2); expect(selection[0].getValue('id')).toBe(2);
expect(selection[1].getValue('id')).toBe(0); expect(selection[1].getValue('id')).toBe(0);
expect(selection[2].getValue('id')).toBe(1); expect(selection[2].getValue('id')).toBe(1);
expect(dataTable.selectedItemsCountChanged.emit).toHaveBeenCalledTimes(3);
});
it('should selectedItemsCountChanged be emitted 4 times', () => {
spyOn(dataTable.selectedItemsCountChanged, 'emit');
dataTable.data = new ObjectDataTableAdapter([{ id: 0 }, { id: 1 }, { id: 2 }], [new ObjectDataColumn({ key: 'id' })]);
const rows = dataTable.data.getRows();
dataTable.selectRow(rows[2], true);
dataTable.selectRow(rows[0], true);
dataTable.selectRow(rows[1], true);
dataTable.selectRow(rows[1], false);
expect(dataTable.selectedItemsCountChanged.emit).toHaveBeenCalledWith(1);
expect(dataTable.selectedItemsCountChanged.emit).toHaveBeenCalledWith(2);
expect(dataTable.selectedItemsCountChanged.emit).toHaveBeenCalledWith(3);
expect(dataTable.selectedItemsCountChanged.emit).toHaveBeenCalledTimes(4);
}); });
it('should update schema if columns change', fakeAsync(() => { it('should update schema if columns change', fakeAsync(() => {
@ -537,6 +557,7 @@ describe('DataTable', () => {
}); });
it('should unselect the row searching it by row id, when row id is defined', () => { it('should unselect the row searching it by row id, when row id is defined', () => {
spyOn(dataTable.selectedItemsCountChanged, 'emit');
const findSelectionByIdSpy = spyOn(dataTable, 'findSelectionById'); const findSelectionByIdSpy = spyOn(dataTable, 'findSelectionById');
dataTable.data = new ObjectDataTableAdapter([], [new ObjectDataColumn({ key: 'name' })]); dataTable.data = new ObjectDataTableAdapter([], [new ObjectDataColumn({ key: 'name' })]);
@ -552,6 +573,8 @@ describe('DataTable', () => {
expect(indexOfSpy).not.toHaveBeenCalled(); expect(indexOfSpy).not.toHaveBeenCalled();
expect(findSelectionByIdSpy).toHaveBeenCalledWith(fakeDataRows[0].id); expect(findSelectionByIdSpy).toHaveBeenCalledWith(fakeDataRows[0].id);
expect(dataTable.selectedItemsCountChanged.emit).toHaveBeenCalledTimes(1);
expect(dataTable.selectedItemsCountChanged.emit).toHaveBeenCalledWith(2);
}); });
it('should unselect the row by searching for the exact same reference of it (indexOf), when row id is not defined ', () => { it('should unselect the row by searching for the exact same reference of it (indexOf), when row id is not defined ', () => {
@ -869,7 +892,7 @@ describe('DataTable', () => {
dataTable.multiselect = true; dataTable.multiselect = true;
dataTable.onCheckboxChange(rows[0], { checked: true } as MatCheckboxChange); dataTable.onCheckboxChange(rows[0], { checked: true } as MatCheckboxChange);
expect(dataTable.isSelectAllIndeterminate).toBe(true); expect(dataTable.isSelectAllIndeterminate).toBe(true);
expect(dataTable.isSelectAllChecked).toBe(false); expect(dataTable.isSelectAllChecked).toBe(true);
dataTable.onCheckboxChange(rows[1], { checked: true } as MatCheckboxChange); dataTable.onCheckboxChange(rows[1], { checked: true } as MatCheckboxChange);
expect(dataTable.isSelectAllIndeterminate).toBe(false); expect(dataTable.isSelectAllIndeterminate).toBe(false);

View File

@ -191,6 +191,10 @@ export class DataTableComponent implements OnInit, AfterContentInit, OnChanges,
@Output() @Output()
columnsWidthChanged = new EventEmitter<DataColumn[]>(); columnsWidthChanged = new EventEmitter<DataColumn[]>();
/** Emitted when the selected row items count in the table changed. */
@Output()
selectedItemsCountChanged = new EventEmitter<number>();
/** /**
* Flag that indicates if the datatable is in loading state and needs to show the * Flag that indicates if the datatable is in loading state and needs to show the
* loading template (see the docs to learn how to configure a loading template). * loading template (see the docs to learn how to configure a loading template).
@ -516,35 +520,38 @@ export class DataTableComponent implements OnInit, AfterContentInit, OnChanges,
} }
private handleRowSelection(row: DataRow, e: KeyboardEvent | MouseEvent) { private handleRowSelection(row: DataRow, e: KeyboardEvent | MouseEvent) {
if (this.data) { if (!this.data) {
if (this.isSingleSelectionMode()) { return;
if (row.isSelected) { }
this.resetSelection();
this.emitRowSelectionEvent('row-unselect', null);
} else {
this.resetSelection();
this.selectRow(row, true);
this.emitRowSelectionEvent('row-select', row);
}
}
if (this.isMultiSelectionMode()) { if (this.isSingleSelectionMode()) {
const modifier = e && (e.metaKey || e.ctrlKey); if (row.isSelected) {
let newValue: boolean; this.resetSelection();
if (this.selection.length === 1) { this.emitRowSelectionEvent('row-unselect', null);
newValue = !row.isSelected; } else {
} else { this.resetSelection();
newValue = modifier ? !row.isSelected : true; this.selectRow(row, true);
} this.emitRowSelectionEvent('row-select', row);
const domEventName = newValue ? 'row-select' : 'row-unselect';
if (!modifier) {
this.resetSelection();
}
this.selectRow(row, newValue);
this.emitRowSelectionEvent(domEventName, row);
} }
} }
if (this.isMultiSelectionMode()) {
const modifier = e && (e.metaKey || e.ctrlKey);
let newValue: boolean;
if (this.selection.length === 1) {
newValue = !row.isSelected;
} else {
newValue = modifier ? !row.isSelected : true;
}
const domEventName = newValue ? 'row-select' : 'row-unselect';
if (!modifier) {
this.resetSelection();
}
this.selectRow(row, newValue);
this.emitRowSelectionEvent(domEventName, row);
this.checkSelectAllCheckboxState();
}
} }
resetSelection(): void { resetSelection(): void {
@ -672,8 +679,8 @@ export class DataTableComponent implements OnInit, AfterContentInit, OnChanges,
this.isSelectAllChecked = true; this.isSelectAllChecked = true;
this.isSelectAllIndeterminate = false; this.isSelectAllIndeterminate = false;
} else if (numberOfSelectedRows > 0 && numberOfSelectedRows < rows.length) { } else if (numberOfSelectedRows > 0 && numberOfSelectedRows < rows.length) {
this.isSelectAllChecked = false;
this.isSelectAllIndeterminate = true; this.isSelectAllIndeterminate = true;
this.isSelectAllChecked = true;
} else { } else {
this.isSelectAllChecked = false; this.isSelectAllChecked = false;
this.isSelectAllIndeterminate = false; this.isSelectAllIndeterminate = false;
@ -799,6 +806,8 @@ export class DataTableComponent implements OnInit, AfterContentInit, OnChanges,
this.selection.splice(idx, 1); this.selection.splice(idx, 1);
} }
} }
this.selectedItemsCountChanged.emit(this.selection.length);
} }
} }