Skip to content

fix(grid-column-groups, pivot): render a hidden row with headers for accessibility purposes #16019

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: 20.0.x
Choose a base branch
from
Draft
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: 1 addition & 5 deletions projects/igniteui-angular/src/lib/grids/cell.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -517,11 +517,7 @@ export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy, CellT
/** @hidden @internal */
@HostBinding('attr.aria-describedby')
public get ariaDescribeBy() {
let describeBy = (this.gridID + '_' + this.column.field).replace('.', '_');
if (this.isInvalid) {
describeBy += ' ' + this.ariaErrorMessage;
}
return describeBy;
return this.isInvalid ? this.ariaErrorMessage : null;
}

/** @hidden @internal */
Expand Down
59 changes: 59 additions & 0 deletions projects/igniteui-angular/src/lib/grids/grid/column-group.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,65 @@ describe('IgxGrid - multi-column headers #grid', () => {

}));

it('Should render a hidden row of the leaf column headers for accessibility purposes.', fakeAsync(() => {
fixture = TestBed.createComponent(BlueWhaleGridComponent) as ComponentFixture<BlueWhaleGridComponent>;
(fixture as ComponentFixture<BlueWhaleGridComponent>).componentInstance.firstGroupRepeats = 0;
(fixture as ComponentFixture<BlueWhaleGridComponent>).componentInstance.secondGroupRepeats = 0;
tick();
fixture.detectChanges();
tick();
fixture.detectChanges();

grid = fixture.componentInstance.grid;
const gridHeader = GridFunctions.getGridHeader(grid);

const groupHeaderEls = Array.from(gridHeader.nativeElement.querySelectorAll('.' + GRID_COL_GROUP_THEAD_TITLE_CLASS));
for (const header of groupHeaderEls) {
expect(header.getAttribute('aria-hidden')).toBe('true');
}

const columnHeaders = GridFunctions.getColumnHeaders(fixture);
for (const header of columnHeaders) {
expect(header.nativeNode.getAttribute('aria-hidden')).toBe('true');
}

const hiddenRow = gridHeader.nativeElement.querySelectorAll('[role="row"]')[1];
const horizontalVirtualization = grid.rowList.first.virtDirRow;
const chunkSize = horizontalVirtualization.state.chunkSize;

expect(hiddenRow.children.length).toBeLessThanOrEqual(chunkSize);
expect(grid.columns.length).toBeGreaterThan(chunkSize);

expect(hiddenRow.children[0].textContent).toBe('ID');
expect(hiddenRow.children[1].textContent).toBe('Company Name');
expect(hiddenRow.children[2].textContent).toBe('ContactName');
expect(hiddenRow.children[3].textContent).toBe('ContactTitle');
expect(hiddenRow.children[4].textContent).toBe('Country');
expect(hiddenRow.children[5].textContent).toBe('Region');
expect(hiddenRow.children[6].textContent).toBe('City');
}));

it('The hidden row of the leaf columns contains only headers of the rendered cells', fakeAsync(() => {
fixture = TestBed.createComponent(BlueWhaleGridComponent) as ComponentFixture<BlueWhaleGridComponent>;
tick();
fixture.detectChanges();

grid = fixture.componentInstance.grid;
const horizontalVirtualization = grid.rowList.first.virtDirRow;
const chunkSize = horizontalVirtualization.state.chunkSize;

tick();
fixture.detectChanges();

const gridHeader = GridFunctions.getGridHeader(grid);
const hiddenRow = gridHeader.nativeElement.querySelectorAll('[role="row"]')[1];

expect(hiddenRow.children.length).toBeLessThanOrEqual(chunkSize);
for (const ariaHeader of Array.from(hiddenRow.children)) {
expect(ariaHeader.textContent).toBe('ID');
}
}));

