Skip to content

Commit 11d6ff6

Browse files
committed
feat(ui5-table-cell): merged property added
- New merged boolean property on ui5-table-cell - Row borders refactored: border-top per cell instead of border-bottom per row - First row top border / last row bottom border handled via :first-of-type / :last-of-type - Selection highlight uses box-shadow instead of border-bottom to avoid layout shifts - Table now uses inset-inline-end/start, the old left/right + :dir(rtl) removed - When merged, cell's top border becomes transparent and content is hidden - Merging is disabled on hover/focus, content reveals with an opacity transition - Merging is also disabled when the row enters popin mode - Implemented via CSS Space Toggle trick, no JS needed - When a selection column is present, its border syncs with the first merged cell - New TableCell website sample added for merged cells - Partially fixes #7238 - CPOUIFTEAMB-2624
1 parent 1c9398e commit 11d6ff6

9 files changed

Lines changed: 336 additions & 95 deletions

File tree

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

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import Title from "../../src/Title.js";
1313
import Slider from "../../src/Slider.js";
1414
import Button from "../../src/Button.js";
1515

16+
const TRANSPARENT = "rgba(0, 0, 0, 0)";
1617

1718
describe("Table - Rendering", () => {
1819
function checkWidth(id: string, expectedWidth: number) {
@@ -1115,21 +1116,20 @@ describe("Table - Cell Merging", () => {
11151116
mountMergedTable();
11161117

11171118
// Merged cell should have transparent top border
1118-
cy.get("#r2cA").should("have.css", "border-top-color", "rgba(0, 0, 0, 0)");
1119+
cy.get("#r2cA").should("have.css", "border-top-color", TRANSPARENT);
11191120
cy.get("#r2cA").find("ui5-label").should("have.css", "opacity", "0");
1120-
cy.get("#r2cA").find("ui5-label").should("have.css", "pointer-events", "none");
11211121

11221122
// Non-merged cell should not have transparent border
1123-
cy.get("#r2cB").should("not.have.css", "border-top-color", "rgba(0, 0, 0, 0)");
1123+
cy.get("#r2cB").should("not.have.css", "border-top-color", TRANSPARENT);
11241124
cy.get("#r2cB").find("ui5-label").should("have.css", "opacity", "1");
11251125

11261126
// Selection cell should have transparent border when first cell is merged
11271127
cy.get("#row2").shadow().find("#selection-cell").should("have.attr", "data-border-merged");
1128-
cy.get("#row2").shadow().find("#selection-cell").should("have.css", "border-top-color", "rgba(0, 0, 0, 0)");
1128+
cy.get("#row2").shadow().find("#selection-cell").should("have.css", "border-top-color", TRANSPARENT);
11291129

11301130
// Selection cell should NOT have transparent border when first cell is not merged
11311131
cy.get("#row1").shadow().find("#selection-cell").should("not.have.attr", "data-border-merged");
1132-
cy.get("#row1").shadow().find("#selection-cell").should("not.have.css", "border-top-color", "rgba(0, 0, 0, 0)");
1132+
cy.get("#row1").shadow().find("#selection-cell").should("not.have.css", "border-top-color", TRANSPARENT);
11331133
});
11341134

11351135
it("should disable merged styles when row has popin", () => {
@@ -1138,29 +1138,28 @@ describe("Table - Cell Merging", () => {
11381138
// At full width, merged styles should be active
11391139
cy.get("ui5-table").invoke("css", "width", "600px");
11401140
cy.get("#r2cA").find("ui5-label").should("have.css", "opacity", "0");
1141-
cy.get("#r2cA").should("have.css", "border-top-color", "rgba(0, 0, 0, 0)");
1142-
cy.get("#row2").shadow().find("#selection-cell").should("have.css", "border-top-color", "rgba(0, 0, 0, 0)");
1141+
cy.get("#r2cA").should("have.css", "border-top-color", TRANSPARENT);
1142+
cy.get("#row2").shadow().find("#selection-cell").should("have.css", "border-top-color", TRANSPARENT);
11431143

11441144
// Shrink table to trigger popin
11451145
cy.get("ui5-table").invoke("css", "width", "250px");
11461146
cy.wait(50);
11471147

11481148
// Merged cell border should fall back to normal border color (not transparent)
11491149
cy.get("#row2").should("have.attr", "_haspopin");
1150-
cy.get("#r2cA").should("not.have.css", "border-top-color", "rgba(0, 0, 0, 0)");
1151-
cy.get("#row2").shadow().find("#selection-cell").should("not.have.css", "border-top-color", "rgba(0, 0, 0, 0)");
1150+
cy.get("#r2cA").should("not.have.css", "border-top-color", TRANSPARENT);
1151+
cy.get("#row2").shadow().find("#selection-cell").should("not.have.css", "border-top-color", TRANSPARENT);
11521152

11531153
// Merged cell content should be fully visible (opacity back to 1)
11541154
cy.get("#r2cA").find("ui5-label").should("have.css", "opacity", "1");
1155-
cy.get("#r2cA").find("ui5-label").should("have.css", "pointer-events", "auto");
11561155

11571156
// Expand table again, merged styles should re-activate
11581157
cy.get("ui5-table").invoke("css", "width", "600px");
11591158
cy.wait(50);
11601159

11611160
cy.get("#r2cA").find("ui5-label").should("have.css", "opacity", "0");
1162-
cy.get("#r2cA").should("have.css", "border-top-color", "rgba(0, 0, 0, 0)");
1163-
cy.get("#row2").shadow().find("#selection-cell").should("have.css", "border-top-color", "rgba(0, 0, 0, 0)");
1161+
cy.get("#r2cA").should("have.css", "border-top-color", TRANSPARENT);
1162+
cy.get("#row2").shadow().find("#selection-cell").should("have.css", "border-top-color", TRANSPARENT);
11641163
});
11651164

11661165
it("should toggle merged styles at runtime", () => {
@@ -1172,31 +1171,25 @@ describe("Table - Cell Merging", () => {
11721171
// Remove merged attribute
11731172
cy.get("#r3cA").invoke("removeAttr", "merged");
11741173
cy.get("#r3cA").find("ui5-label").should("have.css", "opacity", "1");
1175-
cy.get("#r3cA").should("not.have.css", "border-top-color", "rgba(0, 0, 0, 0)");
1174+
cy.get("#r3cA").should("not.have.css", "border-top-color", TRANSPARENT);
11761175

11771176
// Re-add merged attribute
11781177
cy.get("#r3cA").invoke("prop", "merged", true);
11791178
cy.get("#r3cA").find("ui5-label").should("have.css", "opacity", "0");
1180-
cy.get("#r3cA").should("have.css", "border-top-color", "rgba(0, 0, 0, 0)");
1179+
cy.get("#r3cA").should("have.css", "border-top-color", TRANSPARENT);
11811180
});
11821181

1183-
it("should disable merged styles on hover and focus", () => {
1182+
it("should disable merged styles on focus", () => {
11841183
mountMergedTable();
11851184

11861185
// Before hover: merged styles active
11871186
cy.get("#r2cA").find("ui5-label").should("have.css", "opacity", "0");
1188-
cy.get("#r2cA").should("have.css", "border-top-color", "rgba(0, 0, 0, 0)");
1187+
cy.get("#r2cA").should("have.css", "border-top-color", TRANSPARENT);
11891188

1190-
// On hover: merged cell content should become visible and border should restore
1191-
cy.get("#row2").realHover();
1189+
// On focus: merged cell content should become visible but border should remain transparent
1190+
cy.get("#row2").realClick();
11921191
cy.get("#r2cA").find("ui5-label").should("not.have.css", "opacity", "0");
1193-
cy.get("#r2cA").should("not.have.css", "border-top-color", "rgba(0, 0, 0, 0)");
1194-
cy.get("#row2").shadow().find("#selection-cell").should("not.have.css", "border-top-color", "rgba(0, 0, 0, 0)");
1195-
1196-
// On focus: merged cell content should become visible and border should restore
1197-
cy.get("#row3").realClick();
1198-
cy.get("#r3cA").find("ui5-label").should("not.have.css", "opacity", "0");
1199-
cy.get("#r3cA").should("not.have.css", "border-top-color", "rgba(0, 0, 0, 0)");
1200-
cy.get("#row3").shadow().find("#selection-cell").should("not.have.css", "border-top-color", "rgba(0, 0, 0, 0)");
1192+
cy.get("#r2cA").should("have.css", "border-top-color", TRANSPARENT);
1193+
cy.get("#row2").shadow().find("#selection-cell").should("have.css", "border-top-color", TRANSPARENT);
12011194
});
12021195
});

packages/main/src/TableCell.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ class TableCell extends TableCellBase {
3535
* Defines whether the cell is visually merged with the cell directly above it.
3636
*
3737
* This is useful when consecutive cells in a column have the same value and should visually appear as a single merged cell.
38-
*
39-
* **Note:** This feature is disabled when cells are rendered as popin, and should remain `false` for interactive cell content.
38+
* **Note:** This feature is disabled when cells are rendered as popin.
4039
*
4140
* @default false
4241
* @since 2.21.0

packages/main/src/themes/TableCell.css

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44

55
:host([merged]),
66
:host([data-border-merged]) {
7-
--_ui5_table_cell_merged_border_color: var(--_ui5_table_cell_merged) transparent;
7+
--_ui5_table_cell_merged_border_color: var(--_ui5_table_cell_border_merged) transparent;
88
border-top-color: var(--_ui5_table_cell_merged_border_color, var(--sapList_BorderColor));
99
}
1010

1111
:host([merged]) ::slotted(*) {
12-
interactivity: var(--_ui5_table_cell_merged) inert;
13-
pointer-events: var(--_ui5_table_cell_merged) none;
14-
opacity: var(--_ui5_table_cell_merged) 0;
12+
--_ui5_table_cell_merged_content_opacity: var(--_ui5_table_cell_content_merged) 0;
13+
opacity: var(--_ui5_table_cell_merged_content_opacity, 1);
1514
transition: opacity 300ms ease;
1615
}
1716

packages/main/src/themes/TableRow.css

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,14 @@
2222

2323
:host(:not([_haspopin])) {
2424
/* Use CSS Space Toggles until if() or container style queries are widely supported */
25-
--_ui5_table_cell_merged: ;
25+
--_ui5_table_cell_border_merged: ;
26+
--_ui5_table_cell_content_merged: ;
2627
}
2728

28-
:host(:not([_haspopin]):hover),
29+
:host(:not([_haspopin]):active),
2930
:host(:not([_haspopin]):focus-within) {
3031
/* Provide a valid CSS value to intentionally invalidate the TableCell variable-based rules and disable visual merging. */
31-
--_ui5_table_cell_merged: initial;
32+
--_ui5_table_cell_content_merged: initial;
3233
}
3334

3435
@media (hover: hover) {
@@ -38,6 +39,10 @@
3839
:host([_interactive][aria-selected=true]:hover) {
3940
background: var(--sapList_Hover_SelectionBackground);
4041
}
42+
:host(:not([_haspopin]):hover) {
43+
/* Provide a valid CSS value to intentionally invalidate the TableCell variable-based rules and disable visual merging. */
44+
--_ui5_table_cell_content_merged: initial;
45+
}
4146
}
4247

4348
:host([_interactive][_active]),

packages/main/test/pages/Table_Acc.html

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ <h1>ACC Test Page for Table</h1>
9191

9292
<ui5-table-row row-key="row2" navigated interactive>
9393
<ui5-table-cell merged><div><b><ui5-text>USB-C Hub Adapter</ui5-text></b><ui5-link href="#">Product ID HB-5012</ui5-link></div></ui5-table-cell>
94-
<ui5-table-cell merged><ui5-text>ChargeIt</ui5-text></ui5-table-cell>
94+
<ui5-table-cell><ui5-text>ChargeIt</ui5-text></ui5-table-cell>
9595
<ui5-table-cell><ui5-text>Connectivity</ui5-text></ui5-table-cell>
9696
<ui5-table-cell><b><ui5-text style="color:#b14e03" accessible-name="Warning">Low Stock</ui5-text></b></ui5-table-cell>
9797
<ui5-table-cell><b><ui5-text>34.50</ui5-text></b><ui5-text>&nbsp;EUR</ui5-text></ui5-table-cell>
@@ -102,8 +102,8 @@ <h1>ACC Test Page for Table</h1>
102102
</ui5-table-row>
103103

104104
<ui5-table-row row-key="row3">
105-
<ui5-table-cell><div><b><ui5-text>Bluetooth Speaker Mini</ui5-text></b><ui5-link href="#">Product ID BS-2203</ui5-link></div></ui5-table-cell>
106-
<ui5-table-cell merged><ui5-text>ChargeIt</ui5-text></ui5-table-cell>
105+
<ui5-table-cell merged><div><b><ui5-text>Bluetooth Speaker Mini</ui5-text></b><ui5-link href="#">Product ID BS-2203</ui5-link></div></ui5-table-cell>
106+
<ui5-table-cell><ui5-text>ChargeIt</ui5-text></ui5-table-cell>
107107
<ui5-table-cell><ui5-text>Audio</ui5-text></ui5-table-cell>
108108
<ui5-table-cell><b><ui5-text style="color:#126d2c" accessible-name="Success">In Stock</ui5-text></b></ui5-table-cell>
109109
<ui5-table-cell><b><ui5-text>59.99</ui5-text></b><ui5-text>&nbsp;EUR</ui5-text></ui5-table-cell>
@@ -113,9 +113,9 @@ <h1>ACC Test Page for Table</h1>
113113

114114
<ui5-table-row row-key="row4" interactive>
115115
<ui5-table-cell><div><b><ui5-text>Wireless Mouse Pro</ui5-text></b><ui5-link href="#">Product ID MT-2001</ui5-link></div></ui5-table-cell>
116-
<ui5-table-cell merged><ui5-text>ChargeIt</ui5-text></ui5-table-cell>
116+
<ui5-table-cell><ui5-text>ChargeIt</ui5-text></ui5-table-cell>
117117
<ui5-table-cell><ui5-text>Accessories</ui5-text></ui5-table-cell>
118-
<ui5-table-cell merged><b><ui5-text style="color:#126d2c" accessible-name="Success">In Stock</ui5-text></b></ui5-table-cell>
118+
<ui5-table-cell><b><ui5-text style="color:#126d2c" accessible-name="Success">In Stock</ui5-text></b></ui5-table-cell>
119119
<ui5-table-cell><b><ui5-text>45.99</ui5-text></b><ui5-text>&nbsp;EUR</ui5-text></ui5-table-cell>
120120
<ui5-table-cell><b><ui5-text>0.10</ui5-text></b><ui5-text>&nbsp;kg</ui5-text></ui5-table-cell>
121121
<ui5-table-cell><ui5-toggle-button id="input-row4_1" accessible-name-ref="customInputCol">Favourite</ui5-toggle-button><ui5-button id="input-row4_2" accessible-name-ref="customInputCol">Order</ui5-button></ui5-table-cell>
@@ -126,9 +126,9 @@ <h1>ACC Test Page for Table</h1>
126126

127127
<ui5-table-row row-key="row5">
128128
<ui5-table-cell><div><b><ui5-text>4K Webcam HD</ui5-text></b><ui5-link href="#">Product ID WC-4001</ui5-link></div></ui5-table-cell>
129-
<ui5-table-cell merged><ui5-text>ChargeIt</ui5-text></ui5-table-cell>
129+
<ui5-table-cell><ui5-text>ChargeIt</ui5-text></ui5-table-cell>
130130
<ui5-table-cell><ui5-text>Video Devices</ui5-text></ui5-table-cell>
131-
<ui5-table-cell merged><b><ui5-text style="color:#126d2c" accessible-name="Success">In Stock</ui5-text></b></ui5-table-cell>
131+
<ui5-table-cell><b><ui5-text style="color:#126d2c" accessible-name="Success">In Stock</ui5-text></b></ui5-table-cell>
132132
<ui5-table-cell><b><ui5-text>89.99</ui5-text></b><ui5-text>&nbsp;EUR</ui5-text></ui5-table-cell>
133133
<ui5-table-cell><b><ui5-text>0.22</ui5-text></b><ui5-text>&nbsp;kg</ui5-text></ui5-table-cell>
134134
<ui5-table-cell><ui5-checkbox id="input-row5" checked accessible-name-ref="customInputCol"></ui5-checkbox></ui5-table-cell>

packages/website/docs/_components_pages/main/Table/TableCell.mdx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,4 @@ To ensure usability, merged cell styling is automatically disabled in these case
3434
- **Popin mode:** When the table is narrow and cells are rendered in the popin area, merged styling is removed so each row's content remains visible.
3535
- **Hover and focus:** When the user hovers over or focuses within the row, merged styling is temporarily removed, making the merged content visible.
3636

37-
**Note:** Avoid using `merged` on cells that contain interactive content (e.g., buttons, inputs, or links).
38-
3937
<MergedCells />

packages/website/docs/_samples/main/Table/MergedCells/main.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import "@ui5/webcomponents/dist/TableHeaderRow.js";
33
import "@ui5/webcomponents/dist/TableHeaderCell.js";
44
import "@ui5/webcomponents/dist/TableSelectionMulti.js";
55
import "@ui5/webcomponents/dist/Label.js";
6+
import "@ui5/webcomponents/dist/Text.js";
67
import "@ui5/webcomponents/dist/Bar.js";
78
import "@ui5/webcomponents/dist/SegmentedButton.js";
89

0 commit comments

Comments
 (0)