diff --git a/packages/main/bundle.common.js b/packages/main/bundle.common.js index 2b0b47eb9cef..8f0dbae5a156 100644 --- a/packages/main/bundle.common.js +++ b/packages/main/bundle.common.js @@ -74,6 +74,7 @@ import TabSeparator from "./dist/TabSeparator.js"; import Table from "./dist/Table.js"; import TableColumn from "./dist/TableColumn.js"; import TableRow from "./dist/TableRow.js"; +import TableGroupRow from "./dist/TableGroupRow.js"; import TableCell from "./dist/TableCell.js"; import TextArea from "./dist/TextArea.js"; import TimeSelection from "./dist/TimeSelection.js"; diff --git a/packages/main/src/Table.js b/packages/main/src/Table.js index c3dfc8138149..7f807d56ed0b 100644 --- a/packages/main/src/Table.js +++ b/packages/main/src/Table.js @@ -18,6 +18,7 @@ import { LOAD_MORE_TEXT, ARIA_LABEL_SELECT_ALL_CHECKBOX, TABLE_HEADER_ROW_TEXT, + TABLE_ROW_POSITION, } from "./generated/i18n/i18n-defaults.js"; // Template @@ -377,7 +378,7 @@ const metadata = { * @alias sap.ui.webcomponents.main.Table * @extends sap.ui.webcomponents.base.UI5Element * @tagname ui5-table - * @appenddocs TableColumn TableRow TableCell + * @appenddocs TableColumn TableRow TableGroupRow TableCell * @public */ class Table extends UI5Element { @@ -433,13 +434,15 @@ class Table extends UI5Element { onBeforeRendering() { const columnSettings = this.getColumnPropagationSettings(); const columnSettingsString = JSON.stringify(columnSettings); + const rowsCount = this.rows.length; - this.rows.forEach(row => { + this.rows.forEach((row, index) => { if (row._columnsInfoString !== columnSettingsString) { row._columnsInfo = columnSettings; row._columnsInfoString = JSON.stringify(row._columnsInfo); } + row._ariaPosition = this.i18nBundle.getText(TABLE_ROW_POSITION, index + 1, rowsCount); row._busy = this.busy; row.removeEventListener("ui5-_focused", this.fnOnRowFocused); row.addEventListener("ui5-_focused", this.fnOnRowFocused); diff --git a/packages/main/src/TableGroupRow.hbs b/packages/main/src/TableGroupRow.hbs new file mode 100644 index 000000000000..7f2a59a84499 --- /dev/null +++ b/packages/main/src/TableGroupRow.hbs @@ -0,0 +1,12 @@ + + + + + diff --git a/packages/main/src/TableGroupRow.js b/packages/main/src/TableGroupRow.js new file mode 100644 index 000000000000..fc9b33db702d --- /dev/null +++ b/packages/main/src/TableGroupRow.js @@ -0,0 +1,153 @@ +import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; +import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js"; +import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js"; +import TableGroupRowTemplate from "./generated/templates/TableGroupRowTemplate.lit.js"; +import TableMode from "./types/TableMode.js"; + +// Texts +import { + TABLE_GROUP_ROW_ARIA_LABEL, +} from "./generated/i18n/i18n-defaults.js"; + +// Styles +import styles from "./generated/themes/TableGroupRow.css.js"; + +/** + * @public + */ +const metadata = { + tag: "ui5-table-group-row", + slots: /** @lends sap.ui.webcomponents.main.TableGroupRow.prototype */ { + /** + * Defines the text of the component. + *
+ * Note: Although this slot accepts HTML Elements, it is strongly recommended that you only use text in order to preserve the intended design. + * + * @type {Node[]} + * @slot + * @public + */ + "default": { + type: Node, + }, + }, + properties: /** @lends sap.ui.webcomponents.main.TableGroupRow.prototype */ { + /** + * Defines the mode of the row + * + *

+ * Note: + * Available options are: + * + * @type {TableMode} + * @defaultvalue "None" + * @private + */ + mode: { + type: TableMode, + defaultValue: TableMode.None, + }, + _columnsInfo: { + type: Object, + multiple: true, + }, + _tabIndex: { + type: String, + defaultValue: "-1", + }, + _busy: { + type: Boolean, + }, + _ariaPosition: { + type: String, + defaultValue: "", + noAttribute: true, + }, + }, +}; + +/** + * @class + * + *

Overview

+ * + * The ui5-table-group-row component represents a group row in the ui5-table. + * + *

CSS Shadow Parts

+ * + * CSS Shadow Parts allow developers to style elements inside the Shadow DOM. + *
+ * The ui5-table-group-row exposes the following CSS Shadow Parts: + * + * + * @constructor + * @author SAP SE + * @alias sap.ui.webcomponents.main.TableGroupRow + * @extends sap.ui.webcomponents.base.UI5Element + * @tagname ui5-table-group-row + * @since 1.0.0-rc.15 + * @public + */ +class TableGroupRow extends UI5Element { + static get metadata() { + return metadata; + } + + static get styles() { + return styles; + } + + static get render() { + return litRender; + } + + static get template() { + return TableGroupRowTemplate; + } + + constructor() { + super(); + this.i18nBundle = getI18nBundle("@ui5/webcomponents"); + } + + get colSpan() { + return this._colSpan; + } + + get ariaLabelText() { + return `${this.i18nBundle.getText(TABLE_GROUP_ROW_ARIA_LABEL)} ${this.innerText}. ${this._ariaPosition}`; + } + + visibleColCount() { + let count = this._columnsInfo.reduce((acc, column) => { + return column.visible ? ++acc : acc; + }, 0); + + if (this.mode === TableMode.MultiSelect) { + count++; + } + + return count; + } + + onBeforeRendering() { + if (!this._columnsInfo || this._columnsInfo.length === 0) { + return; + } + this._colSpan = this.visibleColCount(); + } + + static async onDefine() { + await fetchI18nBundle("@ui5/webcomponents"); + } +} + +TableGroupRow.define(); + +export default TableGroupRow; diff --git a/packages/main/src/TableRow.js b/packages/main/src/TableRow.js index 4cb40d3c6e1b..95f54be774f3 100644 --- a/packages/main/src/TableRow.js +++ b/packages/main/src/TableRow.js @@ -101,6 +101,11 @@ const metadata = { _busy: { type: Boolean, }, + _ariaPosition: { + type: String, + defaultValue: "", + noAttribute: true, + }, }, events: /** @lends sap.ui.webcomponents.main.TableRow.prototype */ { /** @@ -341,11 +346,12 @@ class TableRow extends UI5Element { } get ariaLabelText() { - return this.cells.map((cell, index) => { + const ariaLabel = this.cells.map((cell, index) => { const columText = this.getColumnTextByIdx(index); const cellText = this.getCellText(cell); return `${columText} ${cellText}`; }).join(" "); + return `${ariaLabel}. ${this._ariaPosition}`; } get ariaLabelRowSelection() { diff --git a/packages/main/src/i18n/messagebundle.properties b/packages/main/src/i18n/messagebundle.properties index ed43c9d7fded..b419cacd5320 100644 --- a/packages/main/src/i18n/messagebundle.properties +++ b/packages/main/src/i18n/messagebundle.properties @@ -187,6 +187,12 @@ LOAD_MORE_TEXT=More #XTXT Table's header row text. TABLE_HEADER_ROW_TEXT=Header Row +#XACT Table row's position +TABLE_ROW_POSITION={0} of {1} + +#XACT Table group row's aria label +TABLE_GROUP_ROW_ARIA_LABEL=Group header row. + #XACT ARIA label for selection checkbox ARIA_LABEL_ROW_SELECTION=Item selection diff --git a/packages/main/src/themes/TableGroupRow.css b/packages/main/src/themes/TableGroupRow.css new file mode 100644 index 000000000000..ce8857079c82 --- /dev/null +++ b/packages/main/src/themes/TableGroupRow.css @@ -0,0 +1,34 @@ +:host { + display: contents; +} + +:host([_busy]) .ui5-table-group-row-root { + opacity: 0.72; + pointer-events: none; +} + +.ui5-table-group-row-root { + height: 2rem; + border-bottom: 1px solid var(--sapList_TableGroupHeaderBorderColor); + background-color: var(--sapList_TableGroupHeaderBackground); + color: var(--sapList_TableGroupHeaderTextColor); + font-family: "72override", var(--sapFontFamily); + font-size: var(--sapFontSize); + font-weight: normal; +} + +.ui5-table-group-row-root:focus { + outline: var(--ui5_table_row_outline_width) dotted var(--sapContent_FocusColor); + outline-offset: -0.0625rem; +} + +td { + word-break: break-word; + vertical-align: middle; + padding: .5rem .25rem .5rem 1rem; + text-align: left; +} + +:host [dir="rtl"] td { + text-align: right; +} \ No newline at end of file diff --git a/packages/main/test/pages/TableGrouping.html b/packages/main/test/pages/TableGrouping.html new file mode 100644 index 000000000000..72d363545cc5 --- /dev/null +++ b/packages/main/test/pages/TableGrouping.html @@ -0,0 +1,107 @@ + + + + + + + Web components Table + + + + + + + + + + + + Table with grouping: + + + City + + + Supplier + + +
+ Country +
+
+ + Country: Bulgaria + + + Sofia + Sirenje EOOD + Bulgaria + + + Plovdiv + Kashkavali AD + Bulgaria + + Country: USA + + Dublin + J.M. Brothers + USA + + + Boston + J.M. Brothers + USA + +
+ +
+
+
+ + MultiSelect Table with grouping: + + + City + + + Supplier + + +
+ Country +
+
+ + Country: Bulgaria + + Sofia + Sirenje EOOD + Bulgaria + + + Plovdiv + Kashkavali AD + Bulgaria + + Country: USA + + Dublin + J.M. Brothers + USA + + + Boston + J.M. Brothers + USA + +
+ + + + + diff --git a/packages/main/test/samples/Table.sample.html b/packages/main/test/samples/Table.sample.html index 8d16855dd464..e99023eed831 100644 --- a/packages/main/test/samples/Table.sample.html +++ b/packages/main/test/samples/Table.sample.html @@ -535,21 +535,21 @@

Growing Table with "More" button

Product - + Supplier - +
Dimensions
- + Weight - + Price @@ -856,21 +856,21 @@

Growing Table on Scroll

Product - + Supplier - +
Dimensions
- + Weight - + Price @@ -1470,4 +1470,177 @@

Growing Table on Scroll

+
+

Tables with grouping (SingleSelect)

+ +
+ +
+ The ui5-table-group-row allows visual grouping of the table rows. +
+ + + + City + + + Supplier + + + Country + + + Country: Bulgaria + + + Sofia + Company 1 + Bulgaria + + + Plovdiv + Company 2 + Bulgaria + + + Country: USA + + + Denver + Company 3 + USA + + + Boston + Company 4 + USA + + +
+ +

+<ui5-table class="demo-table-single">
+	<ui5-table-column id="column-1" slot="columns">
+		<ui5-label>City</ui5-label>
+	</ui5-table-column>
+	<ui5-table-column id="column-2" slot="columns" min-width="500" popin-text="Supplier">
+		<ui5-label>Supplier</ui5-label>
+	</ui5-table-column>
+	<ui5-table-column id="column-3" slot="columns" min-width="500" popin-text="Country" demand-popin>
+		<ui5-label>Country</ui5-label>
+	</ui5-table-column>
+
+	<ui5-table-group-row>Country: Bulgaria</ui5-table-group-row>
+
+	<ui5-table-row>
+		<ui5-table-cell><span>Sofia</span></ui5-table-cell>
+		<ui5-table-cell><span>Company 1</span></ui5-table-cell>
+		<ui5-table-cell><span>Bulgaria</span></ui5-table-cell>
+	</ui5-table-row>
+	<ui5-table-row>
+		<ui5-table-cell><span>Plovdiv</span></ui5-table-cell>
+		<ui5-table-cell><span>Company 2</span></ui5-table-cell>
+		<ui5-table-cell><span>Bulgaria</span></ui5-table-cell>
+	</ui5-table-row>
+
+	<ui5-table-group-row><span>Country: USA</span></ui5-table-group-row>
+
+	<ui5-table-row>
+		<ui5-table-cell><span>Denver</span></ui5-table-cell>
+		<ui5-table-cell><span>Company 3</span></ui5-table-cell>
+		<ui5-table-cell><span>USA</span></ui5-table-cell>
+	</ui5-table-row>
+	<ui5-table-row>
+		<ui5-table-cell><span>Boston</span></ui5-table-cell>
+		<ui5-table-cell><span>Company 4</span></ui5-table-cell>
+		<ui5-table-cell><span>USA</span></ui5-table-cell>
+	</ui5-table-row>
+</ui5-table>
+	
+
+ +
+

Tables with grouping (MultiSelect)

+ +
+ + + City + + + Supplier + + + Country + + + Country: Bulgaria + + + Sofia + Company 1 + Bulgaria + + + Plovdiv + Company 2 + Bulgaria + + + Country: USA + + + Denver + Company 3 + USA + + + Boston + Company 4 + USA + + +
+ +

+<ui5-table class="demo-table-multi" mode="MultiSelect">
+	<ui5-table-column id="column-1" slot="columns">
+		<ui5-label>City</ui5-label>
+	</ui5-table-column>
+	<ui5-table-column id="column-2" slot="columns" min-width="500" popin-text="Supplier">
+		<ui5-label>Supplier</ui5-label>
+	</ui5-table-column>
+	<ui5-table-column id="column-3" slot="columns" min-width="500" popin-text="Country" demand-popin>
+		<ui5-label>Country</ui5-label>
+	</ui5-table-column>
+
+	<ui5-table-group-row>Country: Bulgaria</ui5-table-group-row>
+
+	<ui5-table-row>
+		<ui5-table-cell><span>Sofia</span></ui5-table-cell>
+		<ui5-table-cell><span>Company 1</span></ui5-table-cell>
+		<ui5-table-cell><span>Bulgaria</span></ui5-table-cell>
+	</ui5-table-row>
+	<ui5-table-row>
+		<ui5-table-cell><span>Plovdiv</span></ui5-table-cell>
+		<ui5-table-cell><span>Company 2</span></ui5-table-cell>
+		<ui5-table-cell><span>Bulgaria</span></ui5-table-cell>
+	</ui5-table-row>
+
+	<ui5-table-group-row><span>Country: USA</span></ui5-table-group-row>
+
+	<ui5-table-row>
+		<ui5-table-cell><span>Denver</span></ui5-table-cell>
+		<ui5-table-cell><span>Company 3</span></ui5-table-cell>
+		<ui5-table-cell><span>USA</span></ui5-table-cell>
+	</ui5-table-row>
+	<ui5-table-row>
+		<ui5-table-cell><span>Boston</span></ui5-table-cell>
+		<ui5-table-cell><span>Company 4</span></ui5-table-cell>
+		<ui5-table-cell><span>USA</span></ui5-table-cell>
+	</ui5-table-row>
+</ui5-table>
+	
+
+ diff --git a/packages/main/test/specs/Table.spec.js b/packages/main/test/specs/Table.spec.js index e60bb3244d72..9235075f1fec 100644 --- a/packages/main/test/specs/Table.spec.js +++ b/packages/main/test/specs/Table.spec.js @@ -69,7 +69,7 @@ describe("Table general interaction", () => { it("tests row aria-label value", () => { const row = browser.$("#roll-0").shadow$(".ui5-table-row-root"); - const EXPECTED_TEXT = "Product Notebook Basic 15HT-1000 Supplier Very Best Screens Dimensions 30 x 18 x 3 cm Weight 4.2 KG Price 956 EUR"; + const EXPECTED_TEXT = "Product Notebook Basic 15HT-1000 Supplier Very Best Screens Dimensions 30 x 18 x 3 cm Weight 4.2 KG Price 956 EUR. 1 of 5"; assert.strictEqual(row.getAttribute("aria-label"), EXPECTED_TEXT, "The aria-label value is correct."); @@ -193,7 +193,7 @@ describe("Table general interaction", () => { // test row-click and selection-change events on Enter key activation over an already selected row assert.strictEqual(rowClickCount.getProperty("value"), "3", "Enter key over an already selected row should trigger row-click event"); assert.strictEqual(selectionChangeCount.getProperty("value"), "3", "Enter key over an already selected row should not trigger selection-change event"); - + // act browser.keys("ArrowDown"); browser.keys("ArrowDown"); @@ -333,7 +333,7 @@ describe("Table general interaction", () => { // act checkBoxFirstCell.keys("Space"); - + // test Space key over the selection checkbox of already selected row in MultiSelect mode assert.strictEqual(rowClickCount.getProperty("value"), "2", "Space key over the selection checkbox should not trigger row-click event"); assert.strictEqual(selectionChangeCount.getProperty("value"), "3", "Space key over the selection checkbox of already selected row should trigger selection-change event"); diff --git a/packages/main/test/specs/TableGrouping.spec.js b/packages/main/test/specs/TableGrouping.spec.js new file mode 100644 index 000000000000..827560b899ec --- /dev/null +++ b/packages/main/test/specs/TableGrouping.spec.js @@ -0,0 +1,32 @@ +const assert = require("chai").assert; +const PORT = require("./_port.js"); + +describe("Table general interaction", () => { + before(() => { + browser.url(`http://localhost:${PORT}/test-resources/pages/TableGrouping.html`); + }); + + it("Table group rows should be rendered", () => { + const groupRows = browser.$$("ui5-table-group-row"); + assert.strictEqual(groupRows.length, 4, "There should be 4 group rows rendered."); + }); + + it("Colspan attribute should be calculated correctly for multi and single table modes.", () => { + const groupRows = browser.$$("ui5-table-group-row"); + const colSpanSingleMode = groupRows[0].shadow$("td").getAttribute("colspan"); + const colSpanMultiMode = groupRows[2].shadow$("td").getAttribute("colspan"); + assert.strictEqual(colSpanSingleMode, '3', "The first table should have attribute 'colspan' of 3 on the group row's 'td' element"); + assert.strictEqual(colSpanMultiMode, '4', "The second table should have attribute 'colspan' of 4 on the group row's 'td' element"); + }); + + it("ARIA - aria-label should be calculated correctly.", () => { + const groupRows = browser.$$("ui5-table-group-row"); + const ariaText1 = "Group header row. Country: Bulgaria. 1 of 6"; + const ariaLabel1 = groupRows[0].shadow$("tr").getAttribute("aria-label"); + const ariaText2 = "Group header row. Country: USA. 4 of 6"; + const ariaLabel2 = groupRows[1].shadow$("tr").getAttribute("aria-label"); + + assert.strictEqual(ariaLabel1, ariaText1, "Initially the aria-label is set correctly."); + assert.strictEqual(ariaLabel2, ariaText2, "Initially the aria-label is set correctly."); + }); +}); \ No newline at end of file diff --git a/packages/theme-base/css-vars-usage.json b/packages/theme-base/css-vars-usage.json index b8c91cb9f0be..82641390812a 100644 --- a/packages/theme-base/css-vars-usage.json +++ b/packages/theme-base/css-vars-usage.json @@ -177,6 +177,7 @@ "--sapList_Hover_SelectionBackground", "--sapList_SelectionBackgroundColor", "--sapList_SelectionBorderColor", + "--sapList_TableGroupHeaderBackground", "--sapList_TableGroupHeaderBorderColor", "--sapList_TableGroupHeaderTextColor", "--sapList_TextColor",