From e01cd40b1f28edc367a64aea6a746f4bf9bb8e30 Mon Sep 17 00:00:00 2001 From: Anton Ramanovich <90370279+rmnvch@users.noreply.github.com> Date: Wed, 14 Jan 2026 12:43:29 +0100 Subject: [PATCH] [ACS-10205]: Extends DataTableAdapter interface to handle focus for accessibility (#11506) * [ACS-10205]: updates base DataTableAdapter class with optional props * [ACS-10205]: updates ShareDataTableAdapter with new props for focus handling * [ACS-10205]: bind new focus props to template * [ACS-10205]: uts update * [ACS-10205]: uts refactor * [ACS-10205]: fixes circullar deps --- .../data/share-datatable-adapter.spec.ts | 12 +++ .../data/share-datatable-adapter.ts | 7 +- .../datatable/datatable.component.html | 6 +- .../datatable/datatable.component.spec.ts | 80 ++++++++++++++----- .../lib/datatable/data/datatable-adapter.ts | 2 + 5 files changed, 83 insertions(+), 24 deletions(-) diff --git a/lib/content-services/src/lib/document-list/data/share-datatable-adapter.spec.ts b/lib/content-services/src/lib/document-list/data/share-datatable-adapter.spec.ts index 86919182a7..22944eca39 100644 --- a/lib/content-services/src/lib/document-list/data/share-datatable-adapter.spec.ts +++ b/lib/content-services/src/lib/document-list/data/share-datatable-adapter.spec.ts @@ -493,4 +493,16 @@ describe('ShareDataTableAdapter', () => { expect(adapter.getRowByNodeId('fake-node-id-2')).toEqual(fakeShareDataRows[1]); }); }); + + it('should initialize with allowFocusOnRows as true by default', () => { + const adapter = new ShareDataTableAdapter(thumbnailService, contentService, null); + expect(adapter.allowFocusOnRows).toBe(true); + }); + + it('should set allowFocusOnRows value', () => { + const adapter = new ShareDataTableAdapter(thumbnailService, contentService, null); + expect(adapter.allowFocusOnRows).toBe(true); + adapter.setAllowFocusOnTableRows(false); + expect(adapter.allowFocusOnRows).toBe(false); + }); }); diff --git a/lib/content-services/src/lib/document-list/data/share-datatable-adapter.ts b/lib/content-services/src/lib/document-list/data/share-datatable-adapter.ts index 5549849488..78911da92a 100644 --- a/lib/content-services/src/lib/document-list/data/share-datatable-adapter.ts +++ b/lib/content-services/src/lib/document-list/data/share-datatable-adapter.ts @@ -34,10 +34,11 @@ export class ShareDataTableAdapter implements DataTableAdapter { private filter: RowFilter; private imageResolver: any; - thumbnails: boolean = false; + thumbnails = false; permissionsStyle: PermissionStyleModel[]; selectedRow: DataRow; allowDropFiles: boolean; + allowFocusOnRows = true; set sortingMode(value: string) { let newValue = (value || 'client').toLowerCase(); @@ -196,6 +197,10 @@ export class ShareDataTableAdapter implements DataTableAdapter { this.imageResolver = resolver; } + setAllowFocusOnTableRows(allow: boolean) { + this.allowFocusOnRows = allow; + } + private getFolderIcon(node: any) { if (this.isSmartFolder(node)) { return this.thumbnailService.getMimeTypeIcon('smartFolder'); diff --git a/lib/core/src/lib/datatable/components/datatable/datatable.component.html b/lib/core/src/lib/datatable/components/datatable/datatable.component.html index 186ee5a7e9..cce0da9956 100644 --- a/lib/core/src/lib/datatable/components/datatable/datatable.component.html +++ b/lib/core/src/lib/datatable/components/datatable/datatable.component.html @@ -206,7 +206,9 @@ [ngClass]="getRowStyle(row)" [class.adf-datatable-row__dragging]="isDraggingRow" [attr.data-automation-id]="'datatable-row-' + idx" - (contextmenu)="markRowAsContextMenuSource(row)"> + (contextmenu)="markRowAsContextMenuSource(row)" + [disabled]="!(data.allowFocusOnRows ?? true)" + >
{ }); }); - it('should remove cell focus when [focus] is set to false', () => { - dataTable.showHeader = ShowHeaderMode.Never; - const dataRows = [{ name: 'name1' }]; + describe('DataTable row focus management', () => { + const testFocus = (focus: boolean, selector: string, expectedTabindex: string | null) => { + dataTable.showHeader = ShowHeaderMode.Never; + const dataRows = [{ name: 'name1' }]; - dataTable.data = new ObjectDataTableAdapter([], [new ObjectDataColumn({ key: 'name', template: columnCustomTemplate, focus: false })]); + dataTable.data = new ObjectDataTableAdapter([], [new ObjectDataColumn({ key: 'name', template: columnCustomTemplate, focus })]); - dataTable.ngOnChanges({ - rows: new SimpleChange(null, dataRows, false) + dataTable.ngOnChanges({ + rows: new SimpleChange(null, dataRows, false) + }); + + fixture.detectChanges(); + dataTable.ngAfterViewInit(); + + const element = testingUtils.getByCSS(selector); + 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'; + + it('should remove cell focus when [focus] is set to false', () => { + testFocus(false, cellaValSelector, null); }); - fixture.detectChanges(); - dataTable.ngAfterViewInit(); + it('should allow element focus when [focus] is set to true', () => { + testFocus(true, cellaValSelector, '0'); + }); - const cell = testingUtils.getByCSS('.adf-datatable-row[data-automation-id="datatable-row-0"] .adf-cell-value'); - expect(cell?.nativeElement.getAttribute('tabindex')).toBe(null); + it('should remove col focus when [focus] is set to false', () => { + testFocus(false, cellWrapperSelector, null); + }); + + it('should allow col focus when [focus] is set to true', () => { + testFocus(true, cellWrapperSelector, '0'); + }); }); - it('should allow element focus when [focus] is set to true', () => { - dataTable.showHeader = ShowHeaderMode.Never; - const dataRows = [{ name: 'name1' }]; + describe('ShareDatatable adapter allowFocusOnRows', () => { + class ShareAdapterMock extends ObjectDataTableAdapter { + public allowFocusOnRows = true; - dataTable.data = new ObjectDataTableAdapter([], [new ObjectDataColumn({ key: 'name', template: columnCustomTemplate, focus: true })]); + constructor(data: any[], schema: DataColumn[]) { + super(data, schema); + } - dataTable.ngOnChanges({ - rows: new SimpleChange(null, dataRows, false) + setAllowFocusOnTableRows(allow: boolean) { + this.allowFocusOnRows = allow; + } + } + const testAllowFocusOnRows = (allowFocus: boolean, expectedTabindex: string | null) => { + 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'); + expect(rowElements.length).toBeGreaterThan(0); + expect(rowElements.every((row) => row.nativeElement.getAttribute('tabindex') === expectedTabindex)).toBeTrue(); + }; + + it('should set tabindex to null (disabled === true) on datatable-body rows when allowFocusOnRows is set to false in ShareDatatable adapter', () => { + testAllowFocusOnRows(false, null); }); - fixture.detectChanges(); - dataTable.ngAfterViewInit(); - - const cell = testingUtils.getByCSS('.adf-datatable-row[data-automation-id="datatable-row-0"] .adf-cell-value'); - expect(cell?.nativeElement.getAttribute('tabindex')).toBe('0'); + 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 create focus trap on main menu open', () => { diff --git a/lib/core/src/lib/datatable/data/datatable-adapter.ts b/lib/core/src/lib/datatable/data/datatable-adapter.ts index 1284e16e79..a8aaa6305f 100644 --- a/lib/core/src/lib/datatable/data/datatable-adapter.ts +++ b/lib/core/src/lib/datatable/data/datatable-adapter.ts @@ -22,6 +22,7 @@ import { Subject } from 'rxjs'; export interface DataTableAdapter { rowsChanged?: Subject>; + allowFocusOnRows?: boolean; selectedRow: DataRow; getRows(): Array; @@ -33,4 +34,5 @@ export interface DataTableAdapter { getSorting(): DataSorting; setSorting(sorting: DataSorting): void; sort(key?: string, direction?: string): void; + setAllowFocusOnTableRows?(allow: boolean): void; }