mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-04-23 22:30:37 +00:00
[ACS-10280] Keyboard Navigation: Search: Unnecessary stops in the results table using the Tab key: (#11529)
* [ACS-10280]: removes rows being focusable by default * [ACS-10280]: non fucusable chips by default * [ACS-10280]: datatable UTs * [ACS-10280]: updates focus management for datatable * [ACS-10280]: improves tabfocus for hidden action buttons
This commit is contained in:
@@ -90,19 +90,21 @@ describe('DataTableRowComponent', () => {
|
||||
expect(fixture.debugElement.nativeElement.getAttribute('aria-label')).toBe('some-name');
|
||||
});
|
||||
|
||||
it('should set tabindex as focusable when row is not disabled', () => {
|
||||
it('should set tabindex as non focusable by default (disabled propery is not passed)', () => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement.getAttribute('tabindex')).toBeNull();
|
||||
});
|
||||
|
||||
it('should not set tabindex when row is disabled', () => {
|
||||
component.disabled = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement.getAttribute('tabindex')).toBe('0');
|
||||
});
|
||||
|
||||
it('should not set tabindex when row is disabled', () => {
|
||||
component.disabled = true;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.nativeElement.getAttribute('tabindex')).toBe(null);
|
||||
});
|
||||
|
||||
it('should focus element', () => {
|
||||
expect(document.activeElement.classList.contains('adf-datatable-row')).toBe(false);
|
||||
component.disabled = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
component.focus();
|
||||
expect(document.activeElement.classList.contains('adf-datatable-row')).toBe(true);
|
||||
|
||||
@@ -25,14 +25,13 @@ import { DataRow } from '../../data/data-row.model';
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: {
|
||||
class: 'adf-datatable-row',
|
||||
tabindex: '0',
|
||||
role: 'row'
|
||||
}
|
||||
})
|
||||
export class DataTableRowComponent implements FocusableOption {
|
||||
@Input() row: DataRow;
|
||||
|
||||
@Input() disabled = false;
|
||||
@Input() disabled = true;
|
||||
|
||||
@Output()
|
||||
select: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
cdkDropListOrientation="horizontal"
|
||||
[cdkDropListSortPredicate]="filterDisabledColumns"
|
||||
data-automation-id="datatable-row-header"
|
||||
[disabled]="!isHeaderVisible()"
|
||||
class="adf-datatable-row"
|
||||
role="row">
|
||||
|
||||
@@ -58,7 +57,7 @@
|
||||
(click)="onColumnHeaderClick(col, $event)"
|
||||
(keyup.enter)="onColumnHeaderClick(col, $event)"
|
||||
role="columnheader"
|
||||
[attr.tabindex]="isHeaderVisible() ? 0 : null"
|
||||
[attr.tabindex]="col.sortable ? 0 : null"
|
||||
[attr.aria-sort]="col.sortable ? (getAriaSort(col) | translate) : null"
|
||||
cdkDrag
|
||||
cdkDragLockAxis="x"
|
||||
@@ -214,7 +213,7 @@
|
||||
[class.adf-datatable-row__dragging]="isDraggingRow"
|
||||
[attr.data-automation-id]="'datatable-row-' + idx"
|
||||
(contextmenu)="markRowAsContextMenuSource(row)"
|
||||
[disabled]="!(data.allowFocusOnRows ?? true)"
|
||||
[disabled]="!(data?.allowFocusOnRows ?? true)"
|
||||
>
|
||||
<!-- Drag button -->
|
||||
<div *ngIf="enableDragRows"
|
||||
@@ -251,7 +250,7 @@
|
||||
(click)="onCheckboxLabelClick(row, $event)"
|
||||
[for]="'select-file-' + idx"
|
||||
class="adf-datatable-cell adf-datatable-checkbox adf-datatable-checkbox-single"
|
||||
tabindex="0">
|
||||
[attr.tabindex]="null">
|
||||
<mat-checkbox
|
||||
[id]="'select-file-' + idx"
|
||||
[disabled]="!row?.isSelectable"
|
||||
@@ -278,7 +277,7 @@
|
||||
[attr.aria-selected]="row.isSelected"
|
||||
[attr.aria-label]="col.title ? (col.title | translate) : null"
|
||||
(click)="onRowClick(row, $event)"
|
||||
[attr.tabindex]="col.focus ? 0 : null"
|
||||
[attr.tabindex]="null"
|
||||
(keydown.enter)="onEnterKeyPressed(row, $any($event))"
|
||||
[adf-context-menu]="getContextMenuActions(row, col)"
|
||||
[adf-context-menu-enabled]="contextMenu"
|
||||
@@ -327,7 +326,7 @@
|
||||
<div
|
||||
*ngSwitchCase="'date'"
|
||||
class="adf-cell-value adf-cell-date"
|
||||
[attr.tabindex]="data.getValue(row, col, resolverFn)? 0 : -1"
|
||||
|
||||
[attr.data-automation-id]="'date_' + (data.getValue(row, col, resolverFn) | adfLocalizedDate: 'medium') ">
|
||||
<adf-date-cell class="adf-datatable-center-date-column-ie"
|
||||
[data]="data"
|
||||
@@ -338,7 +337,7 @@
|
||||
[dateConfig]="col.dateConfig" />
|
||||
</div>
|
||||
|
||||
<div *ngSwitchCase="'location'" [attr.tabindex]="data.getValue(row, col, resolverFn)? 0 : -1" class="adf-cell-value"
|
||||
<div *ngSwitchCase="'location'" class="adf-cell-value"
|
||||
[attr.data-automation-id]="'location' + data.getValue(row, col, resolverFn)">
|
||||
<adf-location-cell
|
||||
[data]="data"
|
||||
@@ -347,7 +346,7 @@
|
||||
[resolverFn]="resolverFn"
|
||||
[tooltip]="getCellTooltip(row, col)" />
|
||||
</div>
|
||||
<div *ngSwitchCase="'fileSize'" [attr.tabindex]="data.getValue(row, col, resolverFn)? 0 : -1" class="adf-cell-value"
|
||||
<div *ngSwitchCase="'fileSize'" class="adf-cell-value"
|
||||
[attr.data-automation-id]="'fileSize_' + data.getValue(row, col, resolverFn)">
|
||||
<adf-filesize-cell class="adf-datatable-center-size-column-ie"
|
||||
[data]="data"
|
||||
@@ -356,7 +355,7 @@
|
||||
[resolverFn]="resolverFn"
|
||||
[tooltip]="getCellTooltip(row, col)" />
|
||||
</div>
|
||||
<div *ngSwitchCase="'text'" [attr.tabindex]="data.getValue(row, col, resolverFn)? 0 : -1" class="adf-cell-value"
|
||||
<div *ngSwitchCase="'text'" class="adf-cell-value"
|
||||
[attr.data-automation-id]="'text_' + data.getValue(row, col, resolverFn)">
|
||||
<adf-datatable-cell
|
||||
[copyContent]="col.copyContent"
|
||||
@@ -366,7 +365,7 @@
|
||||
[resolverFn]="resolverFn"
|
||||
[tooltip]="getCellTooltip(row, col)" />
|
||||
</div>
|
||||
<div *ngSwitchCase="'boolean'" [attr.tabindex]="data.getValue(row, col, resolverFn)? 0 : -1" class="adf-cell-value"
|
||||
<div *ngSwitchCase="'boolean'" class="adf-cell-value"
|
||||
[attr.data-automation-id]="'boolean_' + data.getValue(row, col, resolverFn)">
|
||||
<adf-boolean-cell
|
||||
[data]="data"
|
||||
@@ -375,7 +374,7 @@
|
||||
[resolverFn]="resolverFn"
|
||||
[tooltip]="getCellTooltip(row, col)" />
|
||||
</div>
|
||||
<div *ngSwitchCase="'json'" [attr.tabindex]="data.getValue(row, col, resolverFn)? 0 : -1" class="adf-cell-value">
|
||||
<div *ngSwitchCase="'json'" class="adf-cell-value">
|
||||
<adf-json-cell
|
||||
[editable]="col.editable"
|
||||
[data]="data"
|
||||
@@ -385,7 +384,6 @@
|
||||
</div>
|
||||
<div *ngSwitchCase="'amount'"
|
||||
class="adf-cell-value"
|
||||
[attr.tabindex]="data.getValue(row, col, resolverFn)? 0 : -1"
|
||||
[attr.data-automation-id]="'amount_' + data.getValue(row, col, resolverFn)">
|
||||
<adf-amount-cell
|
||||
[data]="data"
|
||||
@@ -396,7 +394,6 @@
|
||||
</div>
|
||||
<div *ngSwitchCase="'number'"
|
||||
class="adf-cell-value"
|
||||
[attr.tabindex]="data.getValue(row, col, resolverFn)? 0 : -1"
|
||||
[attr.data-automation-id]="'number_' + data.getValue(row, col, resolverFn)">
|
||||
<adf-number-cell
|
||||
[data]="data"
|
||||
@@ -411,7 +408,7 @@
|
||||
</ng-container>
|
||||
</div>
|
||||
<div *ngIf="col.template" class="adf-datatable-cell-container">
|
||||
<div class="adf-cell-value" [attr.tabindex]="col.focus ? 0 : null">
|
||||
<div class="adf-cell-value">
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="col.template"
|
||||
[ngTemplateOutletContext]="{ $implicit: { data: data, row: row, col: col }, value: data.getValue(row, col, resolverFn) }" />
|
||||
@@ -425,7 +422,6 @@
|
||||
((actions && actionsPosition === 'right') ||
|
||||
(mainActionTemplate && showMainDatatableActions))"
|
||||
role="gridcell"
|
||||
tabindex="0"
|
||||
class="adf-datatable-cell adf-datatable__actions-cell adf-datatable-center-actions-column-ie adf-datatable-actions-menu">
|
||||
|
||||
<ng-container *ngIf="(actions && actionsPosition === 'right')">
|
||||
|
||||
@@ -342,23 +342,32 @@ $data-table-cell-min-width-file-size: $data-table-cell-min-width-1 !default;
|
||||
}
|
||||
}
|
||||
|
||||
.adf-datatable-row .adf-datatable-actions-menu {
|
||||
margin-left: auto;
|
||||
justify-content: end;
|
||||
padding-right: 15px;
|
||||
|
||||
&:focus-visible,
|
||||
&:focus-within {
|
||||
.adf-datatable-hide-actions-without-hover {
|
||||
.adf-datatable-row {
|
||||
&:focus-within,
|
||||
&:hover {
|
||||
.adf-datatable-actions-menu .adf-datatable-hide-actions-without-hover {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
&-provided {
|
||||
/* stylelint-disable declaration-no-important */
|
||||
max-width: 100px !important;
|
||||
justify-content: center;
|
||||
padding-right: 0;
|
||||
.adf-datatable-actions-menu {
|
||||
margin-left: auto;
|
||||
justify-content: end;
|
||||
padding-right: 15px;
|
||||
|
||||
&:focus-visible,
|
||||
&:focus-within {
|
||||
.adf-datatable-hide-actions-without-hover {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
&-provided {
|
||||
/* stylelint-disable declaration-no-important */
|
||||
max-width: 100px !important;
|
||||
justify-content: center;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import { HarnessLoader } from '@angular/cdk/testing';
|
||||
import { ConfigurableFocusTrapFactory } from '@angular/cdk/a11y';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { DataTableAdapter } from '@alfresco/adf-core';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-custom-column-template-component',
|
||||
@@ -1712,52 +1713,14 @@ describe('Accessibility', () => {
|
||||
|
||||
expect(document.activeElement?.getAttribute('data-automation-id')).toBe('datatable-row-0');
|
||||
});
|
||||
|
||||
it('should select header row when `showHeader` is `Always`', () => {
|
||||
dataTable.showHeader = ShowHeaderMode.Always;
|
||||
|
||||
dataTable.ngOnChanges({
|
||||
rows: new SimpleChange(null, dataRows, false)
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
dataTable.ngAfterViewInit();
|
||||
|
||||
const rowElement = testingUtils.getAllByCSS('.adf-datatable-body .adf-datatable-row')[0];
|
||||
testingUtils.setDebugElement(rowElement);
|
||||
testingUtils.clickByCSS('.adf-datatable-cell');
|
||||
|
||||
fixture.debugElement.nativeElement.dispatchEvent(event);
|
||||
|
||||
expect(document.activeElement?.getAttribute('data-automation-id')).toBe('datatable-row-header');
|
||||
});
|
||||
|
||||
it('should not select header row when `showHeader` is `Never`', () => {
|
||||
dataTable.showHeader = ShowHeaderMode.Never;
|
||||
|
||||
dataTable.ngOnChanges({
|
||||
rows: new SimpleChange(null, dataRows, false)
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
dataTable.ngAfterViewInit();
|
||||
|
||||
const rowElement = testingUtils.getAllByCSS('.adf-datatable-body .adf-datatable-row')[0];
|
||||
testingUtils.setDebugElement(rowElement);
|
||||
testingUtils.clickByCSS('.adf-datatable-cell');
|
||||
|
||||
fixture.debugElement.nativeElement.dispatchEvent(event);
|
||||
|
||||
expect(document.activeElement?.getAttribute('data-automation-id')).toBe('datatable-row-1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('DataTable row focus management', () => {
|
||||
const testFocus = (focus: boolean, selector: string, expectedTabindex: string | null) => {
|
||||
dataTable.showHeader = ShowHeaderMode.Never;
|
||||
describe('Row cells focus management', () => {
|
||||
const testFocus = (sortable: boolean, selector: string, expectedTabindex: string | null) => {
|
||||
dataTable.showHeader = ShowHeaderMode.Always;
|
||||
const dataRows = [{ name: 'name1' }];
|
||||
|
||||
dataTable.data = new ObjectDataTableAdapter([], [new ObjectDataColumn({ key: 'name', template: columnCustomTemplate, focus })]);
|
||||
dataTable.data = new ObjectDataTableAdapter([], [new ObjectDataColumn({ key: 'name', template: columnCustomTemplate, sortable })]);
|
||||
|
||||
dataTable.ngOnChanges({
|
||||
rows: new SimpleChange(null, dataRows, false)
|
||||
@@ -1770,23 +1733,24 @@ describe('Accessibility', () => {
|
||||
expect(element?.nativeElement.getAttribute('tabindex')).toEqual(expectedTabindex);
|
||||
};
|
||||
|
||||
const cellaValSelector = '.adf-datatable-row[data-automation-id="datatable-row-0"] .adf-cell-value';
|
||||
const cellWrapperSelector = '.adf-datatable-cell';
|
||||
const headerCellSelector = '.adf-datatable-cell-header';
|
||||
|
||||
it('should remove cell focus when [focus] is set to false', () => {
|
||||
testFocus(false, cellaValSelector, null);
|
||||
it('should remove header cell focus when cell is not sortable', () => {
|
||||
testFocus(false, headerCellSelector, null);
|
||||
});
|
||||
|
||||
it('should allow element focus when [focus] is set to true', () => {
|
||||
testFocus(true, cellaValSelector, '0');
|
||||
it('should allow header cell focus when cell is sortable', () => {
|
||||
testFocus(true, headerCellSelector, '0');
|
||||
});
|
||||
|
||||
it('should remove col focus when [focus] is set to false', () => {
|
||||
testFocus(false, cellWrapperSelector, null);
|
||||
it('should set tabindex equal to null on body cell by default (sortable = false)', () => {
|
||||
const bodyCellSelector = '.adf-datatable-cell';
|
||||
testFocus(false, bodyCellSelector, null);
|
||||
});
|
||||
|
||||
it('should allow col focus when [focus] is set to true', () => {
|
||||
testFocus(true, cellWrapperSelector, '0');
|
||||
it('should set tabindex equal to null on body cell by default (sortable = true)', () => {
|
||||
const bodyCellSelector = '.adf-datatable-cell';
|
||||
testFocus(true, bodyCellSelector, null);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1802,12 +1766,10 @@ describe('Accessibility', () => {
|
||||
this.allowFocusOnRows = allow;
|
||||
}
|
||||
}
|
||||
const testAllowFocusOnRows = (allowFocus: boolean, expectedTabindex: string | null) => {
|
||||
const testAllowFocusOnRows = (expectedTabindex: string | null, adapter: DataTableAdapter) => {
|
||||
const fakeDataRows = [new FakeDataRow(), new FakeDataRow()];
|
||||
|
||||
const adapter = new ShareAdapterMock([], []);
|
||||
adapter.setRows(fakeDataRows);
|
||||
adapter.setAllowFocusOnTableRows(allowFocus);
|
||||
dataTable.data = adapter;
|
||||
fixture.detectChanges();
|
||||
const rowElements = testingUtils.getAllByCSS('.adf-datatable-body adf-datatable-row');
|
||||
@@ -1816,11 +1778,20 @@ describe('Accessibility', () => {
|
||||
};
|
||||
|
||||
it('should set tabindex to null (disabled === true) on datatable-body rows when allowFocusOnRows is set to false in ShareDatatable adapter', () => {
|
||||
testAllowFocusOnRows(false, null);
|
||||
const adapter = new ShareAdapterMock([], []);
|
||||
adapter.setAllowFocusOnTableRows(false);
|
||||
testAllowFocusOnRows(null, adapter);
|
||||
});
|
||||
|
||||
it('should set tabindex to 0 (disabled === false) on datatable-body rows when allowFocusOnRows is set to true in ShareDatatable adapter (default case)', () => {
|
||||
testAllowFocusOnRows(true, '0');
|
||||
it('should set tabindex to 0 (disabled === false) on datatable-body rows when allowFocusOnRows is not set explicitly in ShareDatatable adapter and falls back to default value ', () => {
|
||||
const adapter = new ShareAdapterMock([], []);
|
||||
adapter.setAllowFocusOnTableRows(true);
|
||||
testAllowFocusOnRows('0', adapter);
|
||||
});
|
||||
|
||||
it('should set tabindex to 0 (disabled === false) by default on datatable-body rows when allowFocusOnRows is not defined in Datatable adapter (fallback case)', () => {
|
||||
const adapter = new ObjectDataTableAdapter([], []);
|
||||
testAllowFocusOnRows('0', adapter);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
[style.border-radius]="roundUpChips ? '20px' : '10px'"
|
||||
[style.font-weight]="'bold'"
|
||||
role="listitem"
|
||||
tabIndex="0"
|
||||
[attr.aria-label]="chip.name"
|
||||
(removed)="removedChip.emit(chip.id)">
|
||||
<span id="adf-dynamic-chip-list-chip-name-{{ idx }}">{{ chip.name }}</span>
|
||||
|
||||
Reference in New Issue
Block a user