it('Should render dynamic column group header correctly (#12165).', () => {
fixture = TestBed.createComponent(BlueWhaleGridComponent) as ComponentFixture<BlueWhaleGridComponent>;
(fixture as ComponentFixture<BlueWhaleGridComponent>).componentInstance.firstGroupRepeats = 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -577,8 +577,6 @@ describe('IgxGrid - Column Pinning #grid', () => {

GridSelectionFunctions.verifyRowHasCheckbox(row);
expect(GridFunctions.getRowDisplayContainer(fix, 0)).toBeDefined();
expect(row.children[2].getAttribute('aria-describedby')).toBe(grid.id + '_CompanyName');
expect(row.children[3].getAttribute('aria-describedby')).toBe(grid.id + '_ContactName');

// check scrollbar DOM
const scrBarStartSection = fix.debugElement.query(By.css(`${GRID_SCROLL_CLASS}-start`));
Expand Down Expand Up @@ -695,7 +693,6 @@ describe('IgxGrid - Column Pinning #grid', () => {
for (let i = 0; i <= pinnedCols.length - 1; i++) {
const elem = row.children[i + 1];
expect(parseInt((elem as any).style.left, 10)).toBe(-330);
expect(elem.getAttribute('aria-describedby')).toBe(grid.id + '_' + pinnedCols[i].field);
}

// check correct headers have left border
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
}
<div class="igx-grid-thead__title"
role="columnheader"
[attr.aria-hidden]="ariaHidden"
[attr.aria-label]="column.header || column.field"
[attr.aria-expanded]="column.expanded"
[attr.aria-selected]="column.selected"
Expand Down Expand Up @@ -97,6 +98,7 @@
}
<igx-grid-header
role="columnheader"
[attr.aria-hidden]="ariaHidden"
class="igx-grid-th--fw"
[id]="grid.id + '_' + column.field"
[ngClass]="column.headerClasses"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,13 @@ export class IgxGridHeaderGroupComponent implements DoCheck {
return Z_INDEX - this.grid.pinnedColumns.indexOf(this.column);
}

/**
* @hidden
*/
public get ariaHidden(): boolean {
return this.grid.hasColumnGroups && (this.column.hidden || this.grid.navigation.activeNode.row !== -1);
}

/**
* Gets whether the header group belongs to a column that is filtered.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,15 @@
}
</div>

<!-- Render a hidden row of the leaf column headers for accessibility purposes -->
@if (grid.hasColumnGroups) {
<div role="row" style="width: 0; height: 0; position: absolute; top: -10000px;">
@for (column of visibleLeafColumns; track column.index) {
<div role="columnheader" [attr.aria-hidden]="isLeafHeaderAriaHidden">{{ column.header || column.field }}</div>
}
</div>
}

<!-- Filter row area -->
@if (grid.filteringService.isFilterRowVisible) {
<igx-grid-filtering-row #filteringRow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,26 @@ export class IgxGridHeaderRowComponent implements DoCheck {
return this.groups.map(group => group.filter);
}

/**
* Gets a list of all visible leaf columns in the grid.
*
* @hidden @internal
*/
public get visibleLeafColumns(): ColumnType[] {
const row = this.grid.gridAPI.get_row_by_index(this.grid.rowList.first?.index || 0);
if (row && row.cells) {
return row.cells.map(cell => cell.column);
}
}

/**
* @hidden
* @internal
*/
public get isLeafHeaderAriaHidden(): boolean {
return this.grid.navigation.activeNode.row === -1;
}

/** The virtualized part of the header row containing the unpinned header groups. */
@ViewChild('headerVirtualContainer', { read: IgxGridForOfDirective, static: true })
public headerContainer: IgxGridForOfDirective<ColumnType, ColumnType[]>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,13 @@
}
}
</div>

<!-- Render a hidden row of the leaf column headers for accessibility purposes -->
<div role="row" style="width: 0; height: 0; position: absolute; top: -10000px;">
@for (column of visibleLeafColumns; track column.index) {
<div role="columnheader" [attr.aria-hidden]="isLeafHeaderAriaHidden">{{ column.header || column.field }}</div>
}
</div>
</div>
</div>
<!-- Header thumb area -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,14 @@ export class IgxPivotHeaderRowComponent extends IgxGridHeaderRowComponent implem
return this.totalDepth * this.grid.renderedRowHeight;
}

/**
* @hidden
* @internal
*/
public override get isLeafHeaderAriaHidden(): boolean {
return super.isLeafHeaderAriaHidden || this.grid.navigation.isRowHeaderActive || this.grid.navigation.isRowDimensionHeaderActive;
}

/**
* @hidden
* @internal
Expand Down
Loading