diff --git a/packages/main/cypress/specs/ComboBox.cy.tsx b/packages/main/cypress/specs/ComboBox.cy.tsx
index 0cd2803c939b..ec75f3a15e0b 100644
--- a/packages/main/cypress/specs/ComboBox.cy.tsx
+++ b/packages/main/cypress/specs/ComboBox.cy.tsx
@@ -2911,6 +2911,67 @@ describe("Event firing", () => {
cy.get("@selectionChangeSpy")
.should("have.been.calledWith", Cypress.sinon.match.has("detail", Cypress.sinon.match.has("item")));
});
+
+ it("selection-change trigger is 'Typeahead' when text is auto-completed", () => {
+ cy.mount(
+
+
+
+
+
+ );
+
+ cy.get("[ui5-combobox]")
+ .as("combo")
+ .invoke('on', 'ui5-selection-change', cy.spy().as('selectionChangeSpy'));
+
+ cy.get("@combo").shadow().find("input").focus().realType("Bul");
+
+ cy.get("@selectionChangeSpy").should("have.been.calledWithMatch", Cypress.sinon.match(event => {
+ return event.detail.item?.text === "Bulgaria" && event.detail.trigger === "Typeahead";
+ }));
+ });
+
+ it("selection-change trigger is 'Click' when an item is clicked in the dropdown", () => {
+ cy.mount(
+
+
+
+
+
+ );
+
+ cy.get("[ui5-combobox]")
+ .as("combo")
+ .invoke('on', 'ui5-selection-change', cy.spy().as('selectionChangeSpy'));
+
+ cy.get("@combo").shadow().find(".inputIcon").realClick();
+ cy.get("@combo").find("ui5-cb-item").eq(2).realClick();
+
+ cy.get("@selectionChangeSpy").should("have.been.calledWithMatch", Cypress.sinon.match(event => {
+ return event.detail.item?.text === "Canada" && event.detail.trigger === "Click";
+ }));
+ });
+
+ it("selection-change trigger is 'Keyboard' when navigating with arrow keys", () => {
+ cy.mount(
+
+
+
+
+
+ );
+
+ cy.get("[ui5-combobox]")
+ .as("combo")
+ .invoke('on', 'ui5-selection-change', cy.spy().as('selectionChangeSpy'));
+
+ cy.get("@combo").shadow().find("input").focus().realPress("F4").realPress("ArrowDown");
+
+ cy.get("@selectionChangeSpy").should("have.been.calledWithMatch", Cypress.sinon.match(event => {
+ return event.detail.trigger === "Keyboard";
+ }));
+ });
});
describe("Scrolling", () => {
diff --git a/packages/main/src/ComboBox.ts b/packages/main/src/ComboBox.ts
index c7bc268c62c0..2eed3a0ec0c2 100644
--- a/packages/main/src/ComboBox.ts
+++ b/packages/main/src/ComboBox.ts
@@ -88,6 +88,7 @@ import "./ComboBoxItemGroup.js";
// eslint-disable-next-line
import { isInstanceOfComboBoxItemGroup } from "./ComboBoxItemGroup.js";
import type ComboBoxFilter from "./types/ComboBoxFilter.js";
+import ComboBoxSelectionChangeTrigger from "./types/ComboBoxSelectionChangeTrigger.js";
import type Input from "./Input.js";
import type { InputEventDetail } from "./Input.js";
import type { ListItemBaseClickEventDetail } from "./ListItemBase.js";
@@ -124,6 +125,7 @@ enum ValueStateIconMapping {
type ComboBoxSelectionChangeEventDetail = {
item: ComboBoxItem | null,
+ trigger: `${ComboBoxSelectionChangeTrigger}`,
};
/**
@@ -241,6 +243,7 @@ type ComboBoxSelectionChangeEventDetail = {
/**
* Fired when selection is changed by user interaction
* @param {IComboBoxItem} item item to be selected.
+ * @param {ComboBoxSelectionChangeTrigger} trigger source of the selection change - typeahead, click or keyboard navigation.
* @public
*/
@event("selection-change", {
@@ -506,6 +509,7 @@ class ComboBox extends UI5Element implements IFormInputElement {
_autocomplete = false;
_isKeyNavigation = false;
_selectionPerformed = false;
+ _selectionTrigger?: `${ComboBoxSelectionChangeTrigger}`;
_lastValue: string;
_selectedItemText = "";
_userTypedValue = "";
@@ -962,6 +966,7 @@ class ComboBox extends UI5Element implements IFormInputElement {
}
_handleArrowDown(e: KeyboardEvent, indexOfItem: number) {
+ this._selectionTrigger = ComboBoxSelectionChangeTrigger.Keyboard;
const isOpen = this.open;
if (this.focused && indexOfItem === -1 && isOpen) {
@@ -983,6 +988,7 @@ class ComboBox extends UI5Element implements IFormInputElement {
}
_handleArrowUp(e: KeyboardEvent, indexOfItem: number) {
+ this._selectionTrigger = ComboBoxSelectionChangeTrigger.Keyboard;
const isOpen = this.open;
if (indexOfItem === 0) {
@@ -1002,6 +1008,7 @@ class ComboBox extends UI5Element implements IFormInputElement {
}
_handlePageUp(e: KeyboardEvent, indexOfItem: number) {
+ this._selectionTrigger = ComboBoxSelectionChangeTrigger.Keyboard;
const allItems = this._getItems();
const isProposedIndexValid = indexOfItem - SKIP_ITEMS_SIZE > -1;
indexOfItem = isProposedIndexValid ? indexOfItem - SKIP_ITEMS_SIZE : 0;
@@ -1011,6 +1018,7 @@ class ComboBox extends UI5Element implements IFormInputElement {
}
_handlePageDown(e: KeyboardEvent, indexOfItem: number) {
+ this._selectionTrigger = ComboBoxSelectionChangeTrigger.Keyboard;
const allItems = this._getItems();
const itemsLength = allItems.length;
const isProposedIndexValid = indexOfItem + SKIP_ITEMS_SIZE < itemsLength;
@@ -1022,12 +1030,14 @@ class ComboBox extends UI5Element implements IFormInputElement {
}
_handleHome(e: KeyboardEvent) {
+ this._selectionTrigger = ComboBoxSelectionChangeTrigger.Keyboard;
const shouldMoveForward = isInstanceOfComboBoxItemGroup(this._filteredItems[0]) && !this.open;
this._handleItemNavigation(e, 0, shouldMoveForward);
}
_handleEnd(e: KeyboardEvent) {
+ this._selectionTrigger = ComboBoxSelectionChangeTrigger.Keyboard;
this._handleItemNavigation(e, this._getItems().length - 1, true /* isForward */);
}
@@ -1351,22 +1361,23 @@ class ComboBox extends UI5Element implements IFormInputElement {
}
const noUserInteraction = !this.focused && !this._isKeyNavigation && !this._selectionPerformed && !this._iconPressed;
- // Skip firing "selection-change" event if this is initial rendering or if there has been no user interaction yet
if (this._initialRendering || noUserInteraction) {
return;
}
- // Fire selection-change event only when selection actually changes
if (previouslySelectedItem !== itemToBeSelected) {
+ const trigger = this._selectionTrigger || ComboBoxSelectionChangeTrigger.Typeahead;
+ this._selectionTrigger = undefined;
+
if (itemToBeSelected) {
- // New item selected
this.fireDecoratorEvent("selection-change", {
item: itemToBeSelected as ComboBoxItem,
+ trigger,
});
} else if (previouslySelectedItem) {
- // Selection cleared - fire event with 'null'
this.fireDecoratorEvent("selection-change", {
item: null,
+ trigger,
});
}
}
@@ -1427,6 +1438,7 @@ class ComboBox extends UI5Element implements IFormInputElement {
if (!item.selected) {
this.fireDecoratorEvent("selection-change", {
item,
+ trigger: ComboBoxSelectionChangeTrigger.Click,
});
}
@@ -1753,6 +1765,7 @@ class ComboBox extends UI5Element implements IFormInputElement {
ComboBox.define();
export default ComboBox;
+export { ComboBoxSelectionChangeTrigger };
export type {
ComboBoxSelectionChangeEventDetail,
diff --git a/packages/main/src/types/ComboBoxSelectionChangeTrigger.ts b/packages/main/src/types/ComboBoxSelectionChangeTrigger.ts
new file mode 100644
index 000000000000..394c8d764dea
--- /dev/null
+++ b/packages/main/src/types/ComboBoxSelectionChangeTrigger.ts
@@ -0,0 +1,28 @@
+/**
+ * Describes the source of a `selection-change` event fired by the `ui5-combobox`.
+ * @public
+ * @since 2.24.0
+ */
+enum ComboBoxSelectionChangeTrigger {
+ /**
+ * Selection caused by typeahead (auto-complete while typing).
+ * @public
+ */
+ Typeahead = "Typeahead",
+
+ /**
+ * Selection caused by clicking or tapping an item in the dropdown.
+ * @public
+ */
+ Click = "Click",
+
+ /**
+ * Selection caused by keyboard navigation (Arrow keys, Home, End, Page Up/Down).
+ *
+ * Note: pressing Enter does not fire `selection-change` - it fires the `change` event instead.
+ * @public
+ */
+ Keyboard = "Keyboard",
+}
+
+export default ComboBoxSelectionChangeTrigger;
diff --git a/packages/main/test/pages/ComboBox.html b/packages/main/test/pages/ComboBox.html
index 0d2e49ee54cf..0c6afc080384 100644
--- a/packages/main/test/pages/ComboBox.html
+++ b/packages/main/test/pages/ComboBox.html
@@ -517,6 +517,102 @@
ComboBox Composition
+
+
Selection Trigger Flag Demo
+
The selection-change event includes a trigger property: "Typeahead", "Click", or "Keyboard"
+
+
+
+
+
+
+
+
+
+
+
+
selection-change events (with trigger):
+
(none yet)
+
+
+
change events:
+
(none yet)
+
+
+
+
+ Trigger types:
+ Typeahead
+ Keyboard
+ Click
+
+
+
Clear Log
+
+
+
+
+