Skip to content

Commit 1695eee

Browse files
authored
fix(ui5-table): aria relevant attributes are set (#12013)
- aria-rowcount/colcount/rowindex/colindex attributes are set - aria-hidden is set for the the growing row and navigated cell - aria-multiselectable custom announcement is added - custom announcement for the header row selection cell Fixes: #11639
1 parent 30777dc commit 1695eee

15 files changed

+158
-15
lines changed

packages/main/cypress/specs/Table.cy.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ describe("Table - Rendering", () => {
4141
cy.get("ui5-table-row").should("exist");
4242
cy.get("ui5-table-header-cell").should("have.length", 2);
4343
cy.get("ui5-table-header-row").should("have.attr", "aria-roledescription", "Column Header Row");
44+
cy.get("ui5-table-header-row").should("have.attr", "aria-rowindex", "1");
45+
cy.get("ui5-table-row").should("have.attr", "aria-rowindex", "2");
46+
cy.get("ui5-table-header-cell").first().should("have.attr", "aria-colindex", "1");
47+
cy.get("ui5-table-header-cell").last().should("have.attr", "aria-colindex", "2");
48+
49+
cy.get("#table").shadow().find("#table").as("innerTable");
50+
cy.get("@innerTable").should("have.attr", "role", "grid");
51+
cy.get("@innerTable").should("have.attr", "aria-colcount", "2");
52+
cy.get("@innerTable").should("have.attr", "aria-rowcount", "2");
4453
});
4554

4655
it("tests if initial empty table renders without errors", () => {
@@ -788,7 +797,9 @@ describe("Table - Horizontal Scrolling", () => {
788797
.shadow()
789798
.find("#navigated-cell")
790799
.should("have.css", "position", "sticky")
791-
.should("have.css", "right", "0px");
800+
.should("have.css", "right", "0px")
801+
.should("have.attr", "aria-hidden", "true")
802+
.should("have.attr", "data-excluded-from-navigation");
792803
});
793804

794805
it("selection column should be fixed to the left", () => {

packages/main/cypress/specs/TableGrowing.cy.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ describe("TableGrowing - Button", () => {
5151
cy.get("[ui5-table")
5252
.shadow()
5353
.find("#growing-row")
54-
.should("exist");
54+
.should("exist")
55+
.should("have.attr", "aria-hidden", "true");
5556

5657
cy.get("[ui5-table-growing]")
5758
.shadow()

packages/main/cypress/specs/TableRowActions.cy.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ describe("TableRowActions", () => {
3737
cy.get("@table").then($table => {
3838
$table[0].addEventListener("row-action-click", cy.stub().as("rowActionClick"));
3939
});
40+
cy.get("@innerTable").should("have.attr", "aria-colcount", "1");
4041
}
4142

4243
function checkTemplateColumn(expectedWidth: string) {

packages/main/cypress/specs/TableSelections.cy.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ const testConfig = {
8383
"mode": "Single",
8484
},
8585
"cases": {
86+
"ARIA_DESCRIPTION": "Single Selection Table",
8687
"BOXES": {
8788
"header": {
8889
"exists": true,
@@ -128,6 +129,7 @@ const testConfig = {
128129
"mode": "Multiple",
129130
},
130131
"cases": {
132+
"ARIA_DESCRIPTION": "Multi Selection Table",
131133
"BOXES": {
132134
"header": {
133135
"exists": true,
@@ -203,10 +205,18 @@ Object.entries(testConfig).forEach(([mode, testConfigEntry]) => {
203205
cy.get("@selection").then($selection => {
204206
$selection.get(0)?.addEventListener("change", cy.stub().as("selectionChangeSpy"));
205207
});
208+
cy.get("#table0").shadow().find("#table").as("innerTable");
209+
cy.get("@innerTable").should("have.attr", "aria-colcount", "5");
210+
});
211+
212+
it("sets the correct aria-description", () => {
213+
cy.get("@innerTable").should("have.attr", "aria-description", testConfigEntry.cases.ARIA_DESCRIPTION);
206214
});
207215

208216
it("Correct boxes are shown", () => {
209217
cy.get("@headerRow").shadow().find("#selection-cell")
218+
.should("have.attr", "aria-colindex", "1")
219+
.should("have.attr", "aria-label", "Selection")
210220
.should(testConfigEntry.cases.BOXES.header.exists ? "exist" : "not.exist");
211221
cy.get("@row0").shadow().find("#selection-cell")
212222
.should(testConfigEntry.cases.BOXES.row.exists ? "exist" : "not.exist");
@@ -314,6 +324,7 @@ Object.entries(testConfig).forEach(([mode, testConfigEntry]) => {
314324
cy.get("#selection").invoke("attr", "behavior", "RowWÓnly");
315325
cy.get("#table0").invoke("on", "row-click", cy.stub().as("rowClickSpy"));
316326
cy.get("#selection").invoke("on", "change", cy.stub().as("selectionChangeSpy"));
327+
cy.get("#table0").shadow().find("#table").should("have.attr", "aria-colcount", "4");
317328
});
318329

319330
it("renders neither selection cell nor selection component", () => {
@@ -366,6 +377,7 @@ describe("TableSelectionMulti", () => {
366377
});
367378

368379
it("updates the header row checkbox when rows are added or removed", () => {
380+
cy.get("@headerRowSelectionCell").should("have.attr", "aria-description", "Contains Select All Checkbox . Checked");
369381
cy.get("@headerRowSelectionCell").children().first().as("headerRowCheckBox");
370382
cy.get("@headerRowCheckBox").should("have.attr", "checked");
371383
cy.get("@headerRowCheckBox").should("have.attr", "title", "Deselect All Rows");
@@ -377,9 +389,11 @@ describe("TableSelectionMulti", () => {
377389
</ui5-table-row>`
378390
);
379391
});
392+
cy.get("@headerRowSelectionCell").should("have.attr", "aria-description", "Contains Select All Checkbox . Not Checked");
380393
cy.get("@headerRowCheckBox").should("not.have.attr", "checked");
381394
cy.get("@headerRowCheckBox").should("have.attr", "title", "Select All Rows");
382395
cy.get("#row3").invoke("remove");
396+
cy.get("@headerRowSelectionCell").should("have.attr", "aria-description", "Contains Select All Checkbox . Checked");
383397
cy.get("@headerRowCheckBox").should("have.attr", "checked");
384398
cy.get("@headerRowCheckBox").should("have.attr", "title", "Deselect All Rows");
385399
cy.get("#row2").invoke("remove");
@@ -389,13 +403,14 @@ describe("TableSelectionMulti", () => {
389403
});
390404

391405
it("should handle header-selector=ClearAll", () => {
392-
cy.get("#headerRow").shadow().find("#selection-cell").children().first().as("headerRowIcon");
406+
cy.get("@headerRowSelectionCell").children().first().as("headerRowIcon");
393407
function checkClearAll(hasSelection: boolean) {
394408
cy.get("@headerRowIcon").should("have.attr", "name", "clear-all");
395409
cy.get("@headerRowIcon").should("have.attr", "mode", "Decorative");
396410
cy.get("@headerRowIcon").should("have.attr", "show-tooltip");
397411
cy.get("@headerRowIcon").should("have.attr", "accessible-name", "Deselect All Rows");
398412
cy.get("@headerRowIcon").should("have.attr", "design", hasSelection ? "Default" : "NonInteractive");
413+
cy.get("@headerRowSelectionCell").should("have.attr", "aria-description", hasSelection ? "Contains Clear All Button" : "Contains Clear All Button . Disabled");
399414
}
400415

401416
cy.get("#selection").invoke("attr", "header-selector", "ClearAll");

packages/main/cypress/specs/TableVirtualizer.cy.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ describe("TableVirtualizer", () => {
5151
cy.get("[ui5-table-row]")
5252
.eq(i - start)
5353
.should("have.attr", "position", `${i}`)
54-
.should("have.attr", "aria-rowindex", `${i + 1}`)
54+
.should("have.attr", "aria-rowindex", `${i + 2}`)
5555
.find("ui5-table-cell")
5656
.should("have.text", `${i}`);
5757
}

packages/main/src/Table.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -662,8 +662,28 @@ class Table extends UI5Element {
662662
return getEffectiveAriaLabelText(this) || undefined;
663663
}
664664

665+
get _ariaDescription() {
666+
return this._getSelection()?.getAriaDescriptionForTable();
667+
}
668+
665669
get _ariaRowCount() {
666-
return this._getVirtualizer()?.rowCount || undefined;
670+
return this._getVirtualizer()?.rowCount || this.rows.length + 1;
671+
}
672+
673+
get _ariaColCount() {
674+
if (!this.headerRow[0]) {
675+
return 0;
676+
}
677+
678+
let ariaColCount = this.headerRow[0]._visibleCells.length;
679+
if (this._isRowSelectorRequired) {
680+
ariaColCount++;
681+
}
682+
if (this.rowActionCount > 0) {
683+
ariaColCount++;
684+
}
685+
686+
return ariaColCount;
667687
}
668688

669689
get _ariaMultiSelectable() {

packages/main/src/TableHeaderRow.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ class TableHeaderRow extends TableRowBase {
7777

7878
onEnterDOM(): void {
7979
super.onEnterDOM();
80-
this.setAttribute("aria-roledescription", TableRowBase.i18nBundle.getText(TABLE_COLUMN_HEADER_ROW));
80+
this.ariaRowIndex = "1";
81+
this.ariaRoleDescription = TableRowBase.i18nBundle.getText(TABLE_COLUMN_HEADER_ROW);
8182
}
8283

8384
onBeforeRendering() {
@@ -103,6 +104,10 @@ class TableHeaderRow extends TableRowBase {
103104
return (this._tableSelection as TableSelectionMulti).headerSelector === "ClearAll";
104105
}
105106

107+
get _selectionCellAriaDescription() {
108+
return this._tableSelection?.getAriaDescriptionForColumnHeader();
109+
}
110+
106111
get _i18nSelection() {
107112
return TableRowBase.i18nBundle.getText(TABLE_SELECTION);
108113
}

packages/main/src/TableHeaderRowTemplate.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ import ClearAll from "@ui5/webcomponents-icons/dist/clear-all.js";
66
import IconDesign from "./types/IconDesign.js";
77
import type TableHeaderRow from "./TableHeaderRow.js";
88

9-
export default function TableHeaderRowTemplate(this: TableHeaderRow) {
9+
export default function TableHeaderRowTemplate(this: TableHeaderRow, ariaColIndex: number = 1) {
1010
return (
1111
<>
1212
{ this._hasSelector &&
1313
<TableHeaderCell id="selection-cell"
1414
aria-selected={this._isSelected}
1515
aria-label={this._i18nSelection}
16+
aria-description={this._selectionCellAriaDescription}
17+
aria-colindex={ariaColIndex++}
1618
data-ui5-table-cell-fixed
1719
data-ui5-table-selection-component
1820
>
@@ -40,12 +42,14 @@ export default function TableHeaderRowTemplate(this: TableHeaderRow) {
4042
</TableHeaderCell>
4143
}
4244

43-
{ this._visibleCells.map(cell => (
44-
<slot name={cell._individualSlot} key={cell._individualSlot}></slot>
45-
))}
45+
{ this._visibleCells.map(cell => {
46+
cell.ariaColIndex = `${ariaColIndex++}`;
47+
return <slot name={cell._individualSlot} key={cell._individualSlot}></slot>;
48+
})}
4649

4750
{ this._rowActionCount > 0 &&
4851
<TableHeaderCell id="actions-cell"
52+
aria-colindex={ariaColIndex++}
4953
aria-label={this._i18nRowActions}
5054
></TableHeaderCell>
5155
}

packages/main/src/TableRow.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class TableRow extends TableRowBase {
7777
rowKey?: string;
7878

7979
/**
80-
* Defines the position of the row related to the total number of rows within the table when the `ui5-table-virtualizer` feature is used.
80+
* Defines the 0-based position of the row related to the total number of rows within the table when the `ui5-table-virtualizer` feature is used.
8181
*
8282
* @default undefined
8383
* @since 2.5.0
@@ -116,10 +116,10 @@ class TableRow extends TableRowBase {
116116

117117
onBeforeRendering() {
118118
super.onBeforeRendering();
119-
toggleAttribute(this, "_interactive", this._isInteractive);
120-
toggleAttribute(this, "aria-rowindex", this.position !== undefined, `${this.position! + 1}`);
121119
toggleAttribute(this, "aria-current", this._renderNavigated && this.navigated, "true");
120+
toggleAttribute(this, "_interactive", this._isInteractive);
122121
toggleAttribute(this, "draggable", this.movable, "true");
122+
this.ariaRowIndex = `${this._rowIndex + 2}`;
123123
}
124124

125125
async focus(focusOptions?: FocusOptions | undefined): Promise<void> {
@@ -168,6 +168,16 @@ class TableRow extends TableRowBase {
168168
return this.interactive || (this._isSelectable && !this._hasSelector);
169169
}
170170

171+
get _rowIndex() {
172+
if (this.position !== undefined) {
173+
return this.position;
174+
}
175+
if (this._table) {
176+
return this._table.rows.indexOf(this);
177+
}
178+
return -1;
179+
}
180+
171181
get _hasOverflowActions() {
172182
let renderedActionsCount = 0;
173183
return this.actions.some(action => {

packages/main/src/TableRowTemplate.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export default function TableRowTemplate(this: TableRow) {
6060
}
6161

6262
{ this._renderNavigated &&
63-
<TableCell id="navigated-cell" data-excluded-from-navigation={true}>
63+
<TableCell id="navigated-cell" data-excluded-from-navigation aria-hidden={true}>
6464
<div id="navigated"></div>
6565
</TableCell>
6666
}

0 commit comments

Comments
 (0)