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