Skip to content

Commit 2c7bc1c

Browse files
committedFeb 20, 2019
fix(table): add missing rowgroup roles (#15131)
The native cdk-table adds thead, tbody, and tfoot elements but was omitting the "rowgroup" role from these. This is necessary since we also specifically add roles to all of the other table elements. This also marks thead and tfoot as `display: none` when there are no corresponding rows.
1 parent e916a5c commit 2c7bc1c

File tree

2 files changed

+89
-15
lines changed

2 files changed

+89
-15
lines changed
 

‎src/cdk/table/table.spec.ts

+65-10
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,32 @@ describe('CdkTable', () => {
505505
]);
506506
});
507507

508+
it('should apply correct roles for native table elements', () => {
509+
const thisFixture = createComponent(NativeHtmlTableApp);
510+
const thisTableElement: HTMLTableElement = thisFixture.nativeElement.querySelector('table');
511+
thisFixture.detectChanges();
512+
513+
const rowGroups = Array.from(thisTableElement.querySelectorAll('thead, tbody, tfoot'));
514+
expect(rowGroups.length).toBe(3, 'Expected table to have a thead, tbody, and tfoot');
515+
for (const group of rowGroups) {
516+
expect(group.getAttribute('role'))
517+
.toBe('rowgroup', 'Expected thead, tbody, and tfoot to have role="rowgroup"');
518+
}
519+
});
520+
521+
it('should hide thead/tfoot when there are no header/footer rows', () => {
522+
const thisFixture = createComponent(NativeTableWithNoHeaderOrFooterRows);
523+
const thisTableElement: HTMLTableElement = thisFixture.nativeElement.querySelector('table');
524+
thisFixture.detectChanges();
525+
526+
const rowGroups: HTMLElement[] = Array.from(thisTableElement.querySelectorAll('thead, tfoot'));
527+
expect(rowGroups.length).toBe(2, 'Expected table to have a thead and tfoot');
528+
for (const group of rowGroups) {
529+
expect(group.style.display)
530+
.toBe('none', 'Expected thead and tfoot to be `display: none`');
531+
}
532+
});
533+
508534
it('should render cells even if row data is falsy', () => {
509535
setupTableTestApp(BooleanRowCdkTableApp);
510536
expectTableToMatchContent(tableElement, [
@@ -1591,27 +1617,27 @@ class MultipleHeaderFooterRowsCdkTableApp {
15911617
15921618
<ng-container cdkColumnDef="index1Column">
15931619
<cdk-header-cell *cdkHeaderCellDef> Column C</cdk-header-cell>
1594-
<cdk-cell *cdkCellDef="let row"> index_1_special_row</cdk-cell>
1620+
<cdk-cell *cdkCellDef> index_1_special_row</cdk-cell>
15951621
</ng-container>
15961622
15971623
<ng-container cdkColumnDef="c3Column">
15981624
<cdk-header-cell *cdkHeaderCellDef> Column C</cdk-header-cell>
1599-
<cdk-cell *cdkCellDef="let row"> c3_special_row</cdk-cell>
1625+
<cdk-cell *cdkCellDef> c3_special_row</cdk-cell>
16001626
</ng-container>
16011627
16021628
<ng-container cdkColumnDef="index">
16031629
<cdk-header-cell *cdkHeaderCellDef> Index</cdk-header-cell>
1604-
<cdk-cell *cdkCellDef="let row; let index = index"> {{index}}</cdk-cell>
1630+
<cdk-cell *cdkCellDef="let index = index"> {{index}}</cdk-cell>
16051631
</ng-container>
16061632
16071633
<ng-container cdkColumnDef="dataIndex">
16081634
<cdk-header-cell *cdkHeaderCellDef> Data Index</cdk-header-cell>
1609-
<cdk-cell *cdkCellDef="let row; let dataIndex = dataIndex"> {{dataIndex}}</cdk-cell>
1635+
<cdk-cell *cdkCellDef="let dataIndex = dataIndex"> {{dataIndex}}</cdk-cell>
16101636
</ng-container>
16111637
16121638
<ng-container cdkColumnDef="renderIndex">
16131639
<cdk-header-cell *cdkHeaderCellDef> Render Index</cdk-header-cell>
1614-
<cdk-cell *cdkCellDef="let row; let renderIndex = renderIndex"> {{renderIndex}}</cdk-cell>
1640+
<cdk-cell *cdkCellDef="let renderIndex = renderIndex"> {{renderIndex}}</cdk-cell>
16151641
</ng-container>
16161642
16171643
<cdk-header-row *cdkHeaderRowDef="columnsToRender"></cdk-header-row>
@@ -1662,12 +1688,12 @@ class WhenRowCdkTableApp {
16621688
16631689
<ng-container cdkColumnDef="index1Column">
16641690
<cdk-header-cell *cdkHeaderCellDef> Column C</cdk-header-cell>
1665-
<cdk-cell *cdkCellDef="let row"> index_1_special_row </cdk-cell>
1691+
<cdk-cell *cdkCellDef> index_1_special_row </cdk-cell>
16661692
</ng-container>
16671693
16681694
<ng-container cdkColumnDef="c3Column">
16691695
<cdk-header-cell *cdkHeaderCellDef> Column C</cdk-header-cell>
1670-
<cdk-cell *cdkCellDef="let row"> c3_special_row </cdk-cell>
1696+
<cdk-cell *cdkCellDef> c3_special_row </cdk-cell>
16711697
</ng-container>
16721698
16731699
<cdk-header-row *cdkHeaderRowDef="columnsToRender"></cdk-header-row>
@@ -1705,12 +1731,12 @@ class WhenRowWithoutDefaultCdkTableApp {
17051731
17061732
<ng-container cdkColumnDef="index1Column">
17071733
<cdk-header-cell *cdkHeaderCellDef> Column C</cdk-header-cell>
1708-
<cdk-cell *cdkCellDef="let row"> index_1_special_row </cdk-cell>
1734+
<cdk-cell *cdkCellDef> index_1_special_row </cdk-cell>
17091735
</ng-container>
17101736
17111737
<ng-container cdkColumnDef="c3Column">
17121738
<cdk-header-cell *cdkHeaderCellDef> Column C</cdk-header-cell>
1713-
<cdk-cell *cdkCellDef="let row"> c3_special_row </cdk-cell>
1739+
<cdk-cell *cdkCellDef> c3_special_row </cdk-cell>
17141740
</ng-container>
17151741
17161742
<cdk-header-row *cdkHeaderRowDef="columnsToRender"></cdk-header-row>
@@ -1790,7 +1816,7 @@ class TrackByCdkTableApp {
17901816
[sticky]="isStuck(stickyStartColumns, column)"
17911817
[stickyEnd]="isStuck(stickyEndColumns, column)">
17921818
<cdk-header-cell *cdkHeaderCellDef> Header {{column}} </cdk-header-cell>
1793-
<cdk-cell *cdkCellDef="let row"> {{column}} </cdk-cell>
1819+
<cdk-cell *cdkCellDef>{{column}}</cdk-cell>
17941820
<cdk-footer-cell *cdkFooterCellDef> Footer {{column}} </cdk-footer-cell>
17951821
</ng-container>
17961822
@@ -2226,6 +2252,35 @@ class NativeHtmlTableApp {
22262252
@ViewChild(CdkTable) table: CdkTable<TestData>;
22272253
}
22282254

2255+
@Component({
2256+
template: `
2257+
<table cdk-table [dataSource]="dataSource">
2258+
<ng-container cdkColumnDef="column_a">
2259+
<th cdk-header-cell *cdkHeaderCellDef> Column A</th>
2260+
<td cdk-cell *cdkCellDef="let row"> {{row.a}}</td>
2261+
</ng-container>
2262+
2263+
<ng-container cdkColumnDef="column_b">
2264+
<th cdk-header-cell *cdkHeaderCellDef> Column B</th>
2265+
<td cdk-cell *cdkCellDef="let row"> {{row.b}}</td>
2266+
</ng-container>
2267+
2268+
<ng-container cdkColumnDef="column_c">
2269+
<th cdk-header-cell *cdkHeaderCellDef> Column C</th>
2270+
<td cdk-cell *cdkCellDef="let row"> {{row.c}}</td>
2271+
</ng-container>
2272+
2273+
<tr cdk-row *cdkRowDef="let row; columns: columnsToRender" class="customRowClass"></tr>
2274+
</table>
2275+
`
2276+
})
2277+
class NativeTableWithNoHeaderOrFooterRows {
2278+
dataSource: FakeDataSource | undefined = new FakeDataSource();
2279+
columnsToRender = ['column_a', 'column_b', 'column_c'];
2280+
2281+
@ViewChild(CdkTable) table: CdkTable<TestData>;
2282+
}
2283+
22292284
@Component({
22302285
template: `
22312286
<table cdk-table [dataSource]="dataSource">

‎src/cdk/table/table.ts

+24-5
Original file line numberDiff line numberDiff line change
@@ -579,11 +579,20 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
579579
* sticky input changes. May be called manually for cases where the cell content changes outside
580580
* of these events.
581581
*/
582-
updateStickyHeaderRowStyles() {
582+
updateStickyHeaderRowStyles(): void {
583583
const headerRows = this._getRenderedRows(this._headerRowOutlet);
584-
this._stickyStyler.clearStickyPositioning(headerRows, ['top']);
584+
const tableElement = this._elementRef.nativeElement as HTMLElement;
585+
586+
// Hide the thead element if there are no header rows. This is necessary to satisfy
587+
// overzealous a11y checkers that fail because the `rowgroup` element does not contain
588+
// required child `row`.
589+
const thead = tableElement.querySelector('thead');
590+
if (thead) {
591+
thead.style.display = headerRows.length ? '' : 'none';
592+
}
585593

586594
const stickyStates = this._headerRowDefs.map(def => def.sticky);
595+
this._stickyStyler.clearStickyPositioning(headerRows, ['top']);
587596
this._stickyStyler.stickRows(headerRows, stickyStates, 'top');
588597

589598
// Reset the dirty state of the sticky input change since it has been used.
@@ -597,11 +606,20 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
597606
* sticky input changes. May be called manually for cases where the cell content changes outside
598607
* of these events.
599608
*/
600-
updateStickyFooterRowStyles() {
609+
updateStickyFooterRowStyles(): void {
601610
const footerRows = this._getRenderedRows(this._footerRowOutlet);
602-
this._stickyStyler.clearStickyPositioning(footerRows, ['bottom']);
611+
const tableElement = this._elementRef.nativeElement as HTMLElement;
612+
613+
// Hide the tfoot element if there are no footer rows. This is necessary to satisfy
614+
// overzealous a11y checkers that fail because the `rowgroup` element does not contain
615+
// required child `row`.
616+
const tfoot = tableElement.querySelector('tfoot');
617+
if (tfoot) {
618+
tfoot.style.display = footerRows.length ? '' : 'none';
619+
}
603620

604621
const stickyStates = this._footerRowDefs.map(def => def.sticky);
622+
this._stickyStyler.clearStickyPositioning(footerRows, ['bottom']);
605623
this._stickyStyler.stickRows(footerRows, stickyStates, 'bottom');
606624
this._stickyStyler.updateStickyFooterContainer(this._elementRef.nativeElement, stickyStates);
607625

@@ -865,7 +883,7 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
865883
}
866884

867885
/** Gets the list of rows that have been rendered in the row outlet. */
868-
_getRenderedRows(rowOutlet: RowOutlet) {
886+
_getRenderedRows(rowOutlet: RowOutlet): HTMLElement[] {
869887
const renderedRows: HTMLElement[] = [];
870888

871889
for (let i = 0; i < rowOutlet.viewContainer.length; i++) {
@@ -983,6 +1001,7 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
9831001

9841002
for (const section of sections) {
9851003
const element = documentRef.createElement(section.tag);
1004+
element.setAttribute('role', 'rowgroup');
9861005
element.appendChild(section.outlet.elementRef.nativeElement);
9871006
documentFragment.appendChild(element);
9881007
}

0 commit comments

Comments
 (0)
Please sign in to comment.