diff --git a/packages/main/cypress/specs/Table.cy.tsx b/packages/main/cypress/specs/Table.cy.tsx
index 33f00cb737a5..15038f21ebf4 100644
--- a/packages/main/cypress/specs/Table.cy.tsx
+++ b/packages/main/cypress/specs/Table.cy.tsx
@@ -8,6 +8,7 @@ import TableHeaderCell from "../../src/TableHeaderCell.js";
import TableHeaderCellActionAI from "../../src/TableHeaderCellActionAI.js";
import Label from "../../src/Label.js";
import Input from "../../src/Input.js";
+import TableRowAction from "../../src/TableRowAction.js";
import Bar from "../../src/Bar.js";
import Title from "../../src/Title.js";
import Slider from "../../src/Slider.js";
@@ -429,8 +430,6 @@ describe("Table - Popin Mode", () => {
cy.get("ui5-table").then($table => {
$table.css("width", "150px");
});
-
- // eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(50);
cy.get("ui5-table").then($table => {
@@ -443,7 +442,7 @@ describe("Table - Popin Mode", () => {
for (const cell of row.cells) {
if (cell._popin) {
popinCellCount++;
- const popinText = cell._headerCell.popinText || cell._headerCell.textContent;
+ const popinText = cell._headerCell!.popinText || cell._headerCell!.textContent;
if (cell.shadowRoot!.textContent === `${popinText}:`) {
validPopinTextCount++;
}
@@ -883,26 +882,24 @@ describe("Table - Navigated Rows", () => {
);
- cy.get("#row1")
- .shadow()
- .find("#navigated-cell")
+ // Navigated cell rendered on all rows and header row
+ cy.get("[ui5-table-header-row]").shadow().find("#navigated-cell")
.should("exist")
+ .should("have.attr", "aria-hidden", "true");
+ cy.get("[ui5-table-header-row]").shadow().find("#navigated-cell")
.should("have.attr", "data-excluded-from-navigation");
- cy.get("#row2")
- .shadow()
- .find("#navigated-cell")
+ cy.get("#row1").shadow().find("#navigated-cell")
.should("exist")
.should("have.attr", "data-excluded-from-navigation");
- cy.get("#row1")
- .shadow()
- .find("#navigated")
- .as("navigated1");
+ cy.get("#row2").shadow().find("#navigated-cell")
+ .should("exist")
+ .should("have.attr", "data-excluded-from-navigation");
- cy.get("#row2")
- .shadow()
- .find("#navigated")
+ // Navigated indicator differs between navigated and non-navigated rows
+ cy.get("#row1").shadow().find("#navigated").as("navigated1");
+ cy.get("#row2").shadow().find("#navigated")
.then($navigated2 => {
cy.get("@navigated1")
.should($navigated1 => {
@@ -1146,7 +1143,7 @@ describe("Table - Cell Merging", () => {
cy.wait(50);
// Merged cell border should fall back to normal border color (not transparent)
- cy.get("#row2").should("have.attr", "_haspopin");
+ cy.get("#row2").should("have.attr", "_has-popin");
cy.get("#r2cA").should("not.have.css", "border-top-color", TRANSPARENT);
cy.get("#row2").shadow().find("#selection-cell").should("not.have.css", "border-top-color", TRANSPARENT);
@@ -1193,3 +1190,180 @@ describe("Table - Cell Merging", () => {
cy.get("#row2").shadow().find("#selection-cell").should("have.css", "border-top-color", TRANSPARENT);
});
});
+
+describe("Table - Dummy Cell", () => {
+ it("should render dummy cell with border and nofocus when all columns have fixed widths", () => {
+ cy.mount(
+
+
+ Product
+ Supplier
+
+
+ Product 1
+ Supplier 1
+
+
+ Product 2
+ Supplier 2
+
+
+ );
+
+ cy.get("[ui5-table-header-row]").shadow().find("#dummy-cell").should("exist");
+ cy.get("#row1").shadow().find("#dummy-cell").should("exist");
+
+ // Should have left border
+ cy.get("[ui5-table-header-row]").shadow().find("#dummy-cell")
+ .should("not.have.css", "border-inline-start-width", "0px");
+ cy.get("#row1").shadow().find("#dummy-cell")
+ .should("not.have.css", "border-inline-start-width", "0px");
+
+ // Should have data-excluded-from-navigation="nofocus" and data-border-merged when no popin
+ cy.get("#row1").shadow().find("#dummy-cell")
+ .should("have.attr", "data-excluded-from-navigation", "nofocus")
+ .should("have.attr", "data-border-merged");
+
+ // Should not focus row or fire row-click when clicking dummy cell
+ cy.get("#table").invoke("on", "row-click", cy.stub().as("rowClickHandler"));
+ cy.get("#row2").shadow().find("#dummy-cell").realClick();
+ cy.get("#row2").should("not.be.focused");
+ cy.get("#row2").should("not.have.attr", "_active");
+ cy.get("@rowClickHandler").should("not.have.been.called");
+ });
+
+ it("should not render dummy cell when columns are flexible", () => {
+ const cases = [
+ { widths: ["200px", "auto"] },
+ { widths: ["Auto"] },
+ { widths: ["200px", undefined] },
+ ];
+
+ cases.forEach(({ widths }) => {
+ cy.mount(
+
+
+ {widths.map((w, i) => (
+ {`Col ${i}`}
+ ))}
+
+
+ {widths.map((_, i) => (
+ {`Cell ${i}`}
+ ))}
+
+
+ );
+
+ cy.get("[ui5-table-header-row]").shadow().find("#dummy-cell").should("not.exist");
+ cy.get("#row1").shadow().find("#dummy-cell").should("not.exist");
+ });
+ });
+
+ it("should render dummy cell after actions when no popin and before actions when popin", () => {
+ cy.mount(
+
+
+ Product
+ Supplier
+
+
+ Product 1
+ Supplier 1
+
+
+
+
+ );
+
+ // No popin: dummy cell is after actions cell (rightmost) in both row and header row
+ cy.get("#row1").shadow().then($shadow => {
+ const dummyCell = $shadow.find("#dummy-cell")[0];
+ const actionsCell = $shadow.find("#actions-cell")[0];
+ const position = dummyCell.compareDocumentPosition(actionsCell);
+ expect(position & Node.DOCUMENT_POSITION_PRECEDING).to.be.greaterThan(0);
+ });
+ cy.get("[ui5-table-header-row]").shadow().then($shadow => {
+ const dummyCell = $shadow.find("#dummy-cell")[0];
+ const actionsCell = $shadow.find("#actions-cell")[0];
+ const position = dummyCell.compareDocumentPosition(actionsCell);
+ expect(position & Node.DOCUMENT_POSITION_PRECEDING).to.be.greaterThan(0);
+ });
+
+ // Shrink to trigger popin: dummy cell moves before actions cell
+ cy.get("ui5-table").invoke("css", "width", "250px");
+ cy.wait(50);
+
+ cy.get("#row1").should("have.attr", "_has-popin");
+ cy.get("#row1").shadow().then($shadow => {
+ const dummyCell = $shadow.find("#dummy-cell")[0];
+ const actionsCell = $shadow.find("#actions-cell")[0];
+ const position = dummyCell.compareDocumentPosition(actionsCell);
+ expect(position & Node.DOCUMENT_POSITION_FOLLOWING).to.be.greaterThan(0);
+ });
+ cy.get("[ui5-table-header-row]").shadow().then($shadow => {
+ const dummyCell = $shadow.find("#dummy-cell")[0];
+ const actionsCell = $shadow.find("#actions-cell")[0];
+ const position = dummyCell.compareDocumentPosition(actionsCell);
+ expect(position & Node.DOCUMENT_POSITION_FOLLOWING).to.be.greaterThan(0);
+ });
+ });
+
+ it("should adapt dummy cell border, navigation attribute, and custom focus outline with popin", () => {
+ cy.mount(
+
+
+
+ Product
+ Supplier
+
+
+ Product 1
+ Supplier 1
+
+
+ );
+
+ // At full width: left border visible, nofocus attribute on both row and header row
+ cy.get("#row1").shadow().find("#dummy-cell")
+ .should("not.have.css", "border-inline-start-width", "0px")
+ .should("have.attr", "data-excluded-from-navigation", "nofocus");
+ cy.get("[ui5-table-header-row]").shadow().find("#dummy-cell")
+ .should("have.attr", "data-excluded-from-navigation", "nofocus");
+
+ // Focus row - custom outline should be applied
+ cy.get("#row1").should("have.attr", "_render-dummy-cell");
+ cy.get("#row1").should("not.have.attr", "_has-popin");
+ cy.realPress("Tab");
+ cy.get("#row1").shadow().find("[data-ui5-custom-outline='start']").should("exist");
+ cy.get("#row1").find("[data-ui5-custom-outline='end']").should("exist");
+ cy.get("#row1").shadow().find("#dummy-cell")
+ .should("not.have.attr", "data-ui5-custom-outline");
+
+ // Shrink to trigger popin to test custom outline is removed
+ cy.get("ui5-table").invoke("css", "width", "250px");
+ cy.wait(50);
+
+ cy.get("#row1").should("have.attr", "_has-popin");
+
+ // Left border removed, data-excluded-from-navigation without nofocus
+ cy.get("#row1").shadow().find("#dummy-cell")
+ .should("have.css", "border-inline-start-width", "0px")
+ .should("have.attr", "data-excluded-from-navigation", "");
+ cy.get("[ui5-table-header-row]").shadow().find("#dummy-cell")
+ .should("have.attr", "data-excluded-from-navigation", "");
+
+ // Expand back - border and nofocus should return, custom outline reapplied via onAfterRendering
+ cy.get("ui5-table").invoke("css", "width", "800px");
+ cy.wait(50);
+
+ cy.get("#row1").should("not.have.attr", "_has-popin");
+ cy.get("#row1").shadow().find("#dummy-cell")
+ .should("not.have.css", "border-inline-start-width", "0px")
+ .should("have.attr", "data-excluded-from-navigation", "nofocus");
+ cy.get("[ui5-table-header-row]").shadow().find("#dummy-cell")
+ .should("have.attr", "data-excluded-from-navigation", "nofocus");
+ cy.get("#row1").shadow().find("[data-ui5-custom-outline='start']").should("exist");
+ cy.get("#row1").find("[data-ui5-custom-outline='end']").should("exist");
+ });
+});
diff --git a/packages/main/src/Table.ts b/packages/main/src/Table.ts
index 6e864a648f71..a93ef0cbcda9 100644
--- a/packages/main/src/Table.ts
+++ b/packages/main/src/Table.ts
@@ -457,8 +457,9 @@ class Table extends UI5Element {
onBeforeRendering(): void {
this._renderNavigated = this.rows.some(row => row.navigated);
[...this.headerRow, ...this.rows].forEach((row, index) => {
+ row._rowActionCount = this.rows.length > 0 ? this.rowActionCount : 0;
row._renderNavigated = this._renderNavigated;
- row._rowActionCount = this.rowActionCount;
+ row._renderDummyCell = !this._hasFlexibleColumns;
row._alternate = this.alternateRowColors && index % 2 === 0;
});
@@ -649,8 +650,16 @@ class Table extends UI5Element {
return width;
}));
+ // Dummy Cell Width (before actions when popin, after navigated otherwise)
+ const dummyColumnWidth = !this._hasFlexibleColumns ? "minmax(0, 1fr)" : "";
+ const hasPopinCells = this.headerRow[0]._popinCells.length > 0;
+
+ if (dummyColumnWidth && hasPopinCells) {
+ widths.push(dummyColumnWidth);
+ }
+
// Row Action Cell Width
- if (this.rowActionCount > 0) {
+ if (this.rowActionCount > 0 && this.rows.length > 0) {
widths.push(`calc(var(--_ui5_button_base_min_width) * ${this.rowActionCount} + var(--_ui5_table_row_actions_gap) * ${this.rowActionCount - 1} + var(--_ui5_table_cell_horizontal_padding) * 2)`);
}
@@ -659,9 +668,17 @@ class Table extends UI5Element {
widths.push(`var(--_ui5_table_navigated_cell_width)`);
}
+ if (dummyColumnWidth && !hasPopinCells) {
+ widths.push(dummyColumnWidth);
+ }
+
return widths.join(" ");
}
+ get _hasFlexibleColumns(): boolean {
+ return this.headerRow?.[0]?._visibleCells.some(cell => !isValidColumnWidth(cell.width));
+ }
+
get _isRowSelectorRequired() {
return this.rows.length > 0 && this._getSelection()?.isRowSelectorRequired();
}
@@ -701,7 +718,7 @@ class Table extends UI5Element {
if (this._isRowSelectorRequired) {
ariaColCount++;
}
- if (this.rowActionCount > 0) {
+ if (this.rowActionCount > 0 && this.rows.length > 0) {
ariaColCount++;
}
if (this.headerRow[0]._popinCells.length > 0) {
diff --git a/packages/main/src/TableCell.ts b/packages/main/src/TableCell.ts
index ddfb0ffedca3..45fe6f91caf1 100644
--- a/packages/main/src/TableCell.ts
+++ b/packages/main/src/TableCell.ts
@@ -34,9 +34,9 @@ class TableCell extends TableCellBase {
/**
* Defines whether the cell is visually merged with the cell directly above it.
*
- * This is useful when consecutive cells in a column have the same value and should visually appear as a single merged cell.
+ * This is useful if consecutive cells in a column have the same value and should visually appear as a single merged cell.
* Although the cell is visually merged with the previous one, its content must still be provided for accessibility purposes.
- * **Note:** This feature is disabled when cells are rendered as popin, and should remain `false` for interactive cell content.
+ * **Note:** This feature is disabled when cells are rendered as a popin, and should remain `false` for interactive cell content.
*
* @default false
* @since 2.21.0
diff --git a/packages/main/src/TableHeaderRowTemplate.tsx b/packages/main/src/TableHeaderRowTemplate.tsx
index 6a598a1eaadc..68b4e4595eeb 100644
--- a/packages/main/src/TableHeaderRowTemplate.tsx
+++ b/packages/main/src/TableHeaderRowTemplate.tsx
@@ -54,13 +54,35 @@ export default function TableHeaderRowTemplate(this: TableHeaderRow, ariaColInde
return [ ];
})}
+ { this._renderDummyCell && this._hasPopin &&
+
+
+ }
+
{ this._rowActionCount > 0 &&
{this._i18nRowActions}
}
- { this._popinCells.length > 0 &&
+ { this._renderNavigated &&
+
+
+
+ }
+
+ { this._renderDummyCell && !this._hasPopin &&
+
+
+ }
+
+ { this._hasPopin &&
{this._i18nRowPopin}
diff --git a/packages/main/src/TableNavigation.ts b/packages/main/src/TableNavigation.ts
index 85f0d1937914..286934f844cc 100644
--- a/packages/main/src/TableNavigation.ts
+++ b/packages/main/src/TableNavigation.ts
@@ -246,6 +246,9 @@ class TableNavigation extends TableExtension {
for (const target of e.composedPath() as any[]) {
if (target.nodeType === Node.ELEMENT_NODE) {
const element = target as HTMLElement;
+ if (element.getAttribute("data-excluded-from-navigation") === "nofocus") {
+ break;
+ }
if (element.matches(":focus-within")) {
focusableElement = element;
break;
diff --git a/packages/main/src/TableRow.ts b/packages/main/src/TableRow.ts
index 56f13c9257b3..4067db5886be 100644
--- a/packages/main/src/TableRow.ts
+++ b/packages/main/src/TableRow.ts
@@ -130,13 +130,6 @@ class TableRow extends TableRowBase {
toggleAttribute(this, "draggable", this.movable, "true");
toggleAttribute(this, "_interactive", this._isInteractive);
toggleAttribute(this, "_alternate", this._alternate);
- toggleAttribute(this, "_haspopin", this._hasPopin);
- }
-
- async focus(focusOptions?: FocusOptions | undefined): Promise {
- this.setAttribute("tabindex", "-1");
- HTMLElement.prototype.focus.call(this, focusOptions);
- return Promise.resolve();
}
async _onpointerdown(e: PointerEvent) {
@@ -198,10 +191,6 @@ class TableRow extends TableRowBase {
}) !== undefined;
}
- get _hasPopin() {
- return this.cells.some(c => c._popin && !c._popinHidden);
- }
-
get _rowIndex() {
if (this.position !== undefined) {
return this.position;
diff --git a/packages/main/src/TableRowBase.ts b/packages/main/src/TableRowBase.ts
index c9fdf238d181..f75a9767eac8 100644
--- a/packages/main/src/TableRowBase.ts
+++ b/packages/main/src/TableRowBase.ts
@@ -40,6 +40,9 @@ abstract class TableRowBase extends
@property({ type: Boolean, noAttribute: true })
_alternate = false;
+ @property({ type: Boolean })
+ _renderDummyCell = false;
+
@query("#selection-cell")
_selectionCell?: HTMLElement;
@@ -49,6 +52,10 @@ abstract class TableRowBase extends
@i18n("@ui5/webcomponents")
static i18nBundle: I18nBundle;
+ isHeaderRow(): boolean {
+ return false;
+ }
+
onEnterDOM() {
!this.role && this.setAttribute("role", "row");
this.toggleAttribute("ui5-table-row-base", true);
@@ -56,14 +63,40 @@ abstract class TableRowBase extends
onBeforeRendering() {
toggleAttribute(this, "aria-selected", this._isSelectable, `${this._isSelected}`);
+ toggleAttribute(this, "_has-popin", this._hasPopin);
+ }
+
+ onAfterRendering() {
+ this._handleCustomFocusOutline();
}
getFocusDomRef() {
return this;
}
- isHeaderRow(): boolean {
- return false;
+ async focus(focusOptions?: FocusOptions | undefined): Promise {
+ this.setAttribute("tabindex", "-1");
+ HTMLElement.prototype.focus.call(this, focusOptions);
+ this._handleCustomFocusOutline();
+ return Promise.resolve();
+ }
+
+ _handleCustomFocusOutline() {
+ if (this._renderDummyCell && !this._hasPopin && document.activeElement === this) {
+ const cells = [...this.shadowRoot!.children].flatMap(element => {
+ return element.localName === "slot" ? (element as HTMLSlotElement).assignedElements() : [element];
+ });
+ const customOutlineAttribute = "data-ui5-custom-outline";
+ cells.forEach(cell => cell.removeAttribute(customOutlineAttribute));
+ const firstVisibleCell = cells.at(0);
+ const lastVisibleCell = cells.at(-2);
+ if (firstVisibleCell === lastVisibleCell) {
+ firstVisibleCell?.setAttribute(customOutlineAttribute, "startend");
+ } else {
+ firstVisibleCell?.setAttribute(customOutlineAttribute, "start");
+ lastVisibleCell?.setAttribute(customOutlineAttribute, "end");
+ }
+ }
}
_onSelectionChange() {
@@ -120,6 +153,10 @@ abstract class TableRowBase extends
return this.cells.filter(c => c._popin && !c._popinHidden);
}
+ get _hasPopin() {
+ return (this._table?.rows.length ?? 0) > 0 && this.cells.some(c => c._popin && !c._popinHidden);
+ }
+
get _stickyCells() {
return [this._selectionCell, ...this.cells, this._navigatedCell].filter(cell => cell?.hasAttribute("fixed"));
}
diff --git a/packages/main/src/TableRowTemplate.tsx b/packages/main/src/TableRowTemplate.tsx
index d882efe05185..b2190320a15c 100644
--- a/packages/main/src/TableRowTemplate.tsx
+++ b/packages/main/src/TableRowTemplate.tsx
@@ -47,6 +47,12 @@ export default function TableRowTemplate(this: TableRow, ariaColIndex: number =
return [ ];
})}
+ { this._renderDummyCell && this._hasPopin &&
+
+
+ }
+
{ this._rowActionCount > 0 &&
}
- { this._popinCells.length > 0 &&
+ { this._renderDummyCell && !this._hasPopin &&
+
+
+ }
+
+ { this._hasPopin &&
[ui5-table-cell],
:host(:first-of-type) > ::slotted([ui5-table-cell]) {
border-top: none;
@@ -18,20 +22,30 @@
:host([aria-selected="true"]) {
background: var(--sapList_SelectionBackgroundColor);
box-shadow: inset 0 calc(-1 * var(--sapList_BorderWidth)) 0 0 var(--sapList_SelectionBorderColor);
+
+ #actions-cell,
+ #navigated-cell,
+ #selection-cell {
+ clip-path: inset(0px 0px 1px 0px); /* selection bottom border should not overlap with sticky cells */
+ }
}
-:host(:not([_haspopin])) {
+:host(:not([_has-popin])) {
/* Use CSS Space Toggles until if() or container style queries are widely supported */
--_ui5_table_cell_border_merged: ;
--_ui5_table_cell_content_merged: ;
}
-:host(:not([_haspopin]):active),
-:host(:not([_haspopin]):focus-within) {
+:host(:not([_has-popin]):active),
+:host(:not([_has-popin]):focus-within) {
/* Provide a valid CSS value to intentionally invalidate the TableCell variable-based rules and disable visual merging. */
--_ui5_table_cell_content_merged: initial;
}
+:host([_interactive]) {
+ cursor: pointer;
+}
+
@media (hover: hover) {
:host([_interactive]:hover) {
background: var(--sapList_Hover_Background);
@@ -39,7 +53,7 @@
:host([_interactive][aria-selected=true]:hover) {
background: var(--sapList_Hover_SelectionBackground);
}
- :host(:not([_haspopin]):hover) {
+ :host(:not([_has-popin]):hover) {
/* Provide a valid CSS value to intentionally invalidate the TableCell variable-based rules and disable visual merging. */
--_ui5_table_cell_content_merged: initial;
}
@@ -50,14 +64,6 @@
background: var(--sapList_Active_Background);
}
-:host([_interactive]) {
- cursor: pointer;
-}
-
-:host([position]) {
- height: var(--row-height);
-}
-
#popin-cell {
padding-inline-start: var(--_ui5_first_table_cell_horizontal_padding);
align-content: initial;
@@ -67,38 +73,25 @@
}
#navigated-cell {
- position: sticky;
- inset-inline-end: 0;
- z-index: 1;
- background-color: inherit;
overflow: visible;
grid-row: span 2;
- min-width: 0;
- padding: 0;
}
:host([navigated]) #navigated {
position: absolute;
- inset: -1px 0px 0px 1px;
+ inset: 0px 0px 0px 1px;
background: var(--sapList_SelectionBorderColor);
}
-:host([tabindex]:focus) #navigated {
- transform: translateX(calc(var(--_ui5_table_navigated_cell_width) * -1));
- bottom: 3px;
- top: 2px;
-}
-
-:host([tabindex]:focus) #navigated:dir(rtl) {
- transform: translateX(var(--_ui5_table_navigated_cell_width));
-}
-
-:host([tabindex]:focus) #navigated-cell {
- clip-path: inset(var(--sapContent_FocusWidth) var(--sapContent_FocusWidth) var(--sapContent_FocusWidth) calc(var(--_ui5_table_navigated_cell_width) * -1));
-}
-
-:host([tabindex]:focus) #navigated-cell:dir(rtl) {
- clip-path: inset(var(--sapContent_FocusWidth) calc(var(--_ui5_table_navigated_cell_width) * -1) var(--sapContent_FocusWidth) var(--sapContent_FocusWidth));
+:host([navigated][tabindex]:focus) {
+ #navigated {
+ transform: translateX(calc(var(--_ui5_table_navigated_cell_width) * -1));
+ bottom: 3px;
+ top: 2px;
+ }
+ #navigated:dir(rtl) {
+ transform: translateX(var(--_ui5_table_navigated_cell_width));
+ }
}
:host([navigated]) #popin-cell {
@@ -113,7 +106,3 @@
#actions-cell {
gap: var(--_ui5_table_row_actions_gap);
}
-
-#actions-cell:has(+ #navigated-cell) {
- inset-inline-end: var(--_ui5_table_navigated_cell_width);
-}
\ No newline at end of file
diff --git a/packages/main/src/themes/TableRowBase.css b/packages/main/src/themes/TableRowBase.css
index cdb18889cbc7..bd8088f128fa 100644
--- a/packages/main/src/themes/TableRowBase.css
+++ b/packages/main/src/themes/TableRowBase.css
@@ -7,14 +7,19 @@
overflow: clip;
}
-:host([tabindex]:focus) {
- outline: var(--sapContent_FocusWidth) var(--sapContent_FocusStyle) var(--sapContent_FocusColor);
- outline-offset: calc(-1 * var(--sapContent_FocusWidth));
+#selection-cell,
+#actions-cell,
+#navigated-cell {
+ background-color: inherit;
+ position: sticky;
+ z-index: 1;
}
-:host([tabindex]:focus) #selection-cell,
-:host([tabindex]:focus) #actions-cell {
- clip-path: inset(var(--sapContent_FocusWidth)); /* focus outline should not overlap sticky cells */
+#selection-cell {
+ padding: 0;
+ inset-inline-start: 0;
+ min-width: auto;
+ justify-content: center;
}
::slotted([ui5-table-cell-base]:first-of-type) {
@@ -25,25 +30,79 @@
padding-inline-start: var(--_ui5_table_cell_horizontal_padding);
}
-#selection-cell,
#actions-cell {
- background-color: inherit;
- clip-path: inset(0px 0px 1px 0px); /* selection border should not overlap with sticky cells */
- position: sticky;
- z-index: 1;
+ inset-inline-end: 0;
}
-#selection-cell {
- padding: 0;
- inset-inline-start: 0;
- min-width: auto;
+#actions-cell:has(+ #navigated-cell) {
+ inset-inline-end: var(--_ui5_table_navigated_cell_width);
}
-#actions-cell {
+#navigated-cell {
inset-inline-end: 0;
+ min-width: 0;
+ padding: 0;
+}
+
+#dummy-cell {
+ border-inline-start: 1px solid var(--sapList_TableFixedBorderColor);
}
-#selection-cell[tabindex]:focus,
-#actions-cell[tabindex]:focus {
- clip-path: inset(0px);
-}
\ No newline at end of file
+:host([_has-popin]) #dummy-cell {
+ border-inline-start: none;
+}
+
+:host([tabindex]:focus) {
+ outline: var(--sapContent_FocusWidth) var(--sapContent_FocusStyle) var(--sapContent_FocusColor);
+ outline-offset: calc(-1 * var(--sapContent_FocusWidth));
+
+ #selection-cell,
+ #actions-cell{
+ clip-path: inset(var(--sapContent_FocusWidth)); /* focus outline should not overlap sticky cells */
+ }
+
+ #navigated-cell {
+ clip-path: inset(3px 4px 3px -4px);
+ }
+ #navigated-cell:dir(rtl) {
+ clip-path: inset(3px -4px 3px 4px);
+ }
+}
+
+:host([tabindex][_render-dummy-cell]:not([_has-popin]):focus) {
+ outline: none;
+
+ --_ui5_table_cell_custom_outline_block: inset 0 var(--sapContent_FocusWidth) 0 0 var(--sapContent_FocusColor), inset 0 calc(-1 * var(--sapContent_FocusWidth)) 0 0 var(--sapContent_FocusColor);
+ --_ui5_table_cell_custom_outline_inline-start: inset var(--sapContent_FocusWidth) 0 0 0 var(--sapContent_FocusColor);
+ --_ui5_table_cell_custom_outline_inline-end: inset calc(-1 * var(--sapContent_FocusWidth)) 0 0 0 var(--sapContent_FocusColor);
+ :dir(rtl) {
+ --_ui5_table_cell_custom_outline_inline-start: inset calc(-1 * var(--sapContent_FocusWidth)) 0 0 0 var(--sapContent_FocusColor);
+ --_ui5_table_cell_custom_outline_inline-end: inset var(--sapContent_FocusWidth) 0 0 0 var(--sapContent_FocusColor);
+ }
+
+ [ui5-table-cell-base], ::slotted([ui5-table-cell-base]) {
+ box-shadow: var(--_ui5_table_cell_custom_outline_block);
+ }
+ [data-ui5-custom-outline="start"], ::slotted([data-ui5-custom-outline="start"]) {
+ box-shadow: var(--_ui5_table_cell_custom_outline_block), var(--_ui5_table_cell_custom_outline_inline-start);
+ }
+ [data-ui5-custom-outline="end"], ::slotted([data-ui5-custom-outline="end"]) {
+ box-shadow: var(--_ui5_table_cell_custom_outline_block), var(--_ui5_table_cell_custom_outline_inline-end);
+ }
+ [data-ui5-custom-outline="startend"], ::slotted([data-ui5-custom-outline="startend"]) {
+ box-shadow: var(--_ui5_table_cell_custom_outline_block), var(--_ui5_table_cell_custom_outline_inline-start), var(--_ui5_table_cell_custom_outline_inline-end);
+ }
+ #dummy-cell {
+ box-shadow: none;
+ }
+
+ #selection-cell,
+ #actions-cell,
+ #navigated-cell {
+ clip-path: none;
+ }
+
+ #navigated {
+ top: 3px;
+ }
+}
diff --git a/packages/main/test/pages/TableNoData.html b/packages/main/test/pages/TableNoData.html
new file mode 100644
index 000000000000..6bb31eaac5b1
--- /dev/null
+++ b/packages/main/test/pages/TableNoData.html
@@ -0,0 +1,179 @@
+
+
+
+
+
+ Table - No Data
+
+
+
+
+
+
+
+
+
+
+ Single column - no data (column should cover full width)
+
+
+ Product
+
+
+
+
+ Single column (width:40%) - no data with custom noData slot
+
+ Add Row
+ Remove Row
+
+
+
+ Product
+
+
+
+
+
+
+
+
+
+ Multiple columns (width:200px each) - no data
+
+ Add Row
+ Remove Row
+
+
+
+ Product
+ Supplier
+ Price
+
+
+
+
+
+
+ Popin - 4 columns (shrink below 1000px to see popin columns)
+
+ Add Row
+ Remove Row
+
+
+
+
+ Product
+ Supplier
+ Dimensions
+ Price
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/main/test/pages/Table_Acc.html b/packages/main/test/pages/Table_Acc.html
index 591b9e93f0a8..bea4765cf669 100644
--- a/packages/main/test/pages/Table_Acc.html
+++ b/packages/main/test/pages/Table_Acc.html
@@ -27,6 +27,7 @@ ACC Test Page for Table
Selection Mode:
Multi
+ Multi ClearAll
Single
None
@@ -224,6 +225,12 @@ ACC Test Page for Table
selection.setAttribute("id", "selection");
selection.setAttribute("slot", "features");
table.insertBefore(selection, table.firstChild);
+ } else if (e.target.selectedOption.textContent === "Multi ClearAll") {
+ selection = document.createElement("ui5-table-selection-multi");
+ selection.setAttribute("id", "selection");
+ selection.setAttribute("slot", "features");
+ selection.setAttribute("header-selector", "ClearAll");
+ table.insertBefore(selection, table.firstChild);
}
});
diff --git a/packages/website/docs/_components_pages/main/Table/TableCell.mdx b/packages/website/docs/_components_pages/main/Table/TableCell.mdx
index da40d9249b11..3fb6157b7f8c 100644
--- a/packages/website/docs/_components_pages/main/Table/TableCell.mdx
+++ b/packages/website/docs/_components_pages/main/Table/TableCell.mdx
@@ -19,21 +19,21 @@ Please note that the behavior of `horizontalAlign` depends on where you set it:
Changes the horizontal alignment of the `TableHeaderCell` and its corresponding `TableCell`.
- `horizontalAlign` is set on `TableCell` level only
The horizontal alignment is only changed for this one `TableCell`
-- `horizontalAlign` is set on `TableHeaderCell` and `TableCell` level
- Changing the `horizontalAlign` property on `TableHeaderCell` level changes only the `TableHeaderCell` itself and those `TableCell` without a `horizontalAlign` property.
+- `horizontalAlign` is set at the `TableHeaderCell` and `TableCell` level
+ Changing the `horizontalAlign` property at the `TableHeaderCell` level changes only the `TableHeaderCell` itself and those `TableCell` properties without a `horizontalAlign` property.
## Merged cells
-Set the `merged` property on a `TableCell` to visually merge it with the cell directly above it in the same column. When a cell is merged,
+Set the `merged` property on a `TableCell` to visually merge it with the cell directly above it in the same column. If a cell is merged,
its content is hidden and the border becomes transparent, giving the appearance of a single spanning cell.
-This is useful when consecutive rows share the same value in a column.
+This is useful if consecutive rows share the same value in a column.
To ensure usability, merged cell styling is automatically disabled in these cases:
-- **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.
+- **Popin mode:** If the table is narrow, and cells are rendered in the pop-in area, merged styling is removed so each row's content remains visible.
- **Hover and focus:** When the user hovers over or focuses within the row, merged styling is temporarily removed, making the merged content visible.
-**Note:** Avoid using `merged` on cells that contain interactive content (e.g., buttons, or inputs).
+**Note:** Avoid using `merged` on cells that contain interactive content (for example, buttons, or inputs).
\ No newline at end of file
diff --git a/packages/website/docs/_components_pages/main/Table/TableHeaderCell.mdx b/packages/website/docs/_components_pages/main/Table/TableHeaderCell.mdx
index a39e66c0a2c5..42853dfcbf87 100644
--- a/packages/website/docs/_components_pages/main/Table/TableHeaderCell.mdx
+++ b/packages/website/docs/_components_pages/main/Table/TableHeaderCell.mdx
@@ -27,7 +27,7 @@ Please note that the behavior of `horizontalAlign` depends on where you set it:
Changes the horizontal alignment of the `TableHeaderCell` and its corresponding `TableCell`.
- `horizontalAlign` is set on `TableCell` level only
The horizontal alignment is only changed for this one `TableCell`
-- `horizontalAlign` is set on `TableHeaderCell` and `TableCell` level
-Changing the `horizontalAlign` property on `TableHeaderCell` level changes only the `TableHeaderCell` itself and those `TableCell` without a `horizontalAlign` property.
+- `horizontalAlign` is set at the `TableHeaderCell` and `TableCell` level
+Changing the `horizontalAlign` property at the `TableHeaderCell` level changes only the `TableHeaderCell` itself and those `TableCell` properties without a `horizontalAlign` property.
\ No newline at end of file