Skip to content

Commit 3d10040

Browse files
committed
fix(grid-column-groups, pivot): render a hidden row w/ headers for a11y
1 parent 7e40671 commit 3d10040

9 files changed

+111
-8
lines changed

projects/igniteui-angular/src/lib/grids/cell.component.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -517,11 +517,7 @@ export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy, CellT
517517
/** @hidden @internal */
518518
@HostBinding('attr.aria-describedby')
519519
public get ariaDescribeBy() {
520-
let describeBy = (this.gridID + '_' + this.column.field).replace('.', '_');
521-
if (this.isInvalid) {
522-
describeBy += ' ' + this.ariaErrorMessage;
523-
}
524-
return describeBy;
520+
return this.isInvalid ? this.ariaErrorMessage : null;
525521
}
526522

527523
/** @hidden @internal */

projects/igniteui-angular/src/lib/grids/grid/column-group.spec.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,63 @@ describe('IgxGrid - multi-column headers #grid', () => {
145145

146146
}));
147147

148+
it('Should render a hidden row of the leaf column headers for accessibility purposes.', fakeAsync(() => {
149+
fixture = TestBed.createComponent(BlueWhaleGridComponent) as ComponentFixture<BlueWhaleGridComponent>;
150+
(fixture as ComponentFixture<BlueWhaleGridComponent>).componentInstance.firstGroupRepeats = 0;
151+
(fixture as ComponentFixture<BlueWhaleGridComponent>).componentInstance.secondGroupRepeats = 0;
152+
tick();
153+
fixture.detectChanges();
154+
155+
grid = fixture.componentInstance.grid;
156+
const gridHeader = GridFunctions.getGridHeader(grid);
157+
158+
const groupHeaderEls = Array.from(gridHeader.nativeElement.querySelectorAll('.' + GRID_COL_GROUP_THEAD_TITLE_CLASS));
159+
for (const header of groupHeaderEls) {
160+
expect(header.getAttribute('aria-hidden')).toBe('true');
161+
}
162+
163+
const columnHeaders = GridFunctions.getColumnHeaders(fixture);
164+
for (const header of columnHeaders) {
165+
expect(header.nativeNode.getAttribute('aria-hidden')).toBe('true');
166+
}
167+
168+
const hiddenRow = gridHeader.nativeElement.querySelectorAll('[role="row"]')[1];
169+
const horizontalVirtualization = grid.rowList.first.virtDirRow;
170+
const chunkSize = horizontalVirtualization.state.chunkSize;
171+
172+
expect(hiddenRow.children.length).toBeLessThanOrEqual(chunkSize);
173+
expect(grid.columns.length).toBeGreaterThan(chunkSize);
174+
175+
expect(hiddenRow.children[0].textContent).toBe('ID');
176+
expect(hiddenRow.children[1].textContent).toBe('Company Name');
177+
expect(hiddenRow.children[2].textContent).toBe('ContactName');
178+
expect(hiddenRow.children[3].textContent).toBe('ContactTitle');
179+
expect(hiddenRow.children[4].textContent).toBe('Country');
180+
expect(hiddenRow.children[5].textContent).toBe('Region');
181+
expect(hiddenRow.children[6].textContent).toBe('City');
182+
}));
183+
184+
it('The hidden row of the leaf columns contains only headers of the rendered cells', fakeAsync(() => {
185+
fixture = TestBed.createComponent(BlueWhaleGridComponent) as ComponentFixture<BlueWhaleGridComponent>;
186+
tick();
187+
fixture.detectChanges();
188+
189+
grid = fixture.componentInstance.grid;
190+
const horizontalVirtualization = grid.rowList.first.virtDirRow;
191+
const chunkSize = horizontalVirtualization.state.chunkSize;
192+
193+
tick();
194+
fixture.detectChanges();
195+
196+
const gridHeader = GridFunctions.getGridHeader(grid);
197+
const hiddenRow = gridHeader.nativeElement.querySelectorAll('[role="row"]')[1];
198+
199+
expect(hiddenRow.children.length).toBeLessThanOrEqual(chunkSize);
200+
for (const ariaHeader of Array.from(hiddenRow.children)) {
201+
expect(ariaHeader.textContent).toBe('ID');
202+
}
203+
}));
204+
148205
it('Should render dynamic column group header correctly (#12165).', () => {
149206
fixture = TestBed.createComponent(BlueWhaleGridComponent) as ComponentFixture<BlueWhaleGridComponent>;
150207
(fixture as ComponentFixture<BlueWhaleGridComponent>).componentInstance.firstGroupRepeats = 1;

projects/igniteui-angular/src/lib/grids/grid/grid.pinning.spec.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -577,8 +577,6 @@ describe('IgxGrid - Column Pinning #grid', () => {
577577

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

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

701698
// check correct headers have left border

projects/igniteui-angular/src/lib/grids/headers/grid-header-group.component.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
}
4343
<div class="igx-grid-thead__title"
4444
role="columnheader"
45+
[attr.aria-hidden]="ariaHidden"
4546
[attr.aria-label]="column.header || column.field"
4647
[attr.aria-expanded]="column.expanded"
4748
[attr.aria-selected]="column.selected"
@@ -97,6 +98,7 @@
9798
}
9899
<igx-grid-header
99100
role="columnheader"
101+
[attr.aria-hidden]="ariaHidden"
100102
class="igx-grid-th--fw"
101103
[id]="grid.id + '_' + column.field"
102104
[ngClass]="column.headerClasses"

projects/igniteui-angular/src/lib/grids/headers/grid-header-group.component.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,13 @@ export class IgxGridHeaderGroupComponent implements DoCheck {
158158
return Z_INDEX - this.grid.pinnedColumns.indexOf(this.column);
159159
}
160160

161+
/**
162+
* @hidden
163+
*/
164+
public get ariaHidden(): boolean {
165+
return this.grid.hasColumnGroups && (this.column.hidden || this.grid.navigation.activeNode.row !== -1);
166+
}
167+
161168
/**
162169
* Gets whether the header group belongs to a column that is filtered.
163170
*

projects/igniteui-angular/src/lib/grids/headers/grid-header-row.component.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,15 @@
106106
}
107107
</div>
108108

109+
<!-- Render a hidden row of the leaf column headers for accessibility purposes -->
110+
@if (grid.hasColumnGroups) {
111+
<div role="row" style="width: 0; height: 0; position: absolute; top: -10000px;">
112+
@for (column of visibleLeafColumns; track column) {
113+
<div role="columnheader" [attr.aria-hidden]="isLeafHeaderAriaHidden">{{ column.header || column.field }}</div>
114+
}
115+
</div>
116+
}
117+
109118
<!-- Filter row area -->
110119
@if (grid.filteringService.isFilterRowVisible) {
111120
<igx-grid-filtering-row #filteringRow

projects/igniteui-angular/src/lib/grids/headers/grid-header-row.component.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,26 @@ export class IgxGridHeaderRowComponent implements DoCheck {
9393
return this.groups.map(group => group.filter);
9494
}
9595

96+
/**
97+
* Gets a list of all visible leaf columns in the grid.
98+
*
99+
* @hidden @internal
100+
*/
101+
public get visibleLeafColumns(): ColumnType[] {
102+
const row = this.grid.gridAPI.get_row_by_index(this.grid.rowList.first?.index || 0);
103+
if (row && row.cells) {
104+
return row.cells.map(cell => cell.column);
105+
}
106+
}
107+
108+
/**
109+
* @hidden
110+
* @internal
111+
*/
112+
public get isLeafHeaderAriaHidden(): boolean {
113+
return this.grid.navigation.activeNode.row === -1;
114+
}
115+
96116
/** The virtualized part of the header row containing the unpinned header groups. */
97117
@ViewChild('headerVirtualContainer', { read: IgxGridForOfDirective, static: true })
98118
public headerContainer: IgxGridForOfDirective<ColumnType, ColumnType[]>;

projects/igniteui-angular/src/lib/grids/pivot-grid/pivot-header-row.component.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,13 @@
310310
}
311311
}
312312
</div>
313+
314+
<!-- Render a hidden row of the leaf column headers for accessibility purposes -->
315+
<div role="row" style="width: 0; height: 0; position: absolute; top: -10000px;">
316+
@for (column of visibleLeafColumns; track column) {
317+
<div role="columnheader" [attr.aria-hidden]="isLeafHeaderAriaHidden">{{ column.header || column.field }}</div>
318+
}
319+
</div>
313320
</div>
314321
</div>
315322
<!-- Header thumb area -->

projects/igniteui-angular/src/lib/grids/pivot-grid/pivot-header-row.component.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,14 @@ export class IgxPivotHeaderRowComponent extends IgxGridHeaderRowComponent implem
203203
return this.totalDepth * this.grid.renderedRowHeight;
204204
}
205205

206+
/**
207+
* @hidden
208+
* @internal
209+
*/
210+
public override get isLeafHeaderAriaHidden(): boolean {
211+
return super.isLeafHeaderAriaHidden || this.grid.navigation.isRowHeaderActive || this.grid.navigation.isRowDimensionHeaderActive;
212+
}
213+
206214
/**
207215
* @hidden
208216
* @internal

0 commit comments

Comments
 (0)