diff --git a/packages/main/cypress/specs/Table.cy.tsx b/packages/main/cypress/specs/Table.cy.tsx
index 6b78b0f37ff2..501595e4c64c 100644
--- a/packages/main/cypress/specs/Table.cy.tsx
+++ b/packages/main/cypress/specs/Table.cy.tsx
@@ -3,6 +3,7 @@ import TableHeaderRow from "../../src/TableHeaderRow.js";
import TableCell from "../../src/TableCell.js";
import TableRow from "../../src/TableRow.js";
import TableSelectionMulti from "../../src/TableSelectionMulti.js";
+import TableSelectionSingle from "../../src/TableSelectionSingle.js";
import TableHeaderCell from "../../src/TableHeaderCell.js";
import TableHeaderCellActionAI from "../../src/TableHeaderCellActionAI.js";
import Label from "../../src/Label.js";
@@ -249,6 +250,47 @@ describe("Table - Rendering", () => {
// 2fr is being ignored
checkWidth("#colD", 48);
});
+
+ it("should alternate rows", () => {
+ cy.mount(
+
+
+
+ ColumnA
+
+
+ R1C1
+
+
+ R2C1
+
+
+ R3C1
+
+
+ R4C1
+
+
+ );
+
+ cy.get("#table1").then($table => {
+ const rows = $table.find("[ui5-table-row]").get();
+ const rowBackgrounds = rows.map(row => getComputedStyle(row).backgroundColor);
+ expect(rowBackgrounds[0]).to.not.equal(rowBackgrounds[1]);
+ expect(rowBackgrounds[1]).to.not.equal(rowBackgrounds[2]);
+ expect(rowBackgrounds[2]).to.not.equal(rowBackgrounds[3]);
+ expect(rowBackgrounds[0]).to.equal(rowBackgrounds[2]);
+ expect(rowBackgrounds[1]).to.equal(rowBackgrounds[3]);
+ });
+
+ cy.get("#selection").invoke("prop", "selected", "r2"); cy.wait(50);
+ cy.get("#table1").then($table => {
+ const rows = $table.find("[ui5-table-row]").get();
+ const rowBackgrounds = rows.map(row => getComputedStyle(row).backgroundColor);
+ expect(rowBackgrounds[1]).to.not.equal(rowBackgrounds[3]);
+ expect(rowBackgrounds[0]).to.equal(rowBackgrounds[2]);
+ });
+ });
});
describe("Table - Popin Mode", () => {
@@ -994,7 +1036,6 @@ describe("Table - HeaderCell", () => {
cy.get("@headerCell2").should("not.have.attr", "aria-sort");
cy.get("@table").invoke("css", "width", "250px");
- // eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(50);
cy.get("@row1").find("ui5-table-cell[_popin]").as("row1popins");
diff --git a/packages/main/cypress/specs/TableCustomAnnouncement.cy.tsx b/packages/main/cypress/specs/TableCustomAnnouncement.cy.tsx
index 55d6aa98edd4..2029faacd058 100644
--- a/packages/main/cypress/specs/TableCustomAnnouncement.cy.tsx
+++ b/packages/main/cypress/specs/TableCustomAnnouncement.cy.tsx
@@ -33,6 +33,7 @@ const {
TABLE_COLUMN_HEADER_ROW: { defaultText: COLUMN_HEADER_ROW },
TABLE_ROW_SELECTED: { defaultText: SELECTED },
TABLE_ROW_NAVIGABLE: { defaultText: NAVIGABLE },
+ TABLE_ROW_NAVIGATED: { defaultText: NAVIGATED },
TABLE_ROW_ACTIVE: { defaultText: ACTIVE },
} = Translations;
@@ -48,7 +49,7 @@ describe("Cell Custom Announcement - More details", () => {
Header3
-
+
@@ -73,6 +74,7 @@ describe("Cell Custom Announcement - More details", () => {
cy.get("@row1").find("#row1-input1").as("row1Input1");
cy.get("@row1").find("#row1-input2").as("row1Input2");
cy.get("@row1").find("#row1-div").as("row1Div");
+ cy.get("@row1").shadow().find("#navigated-cell").as("row1NavigatedCell");
});
function checkAnnouncement(expectedText: string, focusAgain = false) {
@@ -88,14 +90,23 @@ describe("Cell Custom Announcement - More details", () => {
it("should announce table cells", () => {
cy.get("@row1").realClick(); // row focused
+ cy.focused().should("have.attr", "aria-rowindex", "2")
+ .should("have.attr", "role", "row");
+
cy.realPress("ArrowRight"); // selection cell focused
checkAnnouncement("");
+ cy.focused().should("have.attr", "aria-colindex", "1")
+ .should("have.attr", "role", "gridcell");
cy.realPress("ArrowRight"); // first cell focused
checkAnnouncement("Row1Cell1");
+ cy.focused().should("have.attr", "aria-colindex", "2")
+ .should("have.attr", "role", "gridcell");
cy.realPress("ArrowRight"); // second cell focused
checkAnnouncement(CONTAINS_CONTROL);
+ cy.focused().should("have.attr", "aria-colindex", "3")
+ .should("have.attr", "role", "gridcell");
cy.get("@row1Input2").invoke("removeAttr", "hidden");
checkAnnouncement(CONTAINS_CONTROLS, true);
@@ -105,6 +116,8 @@ describe("Cell Custom Announcement - More details", () => {
cy.realPress("ArrowRight"); // third cell focused
checkAnnouncement(EMPTY);
+ cy.focused().should("have.attr", "aria-colindex", "4")
+ .should("have.attr", "role", "gridcell");
cy.get("@row1Div").invoke("attr", "tabindex", "0");
cy.get("@row1Div").invoke("css", "width", "150px");
@@ -114,6 +127,8 @@ describe("Cell Custom Announcement - More details", () => {
cy.realPress("ArrowRight"); // fourth cell focused
checkAnnouncement(`Row1Cell3 . ${CONTAINS_CONTROL}`);
+ cy.focused().should("have.attr", "aria-colindex", "5")
+ .should("have.attr", "role", "gridcell");
cy.document().then((doc) => {
const row1Button = doc.getElementById("row1-button") as Button;
@@ -130,8 +145,10 @@ describe("Cell Custom Announcement - More details", () => {
cy.get("@row1Button").invoke("attr", "data-ui5-table-acc-text", "Button with custom accessibility text");
checkAnnouncement(`Button with custom accessibility text . ${CONTAINS_CONTROL}`, true);
- cy.realPress("ArrowRight"); // ROW_ACTIONS cell
+ cy.realPress("ArrowRight"); // Row actions cell
checkAnnouncement(Table.i18nBundle.getText(MULTIPLE_ACTIONS, 2));
+ cy.focused().should("have.attr", "aria-colindex", "6")
+ .should("have.attr", "role", "gridcell");
cy.get("#row1-edit-action").invoke("remove");
checkAnnouncement(ONE_ROW_ACTION, true);
@@ -139,29 +156,49 @@ describe("Cell Custom Announcement - More details", () => {
cy.get("#row1-add-action").invoke("remove");
checkAnnouncement(EMPTY, true);
+ cy.get("@row1NavigatedCell").should("have.attr", "role", "none")
+ .should("have.attr", "aria-hidden", "true");
+
cy.realPress("Home"); // selection cell focused
checkAnnouncement("");
});
it("should announce table header cells", () => {
cy.get("@headerRow").realClick(); // header row focused
+ cy.focused().should("have.attr", "aria-rowindex", "1")
+ .should("have.attr", "role", "row");
+
cy.realPress("ArrowRight"); // selection cell focused
checkAnnouncement("");
+ cy.focused().should("have.attr", "aria-colindex", "1")
+ .should("have.attr", "role", "columnheader");
cy.realPress("ArrowRight"); // first cell focused
checkAnnouncement(`Header1 ${GENERATED_BY_AI} . ${CONTAINS_CONTROL}`);
+ cy.focused().should("have.attr", "aria-colindex", "2")
+ .should("have.attr", "role", "columnheader")
+ .should("have.attr", "aria-sort", "ascending");
cy.realPress("ArrowRight"); // second cell focused
checkAnnouncement("Header2");
+ cy.focused().should("have.attr", "aria-colindex", "3")
+ .should("have.attr", "role", "columnheader");
cy.realPress("ArrowRight"); // third cell focused
checkAnnouncement("Header3");
+ cy.focused().should("have.attr", "aria-colindex", "4")
+ .should("have.attr", "role", "columnheader");
cy.realPress("ArrowRight"); // forth cell focused
checkAnnouncement(EMPTY);
+ cy.focused().should("have.attr", "aria-colindex", "5")
+ .should("have.attr", "role", "columnheader")
+ .should("have.attr", "aria-sort", "descending");
- cy.realPress("ArrowRight"); // forth cell focused
+ cy.realPress("ArrowRight"); // row action focused
checkAnnouncement(ROW_ACTIONS);
+ cy.focused().should("have.attr", "aria-colindex", "6")
+ .should("have.attr", "role", "columnheader");
cy.realPress("Home"); // selection cell focused
checkAnnouncement("");
@@ -217,6 +254,8 @@ describe("Row Custom Announcement - Less details", () => {
cy.get("#table0").shadow().find("#table").as("innerTable");
cy.get("@rows").first().as("row1");
cy.get("@row1").find("#row1-button").as("row1Button");
+ cy.get("@row1").find("ui5-table-cell").as("row1Cells");
+ cy.get("@headerRow").find("ui5-table-header-cell").first().as("headerRowCells");
cy.document().then((doc) => {
const header1Label = doc.getElementById("Header1Label") as Label;
@@ -257,6 +296,8 @@ describe("Row Custom Announcement - Less details", () => {
checkAnnouncement(`Row . 2 of 2 . ${SELECTED} . ${NAVIGABLE} . H1`);
checkAnnouncement(`H1 . R1C1 . H2 . ${CONTAINS_CONTROLS} . H3 . ${EMPTY} . H4 . C4 Button C4Button`);
checkAnnouncement(ONE_ROW_ACTION);
+ cy.focused().should("have.attr", "aria-rowindex", "2")
+ .should("have.attr", "role", "row");
cy.get("#selection").invoke("attr", "selected", "");
checkAnnouncement(`Row . 2 of 2 . ${NAVIGABLE}`, true);
@@ -277,17 +318,50 @@ describe("Row Custom Announcement - Less details", () => {
cy.get("#row1-nav-action").invoke("remove");
cy.get("#row1-add-action").invoke("remove");
- checkAnnouncement(`Row . 2 of 2 . H1 . R1C1 . H2 . ${CONTAINS_CONTROLS} . H4Popin . C4 Button C4Button`, true, "equal");
+ checkAnnouncement(`Row . 2 of 2 . H1 . R1C1 . H2 . ${CONTAINS_CONTROLS} . H4Popin . C4 Button C4Button . ${NAVIGATED}`, true, "equal");
cy.realPress("ArrowRight"); // selection cell focused
checkAnnouncement("");
+ cy.focused().should("have.attr", "aria-colindex", "1")
+ .should("have.attr", "role", "gridcell");
+
+ cy.realPress("ArrowRight"); // first cell focused
+ checkAnnouncement("R1C1", false, "equal");
+ cy.focused().should("have.attr", "aria-colindex", "2")
+ .should("have.attr", "role", "gridcell");
+
+ cy.realPress("ArrowRight"); // row action cell focused
+ checkAnnouncement(EMPTY, false, "equal");
+ cy.focused().should("have.attr", "aria-colindex", "3")
+ .should("have.attr", "role", "gridcell");
cy.realPress("End"); // popin cell focused we need details
checkAnnouncement(`H2 . ${CONTAINS_CONTROLS} . H4Popin ${GENERATED_BY_AI} . ${CONTAINS_CONTROL} . C4 Button C4Button ${REQUIRED} ${DISABLED} ${READONLY} . ${CONTAINS_CONTROL}`);
+ cy.focused().should("have.attr", "aria-colindex", "4")
+ .should("have.attr", "role", "gridcell");
+
+ cy.get("@row1Cells").each(($cell, index) => {
+ if (index === 0) return;
+ cy.wrap($cell).should("have.attr", "_popin");
+ cy.wrap($cell).should("not.have.attr", "role");
+ cy.wrap($cell).should("not.have.attr", "aria-colindex");
+ });
cy.realPress("Home"); // selection cell focused
cy.realPress("Home"); // row focused
+
cy.get("#table0").invoke("css", "width", "1000px");
+ checkAnnouncement(`Row . 2 of 2 . H1 . R1C1 . H2 . ${CONTAINS_CONTROLS} . H3 . ${EMPTY} . H4 . C4 Button C4Button . ${NAVIGATED}`, true, "equal");
+ cy.get("@row1Cells").each(($cell, index) => {
+ cy.wrap($cell).should("not.have.attr", "_popin");
+ cy.wrap($cell).should("have.attr", "role", "gridcell");
+ cy.wrap($cell).should("have.attr", "aria-colindex", `${index + 2}`);
+ });
+
+ cy.get("@row1").shadow().find("#navigated-cell").should("have.attr", "role", "none")
+ .should("have.attr", "aria-hidden", "true");
+
+ cy.get("@row1").invoke("prop", "navigated", false);
checkAnnouncement(`Row . 2 of 2 . H1 . R1C1 . H2 . ${CONTAINS_CONTROLS} . H3 . ${EMPTY} . H4 . C4 Button C4Button`, true, "equal");
cy.realPress("ArrowUp"); // header row focused
@@ -319,5 +393,25 @@ describe("Row Custom Announcement - Less details", () => {
cy.get("#table0").invoke("append", '');
checkAnnouncement(`${COLUMN_HEADER_ROW} . ${SELECTION} . H1 . H2 . H3 . H4`, true, "equal");
+
+ cy.get("#table0").invoke("css", "width", "301px");
+ checkAnnouncement(`${COLUMN_HEADER_ROW} . ${SELECTION} . H1`, true, "equal");
+ cy.get("@headerRowCells").each(($cell, index) => {
+ if (index === 0) {
+ cy.wrap($cell).should("have.attr", "role", "columnheader");
+ cy.wrap($cell).should("have.attr", "aria-colindex", "2");
+ } else {
+ cy.wrap($cell).should("have.attr", "_popin");
+ cy.wrap($cell).should("not.have.attr", "role");
+ cy.wrap($cell).should("not.have.attr", "aria-colindex");
+ }
+ });
+
+ cy.get("#table0").invoke("css", "width", "1000px");
+ checkAnnouncement(`${COLUMN_HEADER_ROW} . ${SELECTION} . H1 . H2 . H3 . H4`, true, "equal");
+ cy.get("@headerRowCells").each(($cell, index) => {
+ cy.wrap($cell).should("have.attr", "role", "columnheader");
+ cy.wrap($cell).should("have.attr", "aria-colindex", `${index + 2}`);
+ });
});
});
diff --git a/packages/main/cypress/specs/TableGrowing.cy.tsx b/packages/main/cypress/specs/TableGrowing.cy.tsx
index 5fcd9a0f06c2..1727bbb4ff13 100644
--- a/packages/main/cypress/specs/TableGrowing.cy.tsx
+++ b/packages/main/cypress/specs/TableGrowing.cy.tsx
@@ -48,12 +48,6 @@ describe("TableGrowing - Button", () => {
.should("have.attr", "role", "button")
.should("have.attr", "aria-labelledby", "text subtext");
- cy.get("[ui5-table")
- .shadow()
- .find("#growing-row")
- .should("exist")
- .should("have.attr", "aria-hidden", "true");
-
cy.get("[ui5-table-growing]")
.shadow()
.find("#text")
@@ -104,10 +98,10 @@ describe("TableGrowing - Button", () => {
);
- cy.get("[ui5-table]")
+ cy.get("[ui5-table-growing]")
.shadow()
- .find("#growing-row")
- .should("not.exist");
+ .find("#button")
+ .should("not.be.visible");
});
});
@@ -221,11 +215,6 @@ describe("TableGrowing - Scroll", () => {
.shadow()
.find("#button")
.should("not.be.visible");
-
- cy.get("[ui5-table]")
- .shadow()
- .find("#growing-row")
- .should("not.exist");
});
it("tests button shown when not scrollable", () => {
@@ -234,12 +223,7 @@ describe("TableGrowing - Scroll", () => {
cy.get("[ui5-table-growing]")
.shadow()
.find("#button")
- .should("exist");
-
- cy.get("[ui5-table]")
- .shadow()
- .find("#growing-row")
- .should("exist");
+ .should("be.visible");
});
});
@@ -310,11 +294,6 @@ describe("TableGrowing - Scroll", () => {
.find("#button")
.should("not.be.visible");
- cy.get("[ui5-table]")
- .shadow()
- .find("#growing-row")
- .should("not.exist");
-
for (let i = 2; i <= 6; i++) {
cy.get("#wrapper")
.scrollTo("bottom", { duration: 300 });
diff --git a/packages/main/src/Table.ts b/packages/main/src/Table.ts
index 3167e0915618..d5e940aabc65 100644
--- a/packages/main/src/Table.ts
+++ b/packages/main/src/Table.ts
@@ -116,8 +116,10 @@ type TableRowActionClickEventDetail = {
*
* The following features are currently available:
*
- * * [TableSelection](../TableSelection) - adds selection capabilities to the table
+ * * [TableSelectionMulti](../TableSelectionMulti) - adds multi-selection capabilities to the table
+ * * [TableSelectionSingle](../TableSelectionSingle) - adds single-selection capabilities to the table
* * [TableGrowing](../TableGrowing) - provides growing capabilities to load more data
+ * * [TableVirtualizer](../TableVirtualizer) - adds virtualization capabilities to the table
*
* ### Keyboard Handling
*
@@ -140,7 +142,6 @@ type TableRowActionClickEventDetail = {
* * F2 - Focuses the first tabbable element in the row
* * F7 - If focus position is remembered, moves focus to the corresponding focus position row, otherwise to the first tabbable element within the row
* * [Shift]Tab - Move focus to the element in the tab chain outside the table
-
*
* If the focus is on a cell, the following keyboard shortcuts are available:
* * Down - Navigates down
@@ -155,16 +156,25 @@ type TableRowActionClickEventDetail = {
* * Enter - Focuses the first tabbable cell content
* * F7 - If the focus is on an interactive element inside a row, moves focus to the corresponding row and remembers the focus position of the element within the row
* * [Shift]Tab - Move focus to the element in the tab chain outside the table
-
*
* If the focus is on an interactive cell content, the following keyboard shortcuts are available:
* * Down - Move the focus to the interactive element in the same column of the previous row, unless the focused element prevents the default
* * Up - Move the focus to the interactive element in the same column of the next row, unless the focused element prevents the default
* * [Shift]Tab - Move the focus to the element in the tab chain
*
+ * ### Accessibility
+ *
+ * The `ui5-table` follows the [ARIA grid design pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/).
+ * This pattern enables cell-based keyboard navigation and, as explained above, we also support row-based keyboard navigation.
+ * Since the grid design pattern does not inherently provide row-based keyboard behavior, if the focus is on a row, not only the row information but also the corresponding column headers for each cell must be announced.
+ * This can only be achieved through a custom accessibility announcement.
+ * To support this, UI5 Web Components expose its own accessibility metadata via the `accessibilityInfo` property.
+ * The `ui5-table` uses this information to create the required custom announcements dynamically.
+ * If you include custom web components inside table cells that are not part of the standard UI5 Web Components set, their accessibility information can be provided using the `data-ui5-table-acc-text` attribute.
+ *
* ### ES6 Module Import
*
- * `import "@ui5/webcomponents/dist/Table.js";`\
+ * `import "@ui5/webcomponents/dist/Table.js";` (`ui5-table`)\
* `import "@ui5/webcomponents/dist/TableRow.js";` (`ui5-table-row`)\
* `import "@ui5/webcomponents/dist/TableCell.js";` (`ui5-table-cell`)\
* `import "@ui5/webcomponents/dist/TableHeaderRow.js";` (`ui5-table-header-row`)\
@@ -369,6 +379,16 @@ class Table extends UI5Element {
@property({ type: Number })
rowActionCount = 0;
+ /**
+ * Determines whether the table rows are displayed with alternating background colors.
+ *
+ * @default false
+ * @since 2.17
+ * @public
+ */
+ @property({ type: Boolean })
+ alternateRowColors = false;
+
/**
* Defines the sticky top offset of the table, if other sticky elements outside of the table exist.
*/
@@ -436,9 +456,10 @@ class Table extends UI5Element {
onBeforeRendering(): void {
this._renderNavigated = this.rows.some(row => row.navigated);
- [...this.headerRow, ...this.rows].forEach(row => {
+ [...this.headerRow, ...this.rows].forEach((row, index) => {
row._renderNavigated = this._renderNavigated;
row._rowActionCount = this.rowActionCount;
+ row._alternate = this.alternateRowColors && index % 2 === 0;
});
this.style.setProperty(getScopedVarName("--ui5_grid_sticky_top"), this.stickyTop);
diff --git a/packages/main/src/TableCellBase.ts b/packages/main/src/TableCellBase.ts
index 13af6ba3ddf8..0976884644d4 100644
--- a/packages/main/src/TableCellBase.ts
+++ b/packages/main/src/TableCellBase.ts
@@ -2,7 +2,6 @@ import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
import {
customElement, slot, property, i18n,
} from "@ui5/webcomponents-base/dist/decorators.js";
-import { toggleAttribute } from "./TableUtils.js";
import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js";
import TableCellBaseStyles from "./generated/themes/TableCellBase.css.js";
import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js";
@@ -45,19 +44,16 @@ abstract class TableCellBase extends UI5Element {
@property({ type: Boolean, noAttribute: true })
_popinHidden = false;
- protected ariaRole: string = "gridcell";
+ ariaRole: string = "gridcell";
@i18n("@ui5/webcomponents")
static i18nBundle: I18nBundle;
onEnterDOM() {
+ !this.role && this.setAttribute("role", this.ariaRole);
this.toggleAttribute("ui5-table-cell-base", true);
}
- onBeforeRendering() {
- toggleAttribute(this, "role", !this._popin, this.ariaRole);
- }
-
getFocusDomRef() {
return this;
}
diff --git a/packages/main/src/TableCustomAnnouncement.ts b/packages/main/src/TableCustomAnnouncement.ts
index c65a13d6922c..db60b4527b65 100644
--- a/packages/main/src/TableCustomAnnouncement.ts
+++ b/packages/main/src/TableCustomAnnouncement.ts
@@ -12,6 +12,7 @@ import {
TABLE_ROW_SELECTED,
TABLE_ROW_ACTIVE,
TABLE_ROW_NAVIGABLE,
+ TABLE_ROW_NAVIGATED,
TABLE_COLUMN_HEADER_ROW,
TABLE_CELL_SINGLE_CONTROL,
TABLE_CELL_MULTIPLE_CONTROLS,
@@ -218,6 +219,10 @@ class TableCustomAnnouncement extends TableExtension {
descriptions.push(row._actionCellAccText!);
}
+ if (row._renderNavigated && row.navigated) {
+ descriptions.push(i18nBundle.getText(TABLE_ROW_NAVIGATED));
+ }
+
updateInvisibleText(row, descriptions);
}
diff --git a/packages/main/src/TableHeaderCell.ts b/packages/main/src/TableHeaderCell.ts
index 5b14efd2bcc6..0d4057d55853 100644
--- a/packages/main/src/TableHeaderCell.ts
+++ b/packages/main/src/TableHeaderCell.ts
@@ -131,7 +131,7 @@ class TableHeaderCell extends TableCellBase {
@query("slot[name=action]")
_actionSlot!: HTMLSlotElement;
- protected ariaRole: string = "columnheader";
+ ariaRole: string = "columnheader";
_popinWidth: number = 0;
onBeforeRendering() {
diff --git a/packages/main/src/TableHeaderRowTemplate.tsx b/packages/main/src/TableHeaderRowTemplate.tsx
index 5d68ca6e639e..8835e75dd45f 100644
--- a/packages/main/src/TableHeaderRowTemplate.tsx
+++ b/packages/main/src/TableHeaderRowTemplate.tsx
@@ -45,27 +45,26 @@ export default function TableHeaderRowTemplate(this: TableHeaderRow, ariaColInde
{ this.cells.flatMap(cell => {
if (cell._popin) {
+ cell.role = null;
cell.ariaColIndex = null;
return [];
}
- cell.ariaColIndex = `${ariaColIndex++}`;
+
+ cell.role ??= cell.ariaRole;
+ cell.ariaColIndex = (cell.role === cell.ariaRole) ? `${ariaColIndex++}` : null;
return [];
})}
{ this._rowActionCount > 0 &&
-
+
{this._i18nRowActions}
}
{ this._popinCells.length > 0 &&
-
+
+ {this._i18nRowPopin}
+
}
>
);
diff --git a/packages/main/src/TableRow.ts b/packages/main/src/TableRow.ts
index a6ae9c9c896e..ab8e7dd1b48e 100644
--- a/packages/main/src/TableRow.ts
+++ b/packages/main/src/TableRow.ts
@@ -125,10 +125,10 @@ class TableRow extends TableRowBase {
onBeforeRendering() {
super.onBeforeRendering();
- toggleAttribute(this, "aria-current", this._renderNavigated && this.navigated, "true");
- toggleAttribute(this, "_interactive", this._isInteractive);
+ this.ariaRowIndex = (this.role === "row") ? `${this._rowIndex + 2}` : null;
toggleAttribute(this, "draggable", this.movable, "true");
- this.ariaRowIndex = `${this._rowIndex + 2}`;
+ toggleAttribute(this, "_interactive", this._isInteractive);
+ toggleAttribute(this, "_alternate", this._alternate);
}
async focus(focusOptions?: FocusOptions | undefined): Promise {
diff --git a/packages/main/src/TableRowBase.ts b/packages/main/src/TableRowBase.ts
index 62651401c561..d4c429c508c0 100644
--- a/packages/main/src/TableRowBase.ts
+++ b/packages/main/src/TableRowBase.ts
@@ -37,6 +37,9 @@ abstract class TableRowBase extends UI5Element {
@property({ type: Boolean, noAttribute: true })
_renderNavigated = false;
+ @property({ type: Boolean, noAttribute: true })
+ _alternate = false;
+
@query("#selection-cell")
_selectionCell?: HTMLElement;
@@ -47,7 +50,7 @@ abstract class TableRowBase extends UI5Element {
static i18nBundle: I18nBundle;
onEnterDOM() {
- this.setAttribute("role", "row");
+ !this.role && this.setAttribute("role", "row");
this.toggleAttribute("ui5-table-row-base", true);
}
diff --git a/packages/main/src/TableRowTemplate.tsx b/packages/main/src/TableRowTemplate.tsx
index cfb6ec3b1957..5e44c7825df4 100644
--- a/packages/main/src/TableRowTemplate.tsx
+++ b/packages/main/src/TableRowTemplate.tsx
@@ -38,10 +38,13 @@ export default function TableRowTemplate(this: TableRow, ariaColIndex: number =
{ this.cells.flatMap(cell => {
if (cell._popin) {
+ cell.role = null;
cell.ariaColIndex = null;
return [];
}
- cell.ariaColIndex = `${ariaColIndex++}`;
+
+ cell.role ??= cell.ariaRole;
+ cell.ariaColIndex = (cell.role === cell.ariaRole) ? `${ariaColIndex++}` : null;
return [];
})}
@@ -72,6 +75,7 @@ export default function TableRowTemplate(this: TableRow, ariaColIndex: number =
diff --git a/packages/main/src/TableSelection.ts b/packages/main/src/TableSelection.ts
index 9528a424b441..08e60c9beca4 100644
--- a/packages/main/src/TableSelection.ts
+++ b/packages/main/src/TableSelection.ts
@@ -259,7 +259,7 @@ class TableSelection extends UI5Element implements ITableFeature {
const focusedElement = getActiveElement(); // Assumption: The focused element is always the "next" row after navigation.
- if (!(focusedElement?.hasAttribute("ui5-table-row") || this._rangeSelection?.isMouse || focusedElement?.hasAttribute("ui5-growing-row"))) {
+ if (!(focusedElement?.hasAttribute("ui5-table-row") || this._rangeSelection?.isMouse)) {
this._stopRangeSelection();
return;
}
diff --git a/packages/main/src/TableSelectionMulti.ts b/packages/main/src/TableSelectionMulti.ts
index d519ff8a5f6f..42e0824d2d22 100644
--- a/packages/main/src/TableSelectionMulti.ts
+++ b/packages/main/src/TableSelectionMulti.ts
@@ -200,7 +200,7 @@ class TableSelectionMulti extends TableSelectionBase {
const focusedElement = getActiveElement(); // Assumption: The focused element is always the "next" row after navigation.
- if (!(focusedElement?.hasAttribute("ui5-table-row") || this._rangeSelection?.isMouse || focusedElement?.hasAttribute("ui5-growing-row"))) {
+ if (!(focusedElement?.hasAttribute("ui5-table-row") || this._rangeSelection?.isMouse)) {
this._stopRangeSelection();
return;
}
diff --git a/packages/main/src/TableTemplate.tsx b/packages/main/src/TableTemplate.tsx
index 16969179c5bc..8a615584a42d 100644
--- a/packages/main/src/TableTemplate.tsx
+++ b/packages/main/src/TableTemplate.tsx
@@ -37,22 +37,12 @@ export default function TableTemplate(this: Table) {
}
- { this.rows.length > 0 && this._getGrowing()?.hasGrowingComponent() &&
-
- }
-
-
+
{ this.loading &&
- >
- );
-}
-function growingRow(this: Table) {
- return (
-
-
+ { this.rows.length > 0 && this._getGrowing()?.hasGrowingComponent() &&
-
-
+ }
+ >
);
}
diff --git a/packages/main/src/i18n/messagebundle.properties b/packages/main/src/i18n/messagebundle.properties
index 8557c6c3f258..72c126c61b28 100644
--- a/packages/main/src/i18n/messagebundle.properties
+++ b/packages/main/src/i18n/messagebundle.properties
@@ -726,6 +726,8 @@ FORM_TEXTFIELD_REQUIRED=Please fill in this field.
TABLE_SELECTION=Selection
#XACT: ARIA description for the selection component of the row
TABLE_ROW_SELECTOR=Row Selector
+#XACT: ARIA description for the navigated indicator of the row
+TABLE_ROW_NAVIGATED=Navigated
#XMSG: Message text to be displayed when there are no rows in the grid
TABLE_NO_DATA=No Data
#XACT: ARIA announcement for the table that allows single selections
@@ -733,9 +735,9 @@ TABLE_SINGLE_SELECTABLE=Single Selection Table
#XACT: ARIA announcement for the table that allows multi selection
TABLE_MULTI_SELECTABLE=Multi Selection Table
#XACT: accessibility text for the cell that contains a single interactive element
-TABLE_CELL_SINGLE_CONTROL=Contains Control
+TABLE_CELL_SINGLE_CONTROL=Includes element
#XACT: accessibility text for the cell that contains multiple interactive elements
-TABLE_CELL_MULTIPLE_CONTROLS=Contains Controls
+TABLE_CELL_MULTIPLE_CONTROLS=Includes elements
#XACT: ARIA description for the selection column header when select all checkbox is shown
TABLE_COLUMNHEADER_SELECTALL_DESCRIPTION=Select All Checkbox
#XACT: ARIA description for the selection column header when select all checkbox is checked
diff --git a/packages/main/src/themes/Table.css b/packages/main/src/themes/Table.css
index f3e9db83e9d0..319ec481504c 100644
--- a/packages/main/src/themes/Table.css
+++ b/packages/main/src/themes/Table.css
@@ -31,20 +31,6 @@
justify-content: center;
}
-#footer {
- grid-column: 1 / -1;
- grid-template-rows: subgrid;
- height: fit-content;
-}
-
-#growing-cell {
- grid-column: 1 / -1;
- justify-content: center;
- padding: 0;
- border-top: 1px solid var(--sapList_BorderColor);
- box-sizing: border-box;
-}
-
#loading {
position: absolute;
inset: 0;
diff --git a/packages/main/src/themes/TableGrowing.css b/packages/main/src/themes/TableGrowing.css
index baf6062f3179..6e7b8e97c432 100644
--- a/packages/main/src/themes/TableGrowing.css
+++ b/packages/main/src/themes/TableGrowing.css
@@ -1,23 +1,16 @@
@import "./InvisibleTextStyles.css";
-:host {
- /** Growing Control spans the whole row **/
- flex-grow: 1;
-}
-
-#button:focus {
- outline: var(--sapContent_FocusWidth) var(--sapContent_FocusStyle) var(--sapContent_FocusColor);
- outline-offset: calc(-1 * var(--sapContent_FocusWidth));
-}
-
#button {
- margin: 0;
- padding: 0;
display: flex;
- justify-content: center;
align-items: center;
flex-direction: column;
cursor: pointer;
+ border-bottom: 1px solid var(--sapList_BorderColor);
+}
+
+#button:focus {
+ outline: var(--sapContent_FocusWidth) var(--sapContent_FocusStyle) var(--sapContent_FocusColor);
+ outline-offset: calc(-1 * var(--sapContent_FocusWidth));
}
@media (hover: hover) {
@@ -32,7 +25,7 @@
}
#text {
- padding: 1rem 0 1rem 0;
+ padding: 1rem 0;
font-size: var(--sapFontSize);
color: var(--sapButton_TextColor);
font-weight: bold;
diff --git a/packages/main/src/themes/TableHeaderRow.css b/packages/main/src/themes/TableHeaderRow.css
index d3e0c800f26a..efadc68a537f 100644
--- a/packages/main/src/themes/TableHeaderRow.css
+++ b/packages/main/src/themes/TableHeaderRow.css
@@ -26,6 +26,7 @@
height: var(--_ui5_checkbox_inner_width_height);
}
+#popin-cell-content,
#actions-cell-content {
position: absolute;
clip: rect(0, 0, 0, 0);
diff --git a/packages/main/src/themes/TableRow.css b/packages/main/src/themes/TableRow.css
index ac25f553c64a..0ceb9024b025 100644
--- a/packages/main/src/themes/TableRow.css
+++ b/packages/main/src/themes/TableRow.css
@@ -2,19 +2,15 @@
background: var(--sapList_Background);
}
-:host([position]) {
- height: var(--row-height);
+:host([_alternate]) {
+ background: var(--_ui5_table_row_alternating_background);
}
:host([aria-selected=true]) {
- background-color: var(--sapList_SelectionBackgroundColor);
+ background: var(--sapList_SelectionBackgroundColor);
border-bottom: var(--sapList_BorderWidth) solid var(--sapList_SelectionBorderColor);
}
-:host([_interactive]) {
- cursor: pointer;
-}
-
@media (hover: hover) {
:host([_interactive]:hover) {
background: var(--sapList_Hover_Background);
@@ -31,6 +27,14 @@
background: var(--sapList_Active_Background);
}
+:host([position]) {
+ height: var(--row-height);
+}
+
+:host([_interactive]) {
+ cursor: pointer;
+}
+
#popin-cell {
align-content: initial;
flex-direction: column;
@@ -54,7 +58,7 @@
:host([navigated]) #navigated {
position: absolute;
inset: 0;
- background-color: var(--sapList_SelectionBorderColor);
+ background: var(--sapList_SelectionBorderColor);
}
:host([tabindex]:focus) #navigated {
diff --git a/packages/main/src/themes/base/Table-parameters.css b/packages/main/src/themes/base/Table-parameters.css
index 3094dcb658a1..0a73a9c90f79 100644
--- a/packages/main/src/themes/base/Table-parameters.css
+++ b/packages/main/src/themes/base/Table-parameters.css
@@ -6,4 +6,8 @@
--_ui5_table_cell_horizontal_padding: 0.5rem;
--_ui5_table_cell_vertical_padding: 0.25rem;
--_ui5_table_row_actions_gap: 0.25rem;
+ --_ui5_table_row_alternating_background: var(--sapTableRow_AlternatingBackground, var(--sapList_AlternatingBackground));
+ --_ui5_table_row_alternating_hover_background: var(--sapTableRow_AlternatingHoverBackground, var(--sapList_Hover_Background));
+ --_ui5_table_row_alternating_selection_background: var(--sapTableRow_AlternatingSelectionBackground, var(--sapList_SelectionBackgroundColor));
+ --_ui5_table_row_alternating_selection_hover_background: var(--sapTableRow_AlternatingSelectionHoverBackground, var(--sapList_Hover_SelectionBackground));
}
\ No newline at end of file
diff --git a/packages/main/test/pages/Table.html b/packages/main/test/pages/Table.html
index 912151a44168..65270e1770b1 100644
--- a/packages/main/test/pages/Table.html
+++ b/packages/main/test/pages/Table.html
@@ -31,8 +31,8 @@
-
-
+
+
@@ -51,7 +51,7 @@
Notebook Basic 15
HT-1000
Very Best Screens
- 30 x 18 x 3 cmButton
+ 30 x 18 x 3 cmCalculate
4.2 KG
956 EUR