[ACS-12582] [ADW] Document list container is not accessible via keyboard navigation for file selection (#12210)

This commit is contained in:
Dominik Iwanek
2026-09-09 14:20:24 +02:00
committed by GitHub
parent 27c700165c
commit e28267621a
5 changed files with 80 additions and 13 deletions
@@ -87,12 +87,26 @@ describe('DataTableRowComponent', () => {
expect(fixture.debugElement.nativeElement.getAttribute('tabindex')).toBeNull();
});
it('should not set tabindex when row is disabled', () => {
it('should set tabindex as focusable only with the arrow keys when row is enabled but not active', () => {
component.disabled = false;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement.getAttribute('tabindex')).toBe('-1');
});
it('should set tabindex as focusable with the Tab key when row is enabled and active', () => {
component.disabled = false;
component.active = true;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement.getAttribute('tabindex')).toBe('0');
});
it('should not set tabindex when row is disabled even if it is active', () => {
component.disabled = true;
component.active = true;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement.getAttribute('tabindex')).toBeNull();
});
it('should focus element', () => {
expect(document.activeElement.classList.contains('adf-datatable-row')).toBe(false);
component.disabled = false;
@@ -35,6 +35,8 @@ export class DataTableRowComponent implements FocusableOption {
@Input() disabled = true;
@Input() active = false;
@Output()
select: EventEmitter<any> = new EventEmitter<any>();
@@ -65,7 +67,10 @@ export class DataTableRowComponent implements FocusableOption {
@HostBinding('attr.tabindex')
get tabindex(): number | null {
return this.disabled ? null : 0;
if (this.disabled) {
return null;
}
return this.active ? 0 : -1;
}
@HostListener('keydown.space', ['$event'])
@@ -262,7 +262,9 @@
[class.adf-datatable-row__dragging]="isDraggingRow"
[attr.data-automation-id]="'datatable-row-' + idx"
(contextmenu)="markRowAsContextMenuSource(row)"
[disabled]="!(enableDragRows || multiselect)"
(focus)="onRowFocus(idx)"
[disabled]="!isRowKeyboardNavigable()"
[active]="isRowActive(idx)"
[attr.aria-description]="
(multiselect ? ('ADF-DATATABLE.ACCESSIBILITY.ROW_SELECTION' | translate) : '') +
(multiselect && enableDragRows ? '. ' : '') +
@@ -1881,10 +1881,10 @@ describe('Accessibility', () => {
const getBodyRows = (): DebugElement[] => testingUtils.getAllByCSS(rowSelector);
const expectRowsTabindex = (expected: string | null): void => {
const expectRowsTabindex = (expected: (string | null)[]): void => {
const rowElements = getBodyRows();
expect(rowElements.length).toBeGreaterThan(0);
expect(rowElements.every((row) => row.nativeElement.getAttribute('tabindex') === expected)).toBeTrue();
expect(rowElements.length).toBe(expected.length);
expect(rowElements.map((row) => row.nativeElement.getAttribute('tabindex'))).toEqual(expected);
};
const activateRow = (rowIndex: number): void => {
@@ -1907,24 +1907,42 @@ describe('Accessibility', () => {
dataTable.data = new ObjectDataTableAdapter([], [new ObjectDataColumn({ key: 'name' })]);
});
it('should set tabindex to null (disabled === true) on datatable-body rows when neither multiselect nor enableDragRows is enabled', () => {
it('should set tabindex to null (disabled === true) on datatable-body rows when rows cannot be selected nor dragged', () => {
dataTable.selectionMode = 'none';
setRows();
expectRowsTabindex(null);
expectRowsTabindex([null, null]);
});
it('should set tabindex to 0 (disabled === false) on datatable-body rows when multiselect is enabled', () => {
it('should make only the first row reachable with the Tab key when rows are selectable', () => {
setRows();
expectRowsTabindex(['0', '-1']);
});
it('should make only the first row reachable with the Tab key when multiselect is enabled', () => {
dataTable.multiselect = true;
setRows();
expectRowsTabindex('0');
expectRowsTabindex(['0', '-1']);
});
it('should set tabindex to 0 (disabled === false) on datatable-body rows when enableDragRows is enabled', () => {
it('should make only the first row reachable with the Tab key when enableDragRows is enabled', () => {
dataTable.enableDragRows = true;
setRows();
expectRowsTabindex('0');
expectRowsTabindex(['0', '-1']);
});
it('should move the tabindex to the active row', () => {
setRows();
dataTable.ngAfterViewInit();
activateRow(1);
testingUtils.setDebugElement(fixture.debugElement);
fixture.detectChanges();
expectRowsTabindex(['-1', '0']);
});
it('should focus next row on ArrowDown event', () => {
@@ -1954,6 +1972,17 @@ describe('Accessibility', () => {
expect(document.activeElement?.getAttribute('data-automation-id')).toBe('datatable-row-0');
});
it('should navigate between rows with the arrow keys in single selection mode', () => {
dataTable.selectionMode = 'single';
setRows();
dataTable.ngAfterViewInit();
activateRow(1);
dispatchKeyUp(event);
expect(document.activeElement?.getAttribute('data-automation-id')).toBe('datatable-row-0');
});
});
describe('Row cells focus management', () => {
@@ -347,6 +347,10 @@ export class DataTableComponent implements OnInit, AfterContentInit, OnChanges,
private readonly destroyRef = inject(DestroyRef);
private get rowsOffset(): number {
return this.isHeaderVisible() ? 1 : 0;
}
@HostListener('keyup', ['$event'])
onKeydown(event: KeyboardEvent): void {
if (event.shiftKey && this.enableDragRows) {
@@ -640,7 +644,7 @@ export class DataTableComponent implements OnInit, AfterContentInit, OnChanges,
}
if (row) {
const rowIndex = this.data.getRows().indexOf(row) + (this.isHeaderVisible() ? 1 : 0);
const rowIndex = this.data.getRows().indexOf(row) + this.rowsOffset;
this.keyManager.setActiveItem(rowIndex);
const dataRowEvent = new DataRowEvent(row, mouseEvent, this);
@@ -923,6 +927,19 @@ export class DataTableComponent implements OnInit, AfterContentInit, OnChanges,
return this.selectionMode && this.selectionMode.toLowerCase() === 'multiple';
}
protected isRowKeyboardNavigable(): boolean {
return this.enableDragRows || this.multiselect || this.isSingleSelectionMode() || this.isMultiSelectionMode();
}
protected isRowActive(rowIndex: number): boolean {
const activeItemIndex = this.keyManager?.activeItemIndex ?? -1;
return activeItemIndex < this.rowsOffset ? rowIndex === 0 : activeItemIndex === rowIndex + this.rowsOffset;
}
protected onRowFocus(rowIndex: number): void {
this.keyManager?.updateActiveItem(rowIndex + this.rowsOffset);
}
getRowStyle(row: DataRow): string {
row.cssClass = row.cssClass ? row.cssClass : '';
this.rowStyleClass = this.rowStyleClass ? this.rowStyleClass : '';