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:
+ *
+ * None
+ * SingleSelect
+ * MultiSelect
+ *
+ * @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
+ *
+ *
+ *
+ * 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:
+ *
+ * - group-row - Used to style the native
tr
tag element.
+ *
+ *
+ * @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)
+
+
+
+
+
+
+
+ City
+
+
+ Supplier
+
+
+ Country
+
+
+ Country: Bulgaria
+
+
+ Sofia
+ Company 1
+ Bulgaria
+
+
+ Plovdiv
+ Company 2
+ Bulgaria
+
+
+ Country: USA
+
+
+ Denver
+ Company 3
+ USA
+
+
+ Boston
+ Company 4
+ USA
+
+
+
+
+
+
+
+ City
+
+
+ Supplier
+
+
+ Country
+
+
+ Country: Bulgaria
+
+
+ Sofia
+ Company 1
+ Bulgaria
+
+
+ Plovdiv
+ Company 2
+ Bulgaria
+
+
+ Country: USA
+
+
+ Denver
+ Company 3
+ USA
+
+
+ Boston
+ Company 4
+ USA
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+ City
+
+
+ Supplier
+
+
+ Country
+
+
+ Country: Bulgaria
+
+
+ Sofia
+ Company 1
+ Bulgaria
+
+
+ Plovdiv
+ Company 2
+ Bulgaria
+
+
+ Country: USA
+
+
+ Denver
+ Company 3
+ USA
+
+
+ Boston
+ Company 4
+ USA
+
+
+
+
+
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",