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 + + + + + + + + + +
+ ); + + 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) => ( + + ))} + +
+ ); + + 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 + + + + + + + +
+ ); + + // 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 + + + + + +
+ ); + + // 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