diff --git a/packages/main/cypress/specs/Breadcrumbs.cy.tsx b/packages/main/cypress/specs/Breadcrumbs.cy.tsx index c338c973d90f..491c1aa4adc8 100644 --- a/packages/main/cypress/specs/Breadcrumbs.cy.tsx +++ b/packages/main/cypress/specs/Breadcrumbs.cy.tsx @@ -1031,4 +1031,97 @@ describe("Breadcrumbs with item for current page", () => { .should('not.exist'); }); }); +}); + +describe("BreadcrumbsItem click event", () => { + it("fires click event on item when a visible link is clicked", () => { + cy.mount( + + Link1 + Link2 + Location + + ); + + cy.get("#item1").then(($item) => { + $item[0].addEventListener("ui5-click", cy.stub().as("clickStub")); + }); + + cy.get("[ui5-breadcrumbs]") + .shadow() + .find(".ui5-breadcrumbs-link-wrapper ui5-link") + .first() + .realClick(); + + cy.get("@clickStub").should("have.been.calledOnce"); + }); + + it("fires click event on current page item (label) when activated", () => { + cy.mount( + + Link1 + Location + + ); + + cy.get("#currentItem").then(($item) => { + $item[0].addEventListener("ui5-click", cy.stub().as("clickStub")); + }); + + cy.get("[ui5-breadcrumbs]") + .shadow() + .find(".ui5-breadcrumbs-current-location span") + .realClick(); + + cy.get("@clickStub").should("have.been.calledOnce"); + }); + + it("prevents item-click on breadcrumbs when item click is cancelled", () => { + let itemClickFired = false; + + cy.mount( + { itemClickFired = true; }}> + Link1 + Location + + ); + + cy.get("#item1").then(($item) => { + $item[0].addEventListener("ui5-click", (e: Event) => { e.preventDefault(); }); + }); + + cy.get("[ui5-breadcrumbs]") + .shadow() + .find(".ui5-breadcrumbs-link-wrapper ui5-link") + .first() + .realClick(); + + cy.then(() => { + expect(itemClickFired).to.be.false; + }); + }); + + it("prevents item-click on breadcrumbs when current page item click is cancelled", () => { + let itemClickFired = false; + + cy.mount( + { itemClickFired = true; }}> + Link1 + Location + + ); + + cy.get("#currentItem").then(($item) => { + $item[0].addEventListener("ui5-click", (e: Event) => { e.preventDefault(); }); + }); + + cy.get("[ui5-breadcrumbs]") + .shadow() + .find(".ui5-breadcrumbs-current-location span") + .realClick(); + + cy.then(() => { + expect(itemClickFired).to.be.false; + }); + }); }); \ No newline at end of file diff --git a/packages/main/src/Breadcrumbs.ts b/packages/main/src/Breadcrumbs.ts index 22b5077892e4..bd65f065d1fa 100644 --- a/packages/main/src/Breadcrumbs.ts +++ b/packages/main/src/Breadcrumbs.ts @@ -387,6 +387,16 @@ class Breadcrumbs extends UI5Element implements IToolbarItemContent { shiftKey, } = e.detail; + if (!item.fireDecoratorEvent("click", { + altKey, + ctrlKey, + metaKey, + shiftKey, + })) { + e.preventDefault(); + return; + } + if (!this.fireDecoratorEvent("item-click", { item, altKey, @@ -408,6 +418,15 @@ class Breadcrumbs extends UI5Element implements IToolbarItemContent { shiftKey, } = e; + if (!item.fireDecoratorEvent("click", { + altKey, + ctrlKey, + metaKey, + shiftKey, + })) { + return; + } + this.fireDecoratorEvent("item-click", { item, altKey, @@ -422,6 +441,15 @@ class Breadcrumbs extends UI5Element implements IToolbarItemContent { items = this._getItems(), item = items.find(x => `${x._id}-li` === listItem.id)!; + if (!item.fireDecoratorEvent("click", { + altKey: false, + ctrlKey: false, + metaKey: false, + shiftKey: false, + })) { + return; + } + if (this.fireDecoratorEvent("item-click", { item })) { locationOpen(item.href, item.target || "_self", "noopener,noreferrer"); this.responsivePopover!.open = false; diff --git a/packages/main/src/BreadcrumbsItem.ts b/packages/main/src/BreadcrumbsItem.ts index b1175a109be1..3a478a79c922 100644 --- a/packages/main/src/BreadcrumbsItem.ts +++ b/packages/main/src/BreadcrumbsItem.ts @@ -3,9 +3,17 @@ import type { DefaultSlot } from "@ui5/webcomponents-base/dist/UI5Element.js"; import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js"; import property from "@ui5/webcomponents-base/dist/decorators/property.js"; import slot from "@ui5/webcomponents-base/dist/decorators/slot-strict.js"; +import eventStrict from "@ui5/webcomponents-base/dist/decorators/event-strict.js"; import type { AccessibilityAttributes } from "@ui5/webcomponents-base/dist/types.js"; import LinkDesign from "./types/LinkDesign.js"; +type BreadcrumbsItemClickEventDetail = { + altKey: boolean; + ctrlKey: boolean; + metaKey: boolean; + shiftKey: boolean; +}; + /** * @class * @@ -19,7 +27,26 @@ import LinkDesign from "./types/LinkDesign.js"; * @abstract */ @customElement("ui5-breadcrumbs-item") +/** + * Fired when the component is activated either with a mouse/tap or by using the Enter or Space key. + * + * **Note:** The event is also fired for the current page location item (the last item), which is not a link by design. + * + * @param {boolean} altKey Returns whether the "ALT" key was pressed when the event was triggered. + * @param {boolean} ctrlKey Returns whether the "CTRL" key was pressed when the event was triggered. + * @param {boolean} metaKey Returns whether the "META" key was pressed when the event was triggered. + * @param {boolean} shiftKey Returns whether the "SHIFT" key was pressed when the event was triggered. + * @public + * @since 2.10.0 + */ +@eventStrict("click", { + bubbles: true, + cancelable: true, +}) class BreadcrumbsItem extends UI5Element { + eventDetails!: { + "click": BreadcrumbsItemClickEventDetail, + } /** * Defines the link href. * @@ -87,3 +114,4 @@ class BreadcrumbsItem extends UI5Element { BreadcrumbsItem.define(); export default BreadcrumbsItem; +export type { BreadcrumbsItemClickEventDetail }; diff --git a/packages/main/test/pages/Breadcrumbs.html b/packages/main/test/pages/Breadcrumbs.html index 748ae9c901f7..369770618942 100644 --- a/packages/main/test/pages/Breadcrumbs.html +++ b/packages/main/test/pages/Breadcrumbs.html @@ -200,6 +200,25 @@

Breadcrumbs with hardcoded width: 300px and design="NoCurrentPage"

+

BreadcrumbsItem click event

+

Each item fires its own ui5-click event. The log below shows which item was clicked.

+ + + Products + Laptops + ThinkPad X1 + + +

Last item click: -

+ + + + + + + diff --git a/packages/website/docs/_samples/main/Breadcrumbs/ClickEvent/sample.tsx b/packages/website/docs/_samples/main/Breadcrumbs/ClickEvent/sample.tsx new file mode 100644 index 000000000000..107afa53d20c --- /dev/null +++ b/packages/website/docs/_samples/main/Breadcrumbs/ClickEvent/sample.tsx @@ -0,0 +1,32 @@ +import { useState } from "react"; +import createReactComponent from "@ui5/webcomponents-base/dist/createReactComponent.js"; +import BreadcrumbsClass from "@ui5/webcomponents/dist/Breadcrumbs.js"; +import BreadcrumbsItemClass from "@ui5/webcomponents/dist/BreadcrumbsItem.js"; + +const Breadcrumbs = createReactComponent(BreadcrumbsClass); +const BreadcrumbsItem = createReactComponent(BreadcrumbsItemClass); + +function App() { + const [lastClicked, setLastClicked] = useState("-"); + + return ( + <> + + setLastClicked("Products")}> + Products + + setLastClicked("Laptops")}> + Laptops + + setLastClicked("ThinkPad X1")}> + ThinkPad X1 + + +

+ Last clicked item: {lastClicked} +

+ + ); +} + +export default App;