Skip to content

Commit 4d43265

Browse files
feat(ui5-notification-list-item, ui5-tab): add semantic click event
1 parent a0d9146 commit 4d43265

13 files changed

Lines changed: 269 additions & 14 deletions

File tree

packages/fiori/cypress/specs/NotificationList.cy.tsx

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,3 +1131,82 @@ describe("Notification List Item Without a Group", () => {
11311131
});
11321132

11331133
});
1134+
1135+
describe("NotificationListItem semantic click event", () => {
1136+
it("fires click event when clicked", () => {
1137+
cy.mount(
1138+
<NotificationList id="nl1">
1139+
<NotificationListItem id="nli1">Item 1</NotificationListItem>
1140+
</NotificationList>
1141+
);
1142+
1143+
cy.get("#nli1").then(($item) => {
1144+
$item[0].addEventListener("click", cy.stub().as("clickStub"));
1145+
});
1146+
1147+
cy.get("#nli1").realClick();
1148+
1149+
cy.get("@clickStub").should("have.been.calledOnce");
1150+
cy.get("@clickStub").should((stub: any) => {
1151+
const event = stub.firstCall.args[0];
1152+
expect(event).to.be.instanceOf(CustomEvent);
1153+
expect(event.detail.item).to.exist;
1154+
expect(event.detail.originalEvent).to.be.instanceOf(MouseEvent);
1155+
});
1156+
});
1157+
1158+
it("fires click event when activated with Enter key", () => {
1159+
cy.mount(
1160+
<NotificationList>
1161+
<NotificationListItem id="nli1">Item 1</NotificationListItem>
1162+
</NotificationList>
1163+
);
1164+
1165+
cy.get("#nli1").then(($item) => {
1166+
$item[0].addEventListener("click", cy.stub().as("clickStub"));
1167+
});
1168+
1169+
cy.get("#nli1").realClick();
1170+
cy.realPress("Enter");
1171+
1172+
cy.get("@clickStub").should("have.been.calledTwice");
1173+
});
1174+
1175+
it("fires click event when activated with Space key", () => {
1176+
cy.mount(
1177+
<NotificationList>
1178+
<NotificationListItem id="nli1">Item 1</NotificationListItem>
1179+
</NotificationList>
1180+
);
1181+
1182+
cy.get("#nli1").then(($item) => {
1183+
$item[0].addEventListener("click", cy.stub().as("clickStub"));
1184+
});
1185+
1186+
cy.get("#nli1").realClick();
1187+
cy.realPress("Space");
1188+
1189+
cy.get("@clickStub").should("have.been.calledTwice");
1190+
});
1191+
1192+
it("fires both click on item and item-click on NotificationList", () => {
1193+
cy.mount(
1194+
<NotificationList id="nl1">
1195+
<NotificationListItem id="nli1">Item 1</NotificationListItem>
1196+
</NotificationList>
1197+
);
1198+
1199+
cy.get("#nli1").then(($item) => {
1200+
$item[0].addEventListener("click", cy.stub().as("itemClickStub"));
1201+
});
1202+
1203+
cy.get("#nl1").then(($list) => {
1204+
$list[0].addEventListener("ui5-item-click", cy.stub().as("listItemClickStub"));
1205+
});
1206+
1207+
cy.get("#nli1").realClick();
1208+
1209+
cy.get("@itemClickStub").should("have.been.calledOnce");
1210+
cy.get("@listItemClickStub").should("have.been.calledOnce");
1211+
});
1212+
});

packages/fiori/src/NotificationListItem.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ type NotificationListItemPressEventDetail = {
5858
item: NotificationListItem,
5959
};
6060

61+
type NotificationListItemClickEventDetail = {
62+
item: NotificationListItem,
63+
originalEvent: Event,
64+
};
65+
6166
type Footnote = Record<string, any>;
6267

