diff --git a/packages/main/cypress/specs/Table.cy.tsx b/packages/main/cypress/specs/Table.cy.tsx index f41b55637967..a59c4c8b6c35 100644 --- a/packages/main/cypress/specs/Table.cy.tsx +++ b/packages/main/cypress/specs/Table.cy.tsx @@ -982,6 +982,50 @@ describe("Table - Interactive Rows", () => { cy.get("@buttonClickHandler").should("have.been.calledThrice"); cy.get("@rowClickHandler").should("have.been.calledThrice"); }); + + it("fires click event on the row element", () => { + cy.mount( + + + ColumnA + ColumnB + + + + + + + + + +
+ ); + + cy.get("#row1").invoke("on", "click", cy.stub().as("row1ClickSpy")); + cy.get("#row2").invoke("on", "click", cy.stub().as("row2ClickSpy")); + + // Non-interactive row should not fire custom click + cy.get("#row1").realClick(); + cy.get("@row1ClickSpy").then(stub => { + const customClicks = (stub as unknown as Cypress.Agent).getCalls().filter(call => call.args[0].originalEvent instanceof CustomEvent); + expect(customClicks).to.have.length(0); + }); + + // Interactive row does not fire custom click on mouse click (native click bubbles normally) + cy.get("#row2").realClick(); + cy.get("@row2ClickSpy").then(stub => { + const customClicks = (stub as unknown as Cypress.Agent).getCalls().filter(call => call.args[0].originalEvent instanceof CustomEvent); + expect(customClicks).to.have.length(0); + }); + + // Interactive row fires custom click on Enter key + cy.get("#row2").realPress("Enter"); + cy.get("@row2ClickSpy").then(stub => { + const customClicks = (stub as unknown as Cypress.Agent).getCalls().filter(call => call.args[0].originalEvent instanceof CustomEvent); + expect(customClicks).to.have.length(1); + expect(customClicks[0].args[0].originalEvent).to.have.property("detail"); + }); + }); }); describe("Table - HeaderCell", () => { diff --git a/packages/main/src/TableRow.ts b/packages/main/src/TableRow.ts index e60c521d5194..04eb8499bc4b 100644 --- a/packages/main/src/TableRow.ts +++ b/packages/main/src/TableRow.ts @@ -1,4 +1,6 @@ -import { customElement, slotStrict as slot, property } from "@ui5/webcomponents-base/dist/decorators.js"; +import { + customElement, slotStrict as slot, property, eventStrict, +} from "@ui5/webcomponents-base/dist/decorators.js"; import { isEnter } from "@ui5/webcomponents-base/dist/Keys.js"; import getActiveElement from "@ui5/webcomponents-base/dist/util/getActiveElement.js"; import query from "@ui5/webcomponents-base/dist/decorators/query.js"; @@ -36,7 +38,23 @@ import { styles: [TableRowBase.styles, TableRowCss], template: TableRowTemplate, }) +/** + * Fired when the row is activated by the user via click or Enter key. + * + * **Note:** This event is not fired when the row has `behavior="RowOnly"` selection. + * In that case, use the selection component's `change` event instead. + * + * @public + * @since 2.9.0 + */ +@eventStrict("click", { + bubbles: true, +}) class TableRow extends TableRowBase { + eventDetails!: TableRowBase["eventDetails"] & { + click: void + } + /** * Defines the cells of the component. * @@ -160,15 +178,25 @@ class TableRow extends TableRowBase { if (eventOrigin === this && this._isInteractive && isEnter(e)) { this._setActive("keyup"); - this._onclick(); + this._handleClick(true); + } + } + + _onclick(e: Event) { + if (e instanceof CustomEvent) { + return; } + this._handleClick(false); } - _onclick() { + _handleClick(fireClick = false) { if (this === getActiveElement()) { if (this._isSelectable && !this._hasSelector) { this._onSelectionChange(); } else if (this.interactive || this._isNavigable) { + if (fireClick) { + this.fireDecoratorEvent("click"); + } this._table?._onRowClick(this); } } diff --git a/packages/main/src/TableRowBase.ts b/packages/main/src/TableRowBase.ts index 0798cdad6dfd..fedf563abc73 100644 --- a/packages/main/src/TableRowBase.ts +++ b/packages/main/src/TableRowBase.ts @@ -26,6 +26,10 @@ import { styles: TableRowBaseCss, }) abstract class TableRowBase extends UI5Element { + eventDetails!: { + click: void + } + cells!: Array; @property({ type: Number, noAttribute: true }) diff --git a/packages/website/docs/_components_pages/main/Table/Table.mdx b/packages/website/docs/_components_pages/main/Table/Table.mdx index dd2d159d020d..b2800099847d 100644 --- a/packages/website/docs/_components_pages/main/Table/Table.mdx +++ b/packages/website/docs/_components_pages/main/Table/Table.mdx @@ -9,6 +9,7 @@ import StickyHeader from "../../../_samples/main/Table/StickyHeader/StickyHeader import StickyHeaderContainer from "../../../_samples/main/Table/StickyHeaderContainer/StickyHeaderContainer.md"; import NoDataSlot from "../../../_samples/main/Table/NoDataSlot/NoDataSlot.md"; import Interactive from "../../../_samples/main/Table/Interactive/Interactive.md"; +import RowClick from "../../../_samples/main/Table/RowClick/RowClick.md"; import DragAndDrop from "../../../_samples/main/Table/DragAndDrop/DragAndDrop.md"; <%COMPONENT_OVERVIEW%> @@ -57,6 +58,13 @@ will fire the `row-click` event. +### Row Click Event + +The `click` event is fired directly on `ui5-table-row` when an interactive row is activated via mouse click or keyboard Enter. +This allows attaching click handlers directly on row elements, which is particularly useful for framework wrappers like React. + + + ### Drag and Drop Enable Drag and Drop by using the `move-over` and `move` event in combination with the `movable` property on the diff --git a/packages/website/docs/_components_pages/main/Table/TableRow.mdx b/packages/website/docs/_components_pages/main/Table/TableRow.mdx index 9b7821ceb777..c741295a695d 100644 --- a/packages/website/docs/_components_pages/main/Table/TableRow.mdx +++ b/packages/website/docs/_components_pages/main/Table/TableRow.mdx @@ -3,6 +3,7 @@ slug: ../../TableRow --- import Interactive from "../../../_samples/main/Table/Interactive/Interactive.md"; +import RowClick from "../../../_samples/main/Table/RowClick/RowClick.md"; import DragAndDrop from "../../../_samples/main/Table/DragAndDrop/DragAndDrop.md"; <%COMPONENT_OVERVIEW%> @@ -16,6 +17,13 @@ will fire the `row-click` event. +## Row Click Event + +The `click` event is fired directly on `ui5-table-row` when an interactive row is activated via mouse click or keyboard Enter. +This allows attaching click handlers directly on row elements, which is particularly useful for framework wrappers like React. + + + ## Movable Rows Adding the `movable` property enables the `ui5-table-row` for drag and drop operations. diff --git a/packages/website/docs/_samples/main/Table/RowClick/RowClick.md b/packages/website/docs/_samples/main/Table/RowClick/RowClick.md new file mode 100644 index 000000000000..0c062a836e84 --- /dev/null +++ b/packages/website/docs/_samples/main/Table/RowClick/RowClick.md @@ -0,0 +1,5 @@ +import html from '!!raw-loader!./sample.html'; +import js from '!!raw-loader!./main.js'; +import react from '!!raw-loader!./sample.tsx'; + + diff --git a/packages/website/docs/_samples/main/Table/RowClick/main.js b/packages/website/docs/_samples/main/Table/RowClick/main.js new file mode 100644 index 000000000000..d1e22690a2df --- /dev/null +++ b/packages/website/docs/_samples/main/Table/RowClick/main.js @@ -0,0 +1,17 @@ +import "@ui5/webcomponents/dist/Table.js"; +import "@ui5/webcomponents/dist/TableHeaderRow.js"; +import "@ui5/webcomponents/dist/TableHeaderCell.js"; +import "@ui5/webcomponents/dist/Label.js"; +import "@ui5/webcomponents/dist/Toast.js"; + +const toast = document.getElementById("message"); + +document.getElementById("row-a").addEventListener("click", () => { + toast.textContent = "Row A clicked!"; + toast.open = true; +}); + +document.getElementById("row-b").addEventListener("click", () => { + toast.textContent = "Row B clicked!"; + toast.open = true; +}); diff --git a/packages/website/docs/_samples/main/Table/RowClick/sample.html b/packages/website/docs/_samples/main/Table/RowClick/sample.html new file mode 100644 index 000000000000..f13f850b8a25 --- /dev/null +++ b/packages/website/docs/_samples/main/Table/RowClick/sample.html @@ -0,0 +1,38 @@ + + + + + + + + Sample + + + + + + + + + Product + Supplier + Price + + + + Notebook Basic 15 + Very Best Screens + 956 EUR + + + Notebook Basic 17 + Smartcards + 1249 EUR + + + + + + + + diff --git a/packages/website/docs/_samples/main/Table/RowClick/sample.tsx b/packages/website/docs/_samples/main/Table/RowClick/sample.tsx new file mode 100644 index 000000000000..fb44a8716ebc --- /dev/null +++ b/packages/website/docs/_samples/main/Table/RowClick/sample.tsx @@ -0,0 +1,71 @@ +import { useRef } from "react"; +import createReactComponent from "@ui5/webcomponents-base/dist/createReactComponent.js"; +import LabelClass from "@ui5/webcomponents/dist/Label.js"; +import TableClass from "@ui5/webcomponents/dist/Table.js"; +import TableCellClass from "@ui5/webcomponents/dist/TableCell.js"; +import TableHeaderCellClass from "@ui5/webcomponents/dist/TableHeaderCell.js"; +import TableHeaderRowClass from "@ui5/webcomponents/dist/TableHeaderRow.js"; +import TableRowClass from "@ui5/webcomponents/dist/TableRow.js"; +import ToastClass from "@ui5/webcomponents/dist/Toast.js"; + +const Label = createReactComponent(LabelClass); +const Table = createReactComponent(TableClass); +const TableCell = createReactComponent(TableCellClass); +const TableHeaderCell = createReactComponent(TableHeaderCellClass); +const TableHeaderRow = createReactComponent(TableHeaderRowClass); +const TableRow = createReactComponent(TableRowClass); +const Toast = createReactComponent(ToastClass); + +function App() { + const toastRef = useRef(null); + + const showToast = (message: string) => { + if (toastRef.current) { + toastRef.current!.textContent = message; + toastRef.current!.open = true; + } + }; + + return ( + <> + + + {/* playground-fold */} + + Product + Supplier + Price + + {/* playground-fold-end */} + showToast("Row A clicked!")}> + + + + + + + + + + + showToast("Row B clicked!")}> + + + + + + + + + + +
+ + ); +} + +export default App;