diff --git a/packages/main/cypress/specs/Breadcrumbs.cy.tsx b/packages/main/cypress/specs/Breadcrumbs.cy.tsx
index c338c973d90f..30ab14723cd1 100644
--- a/packages/main/cypress/specs/Breadcrumbs.cy.tsx
+++ b/packages/main/cypress/specs/Breadcrumbs.cy.tsx
@@ -91,7 +91,15 @@ describe("Breadcrumbs - getFocusDomRef Method", () => {
const item = $el[1];
const breadcrumbsAnchor = breadcrumbs.getFocusDomRef();
- const itemAnchor = item.getFocusDomRef().shadowRoot.querySelector("a");
+ const itemFocusDomRef = item.getFocusDomRef();
+ const itemAnchor = itemFocusDomRef?.shadowRoot?.querySelector("a");
+
+ expect(breadcrumbsAnchor).to.exist;
+ expect(itemAnchor).to.exist;
+
+ if (!breadcrumbsAnchor || !itemAnchor) {
+ return;
+ }
expect(breadcrumbsAnchor.textContent).to.equal(itemAnchor.textContent);
});
@@ -121,7 +129,15 @@ describe("Breadcrumbs - getFocusDomRef Method", () => {
const item = $el[1];
const breadcrumbsAnchor = breadcrumbs.getFocusDomRef();
- const itemAnchor = item.getFocusDomRef().shadowRoot.querySelector("a");
+ const itemFocusDomRef = item.getFocusDomRef();
+ const itemAnchor = itemFocusDomRef?.shadowRoot?.querySelector("a");
+
+ expect(breadcrumbsAnchor).to.exist;
+ expect(itemAnchor).to.exist;
+
+ if (!breadcrumbsAnchor || !itemAnchor) {
+ return;
+ }
expect(breadcrumbsAnchor.textContent).to.equal(itemAnchor.textContent);
});
@@ -143,6 +159,11 @@ describe("Breadcrumbs general interaction", () => {
const domRef = breadcrumbItemElement.getDomRef();
expect(domRef).to.exist;
+
+ if (!domRef) {
+ return;
+ }
+
expect(domRef.nodeType).to.equal(Node.ELEMENT_NODE);
});
});
@@ -910,37 +931,47 @@ describe("Breadcrumbs general interaction", () => {
>
);
- // assert that last link in the narrow Breadcrumbs is truncated
+ // Assert actual text truncation by checking ellipsis overflow metrics.
cy.get("#breadcrumbs2")
.shadow()
- .find("li:last-child")
- .then(($lastLink) => {
- const linkRect = $lastLink[0].getBoundingClientRect();
- const maxExpectedWidth = 300;
-
- expect(linkRect.width, "link wrapper should be shrinkable and less than parent width")
- .to.be.lessThan(maxExpectedWidth);
- });
+ .find(".ui5-breadcrumbs-current-location [ui5-label]")
+ .shadow()
+ .find(".ui5-label-text-wrapper")
+ .should(($textWrapper) => {
+ const wrapper = $textWrapper[0] as HTMLElement;
+ expect(wrapper.scrollWidth, "text should overflow the available width")
+ .to.be.greaterThan(wrapper.clientWidth);
+ });
// assert that height of both Breadcrumbs (one that fits and one that truncates)
// is the same
cy.get("#breadcrumbs1")
.then(($breadcrumbs1) => {
const breadcrumbs1DOMRef = ($breadcrumbs1[0] as Breadcrumbs).getDomRef();
- const breadcrumbs1Rect = breadcrumbs1DOMRef.getBoundingClientRect();
- const breadcrumbs1Height = breadcrumbs1Rect.height;
+ expect(breadcrumbs1DOMRef).to.exist;
+ if (!breadcrumbs1DOMRef) {
+ return 0;
+ }
+ return breadcrumbs1DOMRef.getBoundingClientRect().height;
+ })
+ .then((breadcrumbs1Height) => {
cy.get("#breadcrumbs2")
- .then(($breadcrumbs2) => {
+ .should(($breadcrumbs2) => {
const breadcrumbs2DOMRef = ($breadcrumbs2[0] as Breadcrumbs).getDomRef();
- const breadcrumbs2Rect = breadcrumbs2DOMRef.getBoundingClientRect();
- const breadcrumbs2Height = breadcrumbs2Rect.height;
+ expect(breadcrumbs2DOMRef).to.exist;
+
+ if (!breadcrumbs2DOMRef) {
+ return;
+ }
+
+ const breadcrumbs2Height = breadcrumbs2DOMRef.getBoundingClientRect().height;
expect(breadcrumbs2Height, "link height should remain the same")
- .to.be.equal(breadcrumbs1Height);
- });
- });
+ .to.be.closeTo(breadcrumbs1Height, 1);
+ });
+ });
});
});
@@ -1031,4 +1062,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..9cccf9dd35fa 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.22.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: -
+
+
+
+