6368
/**
@@ -141,6 +146,18 @@ const ICON_PER_STATUS_DESIGN = {
141146
bubbles: true,
142147
})
143148

149+
/**
150+
* Fired when the component is activated either with a mouse/tap or by using the Enter or Space key.
151+
*
152+
* @since 2.22.0
153+
* @public
154+
* @param {NotificationListItem} item The activated item.
155+
* @param {Event} originalEvent The original event from the user interaction.
156+
*/
157+
@event("click", {
158+
bubbles: true,
159+
})
160+
144161
/**
145162
* Fired when the `Close` button is pressed.
146163
* @param {HTMLElement} item the closed item.
@@ -160,6 +177,7 @@ const ICON_PER_STATUS_DESIGN = {
160177
class NotificationListItem extends NotificationListItemBase {
161178
eventDetails!: NotificationListItemBase["eventDetails"] & {
162179
_press: NotificationListItemPressEventDetail,
180+
click: NotificationListItemClickEventDetail,
163181
close: NotificationListItemCloseEventDetail,
164182
_close: NotificationListItemCloseEventDetail,
165183
}
@@ -478,8 +496,9 @@ class NotificationListItem extends NotificationListItemBase {
478496
/**
479497
* Event handlers
480498
*/
481-
_onclick() {
482-
this.fireItemPress();
499+
_onclick(e: MouseEvent) {
500+
e.stopPropagation();
501+
this.fireItemPress(e);
483502
}
484503

485504
_onShowMoreClick(e: UI5CustomEvent<Link, "click">) {
@@ -549,14 +568,15 @@ class NotificationListItem extends NotificationListItemBase {
549568
/**
550569
* Private
551570
*/
552-
fireItemPress() {
571+
fireItemPress(e: Event) {
553572
if (this.getFocusDomRef()!.matches(":has(:focus-within)")) {
554573
return;
555574
}
556575

557576
// NotificationListItem will never be assigned to a variable of type ListItemBase
558577
// typescipt complains here, if that is the case, the parameter to the _press event handler could be a ListItemBase item,
559578
// but this is never the case, all components are used by their class and never assigned to a variable with a type of ListItemBase
579+
this.fireDecoratorEvent("click", { item: this, originalEvent: e });
560580
this.fireDecoratorEvent("_press", { item: this });
561581
}
562582

@@ -591,5 +611,6 @@ NotificationListItem.define();
591611
export default NotificationListItem;
592612
export type {
593613
NotificationListItemPressEventDetail,
614+
NotificationListItemClickEventDetail,
594615
NotificationListItemCloseEventDetail,
595616
};

packages/fiori/src/Search.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ interface ISearchSuggestionItem extends UI5Element {
4444
selected: boolean;
4545
text: string;
4646
items?: ISearchSuggestionItem[];
47+
eventDetails: { click?: object };
4748
}
4849

4950
type SearchEventDetails = {

packages/fiori/src/SearchItemShowMore.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import customElement from "@ui5/webcomponents-base/dist/decorators/customElement
22
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
33
import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js";
44
import ListItemBase from "@ui5/webcomponents/dist/ListItemBase.js";
5+
import type { ListItemBaseClickEventDetail } from "@ui5/webcomponents/dist/ListItemBase.js";
56
import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js";
67
import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js";
78
import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js";
@@ -11,7 +12,7 @@ import SearchItemShowMoreCss from "./generated/themes/SearchItemShowMore.css.js"
1112
import { SEARCH_ITEM_SHOW_MORE_COUNT, SEARCH_ITEM_SHOW_MORE_NO_COUNT } from "./generated/i18n/i18n-defaults.js";
1213
import { isEnter, isSpace } from "@ui5/webcomponents-base/dist/Keys.js";
1314

14-
type ShowMoreItemClickEventDetail = {
15+
type ShowMoreItemClickEventDetail = ListItemBaseClickEventDetail & {
1516
fromKeyboard: boolean;
1617
}
1718

@@ -100,7 +101,7 @@ If a number is provided, it displays "Show more (N)", where N is that number.
100101

101102
_onclick(e: MouseEvent | KeyboardEvent, fromKeyboard = false) {
102103
e.stopImmediatePropagation();
103-
this.fireDecoratorEvent("click", { fromKeyboard });
104+
this.fireDecoratorEvent("click", { item: this, originalEvent: e, fromKeyboard });
104105
}
105106

106107
_onkeydown(e: KeyboardEvent) {

packages/main/cypress/specs/TabContainer.cy.tsx

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,3 +1287,103 @@ describe("TabContainer popover", () => {
12871287
cy.get("@list").find(".ui5-tab-overflow-itemContent-wrapper").eq(3).should("have.css", "padding-left", "24px");
12881288
});
12891289
});
1290+
1291+
describe("Tab semantic click event", () => {
1292+
it("fires click event on tab when clicked", () => {
1293+
cy.mount(
1294+
<TabContainer id="tc1" collapsed>
1295+
<Tab id="tab1" text="Products"></Tab>
1296+
<Tab text="Laptops" selected></Tab>
1297+
</TabContainer>
1298+
);
1299+
1300+
cy.get("#tab1").then(($tab) => {
1301+
$tab[0].addEventListener("click", cy.stub().as("clickStub"));
1302+
});
1303+
1304+
cy.get("#tc1").shadow().find(".ui5-tab-strip-item:nth-child(1)").realClick();
1305+
1306+
cy.get("@clickStub").should("have.been.calledOnce");
1307+
cy.get("@clickStub").should((stub: any) => {
1308+
const event = stub.firstCall.args[0];
1309+
expect(event).to.be.instanceOf(CustomEvent);
1310+
expect(event.detail.tab).to.exist;
1311+
expect(event.detail.originalEvent).to.be.instanceOf(MouseEvent);
1312+
});
1313+
});
1314+
1315+
it("fires click event on tab when activated with Enter key", () => {
1316+
cy.mount(
1317+
<TabContainer id="tc1" collapsed>
1318+
<Tab id="tab1" text="Products"></Tab>
1319+
<Tab text="Laptops" selected></Tab>
1320+
</TabContainer>
1321+
);
1322+
1323+
cy.get("#tab1").then(($tab) => {
1324+
$tab[0].addEventListener("click", cy.stub().as("clickStub"));
1325+
});
1326+
1327+
cy.get("#tc1").shadow().find(".ui5-tab-strip-item:nth-child(1)").realClick();
1328+
cy.realPress("Enter");
1329+
1330+
cy.get("@clickStub").should("have.been.calledTwice");
1331+
});
1332+
1333+
it("fires click event on tab when activated with Space key", () => {
1334+
cy.mount(
1335+
<TabContainer id="tc1" collapsed>
1336+
<Tab id="tab1" text="Products"></Tab>
1337+
<Tab text="Laptops" selected></Tab>
1338+
</TabContainer>
1339+
);
1340+
1341+
cy.get("#tab1").then(($tab) => {
1342+
$tab[0].addEventListener("click", cy.stub().as("clickStub"));
1343+
});
1344+
1345+
cy.get("#tc1").shadow().find(".ui5-tab-strip-item:nth-child(1)").realClick();
1346+
cy.realPress("Space");
1347+
1348+
cy.get("@clickStub").should("have.been.calledTwice");
1349+
});
1350+
1351+
it("does not fire click event on disabled tab", () => {
1352+
cy.mount(
1353+
<TabContainer id="tc1" collapsed>
1354+
<Tab id="tab1" text="Products" disabled></Tab>
1355+
<Tab text="Laptops" selected></Tab>
1356+
</TabContainer>
1357+
);
1358+
1359+
cy.get("#tab1").then(($tab) => {
1360+
$tab[0].addEventListener("click", cy.stub().as("clickStub"));
1361+
});
1362+
1363+
cy.get("#tc1").shadow().find(".ui5-tab-strip-item:nth-child(1)").click({ force: true });
1364+
1365+
cy.get("@clickStub").should("not.have.been.called");
1366+
});
1367+
1368+
it("fires both click on Tab and tab-select on TabContainer", () => {
1369+
cy.mount(
1370+
<TabContainer id="tc1" collapsed>
1371+
<Tab id="tab1" text="Products"></Tab>
1372+
<Tab text="Laptops" selected></Tab>
1373+
</TabContainer>
1374+
);
1375+
1376+
cy.get("#tab1").then(($tab) => {
1377+
$tab[0].addEventListener("click", cy.stub().as("tabClickStub"));
1378+
});
1379+
1380+
cy.get("#tc1").then(($tc) => {
1381+
$tc[0].addEventListener("ui5-tab-select", cy.stub().as("tabSelectStub"));
1382+
});
1383+
1384+
cy.get("#tc1").shadow().find(".ui5-tab-strip-item:nth-child(1)").realClick();
1385+
1386+
cy.get("@tabClickStub").should("have.been.calledOnce");
1387+
cy.get("@tabSelectStub").should("have.been.calledOnce");
1388+
});
1389+
});

packages/main/src/ComboBox.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ interface IComboBoxItem extends UI5Element {
107107
selected?: boolean,
108108
additionalText?: string,
109109
_isVisible?: boolean,
110-
items?: Array<IComboBoxItem>
110+
items?: Array<IComboBoxItem>,
111+
eventDetails: { click?: object },
111112
}
112113

113114
type ValueStateAnnouncement = Record<Exclude<ValueState, ValueState.None>, string>;

packages/main/src/Input.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ interface IInputSuggestionItem extends UI5Element {
110110
focused: boolean;
111111
additionalText?: string;
112112
items?: IInputSuggestionItem[];
113+
eventDetails: { click?: object };
113114
}
114115

115116
interface IInputSuggestionItemSelectable extends IInputSuggestionItem {

packages/main/src/ListItemBase.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ type ListItemBasePressEventDetail = {
2525
key?: string,
2626
}
2727

28+
type ListItemBaseClickEventDetail = {
29+
item: ListItemBase,
30+
originalEvent: Event,
31+
}
32+
2833
/**
2934
* @class
3035
* A class to serve as a foundation
@@ -38,6 +43,19 @@ type ListItemBasePressEventDetail = {
3843
renderer: jsxRenderer,
3944
styles: [styles, draggableElementStyles],
4045
})
46+
/**
47+
* Fired when the component is activated either with a mouse/tap or by using the Enter or Space key.
48+
*
49+
* **Note:** The event will not be fired if the `disabled` property is set to `true`.
50+
*
51+
* @since 2.22.0
52+
* @public
53+
* @param {ListItemBase} item The activated item.
54+
* @param {Event} originalEvent The original event from the user interaction.
55+
*/
56+
@event("click", {
57+
bubbles: true,
58+
})
4159
@event("request-tabindex-change", {
4260
bubbles: true,
4361
})
@@ -56,6 +74,7 @@ type ListItemBasePressEventDetail = {
5674
})
5775
class ListItemBase extends UI5Element implements ITabbable {
5876
eventDetails!: {
77+
"click": ListItemBaseClickEventDetail,
5978
"request-tabindex-change": FocusEvent,
6079
"_press": ListItemBasePressEventDetail,
6180
"_focused": FocusEvent,
@@ -168,6 +187,7 @@ class ListItemBase extends UI5Element implements ITabbable {
168187
if (this.getFocusDomRef()!.matches(":has(:focus-within)") || this._isDisabledInteractiveContentClicked(e)) {
169188
return;
170189
}
190+
e.stopPropagation();
171191
this.fireItemPress(e);
172192
}
173193

@@ -233,6 +253,7 @@ class ListItemBase extends UI5Element implements ITabbable {
233253
if (isEnter(e as KeyboardEvent)) {
234254
e.preventDefault();
235255
}
256+
this.fireDecoratorEvent("click", { item: this, originalEvent: e });
236257
this.fireDecoratorEvent("_press", { item: this, selected: this.selected, key: (e as KeyboardEvent).key });
237258
}
238259

@@ -313,4 +334,5 @@ export default ListItemBase;
313334

314335
export type {
315336
ListItemBasePressEventDetail,
337+
ListItemBaseClickEventDetail,
316338
};

packages/main/src/ListItemGroup.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ type ListItemGroupMoveEventDetail = {
8383

8484
class ListItemGroup extends UI5Element {
8585
eventDetails!: {
86+
"click"?: object,
8687
"move-over": ListItemGroupMoveEventDetail,
8788
"move": ListItemGroupMoveEventDetail,
8889
}

packages/main/src/Menu.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ interface IMenuItem extends UI5Element {
6060
isMenuItem?: boolean;
6161
isSeparator?: boolean;
6262
isGroup?: boolean;
63+
eventDetails: { click?: object };
6364
}
6465

6566
type MenuItemClickEventDetail = {

0 commit comments

Comments
 (0)