From 7a014ae7ce09ada5271bbe1c513c9688ae538f47 Mon Sep 17 00:00:00 2001 From: Evdokia Yordanova Date: Fri, 25 Apr 2025 16:40:14 +0300 Subject: [PATCH 1/2] fix(ui5-combobox): properly navigate with arrow up/down keys --- packages/main/cypress/specs/ComboBox.cy.tsx | 71 +++++++++++++++++++++ packages/main/src/ComboBox.ts | 4 +- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/packages/main/cypress/specs/ComboBox.cy.tsx b/packages/main/cypress/specs/ComboBox.cy.tsx index d133e54b2f72..1c1e51f81098 100644 --- a/packages/main/cypress/specs/ComboBox.cy.tsx +++ b/packages/main/cypress/specs/ComboBox.cy.tsx @@ -85,6 +85,77 @@ describe("Keyboard interaction", () => { cy.get("@combobox") .find("[ui5-cb-item]").eq(2).should("have.prop", "selected", true); }); + it("tests navigating with arrow down and up when item text is the same as in the previous selected item", () => { + cy.mount( + + + + + + ); + + cy.get("[ui5-combobox]").as("combobox"); + + cy.get("@combobox") + .shadow() + .find("input") + .as("inner"); + + cy.get("@inner").focus(); + + cy.get("@inner").realPress("F4"); + cy.get("@combobox") + .find("[ui5-cb-item]").eq(0).should("have.prop", "selected", true); + + cy.get("@inner").realPress("ArrowDown"); + cy.get("@combobox") + .find("[ui5-cb-item]").eq(1).should("have.prop", "selected", true); + + cy.get("@inner").realPress("ArrowDown"); + cy.get("@combobox") + .find("[ui5-cb-item]").eq(2).should("have.prop", "selected", true); + + cy.get("@inner").realPress("ArrowUp"); + cy.get("@combobox") + .find("[ui5-cb-item]").eq(1).should("have.prop", "selected", true); + }); + + it("tests navigating with arrow down and up when item text is the same as in the previous selected item (with grouping)", () => { + cy.mount( + + + + + + + + ); + + cy.get("[ui5-combobox]").as("combobox"); + + cy.get("@combobox") + .shadow() + .find("input") + .as("inner"); + + cy.get("@inner").focus(); + + cy.get("@inner").realPress("F4"); + cy.get("@combobox") + .find("[ui5-cb-item]").eq(0).should("have.prop", "selected", true); + + cy.get("@inner").realPress("ArrowDown"); + cy.get("@combobox") + .find("[ui5-cb-item]").eq(1).should("have.prop", "selected", true); + + cy.get("@inner").realPress("ArrowDown"); + cy.get("@combobox") + .find("[ui5-cb-item]").eq(2).should("have.prop", "selected", true); + + cy.get("@inner").realPress("ArrowUp"); + cy.get("@combobox") + .find("[ui5-cb-item]").eq(1).should("have.prop", "selected", true); + }); }); describe("Event firing", () => { diff --git a/packages/main/src/ComboBox.ts b/packages/main/src/ComboBox.ts index d9564c7a8d28..2b5da5c0ffbe 100644 --- a/packages/main/src/ComboBox.ts +++ b/packages/main/src/ComboBox.ts @@ -1081,7 +1081,7 @@ class ComboBox extends UI5Element implements IFormInputElement { const matchingItems: Array = this._startsWithMatchingItems(current); if (matchingItems.length) { - const exactMatch = matchingItems.find(item => item.text === current); + const exactMatch = matchingItems.find(item => item.text === current && item.focused); return exactMatch ?? matchingItems[0]; } } @@ -1101,7 +1101,7 @@ class ComboBox extends UI5Element implements IFormInputElement { this._filteredItems.forEach(item => { if (!shouldSelectionBeCleared && !itemToBeSelected) { - itemToBeSelected = ((!item.isGroupItem && (item.text === this.value)) ? item : item?.items?.find(i => i.text === this.value)); + itemToBeSelected = ((!item.isGroupItem && (item.text === this.value && item === currentlyFocusedItem)) ? item : item?.items?.find(i => i.text === this.value && i.focused)); } }); From 85ebfcbb8a76cc6337f1bd55d265cdba6bd0717e Mon Sep 17 00:00:00 2001 From: Evdokia Yordanova Date: Wed, 7 May 2025 02:29:40 +0300 Subject: [PATCH 2/2] fix(ui5-combobox): properly navigate with arrow up/down keys WIP commit start using selected item to select the correct item when there are multiple items with the same text --- packages/main/src/ComboBox.ts | 36 +++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/packages/main/src/ComboBox.ts b/packages/main/src/ComboBox.ts index 2b5da5c0ffbe..38bb32e03986 100644 --- a/packages/main/src/ComboBox.ts +++ b/packages/main/src/ComboBox.ts @@ -808,13 +808,13 @@ class ComboBox extends UI5Element implements IFormInputElement { return; } // autocomplete - this._handleTypeAhead(this.value, this.open ? this._userTypedValue : "", false); + this._handleTypeAhead(this.value, this.open ? this._userTypedValue : "", false, nextItem); this.fireDecoratorEvent("input"); } - _handleTypeAhead(value: string, filterValue: string, checkForGroupItem: boolean) { - const item = this._getFirstMatchingItem(value); + _handleTypeAhead(value: string, filterValue: string, checkForGroupItem: boolean, nextItem?: IComboBoxItem) { + const item = this._getMatchingItem(value, nextItem || {} as IComboBoxItem); if (!item) { return; @@ -1069,7 +1069,7 @@ class ComboBox extends UI5Element implements IFormInputElement { return [...filteredItemGroups, ...filteredItems]; } - _getFirstMatchingItem(current: string): IComboBoxItem | void { + _getMatchingItem(current: string, nextItem: IComboBoxItem): IComboBoxItem | void { const allItems = this._getItems(); const currentlyFocusedItem = allItems.find(item => item.focused === true); @@ -1081,8 +1081,19 @@ class ComboBox extends UI5Element implements IFormInputElement { const matchingItems: Array = this._startsWithMatchingItems(current); if (matchingItems.length) { - const exactMatch = matchingItems.find(item => item.text === current && item.focused); - return exactMatch ?? matchingItems[0]; + let exactMatch: IComboBoxItem | undefined; + + if (this.open) { + exactMatch = matchingItems.find(item => item.text === current && item.focused); + } else { + const currentIndex = allItems.indexOf(nextItem); + const previousItem = allItems[currentIndex - 1]; + const nextItemInList = allItems[currentIndex + 1]; + + exactMatch = matchingItems.find(item => item.text === current && (item === previousItem || item === nextItemInList)); + } + + return (exactMatch ?? matchingItems.find(item => item === currentlyFocusedItem) ?? matchingItems[0]); } } @@ -1095,13 +1106,22 @@ class ComboBox extends UI5Element implements IFormInputElement { } _selectMatchingItem() { - const currentlyFocusedItem = this.items.find(item => item.focused); + const allItems = this.items; + const currentlyFocusedItem = allItems.find(item => item.focused); + const currentlySelectedItem = allItems.find(item => (!isInstanceOfComboBoxItemGroup(item) ? item.selected : item.items?.some(i => i.selected))); const shouldSelectionBeCleared = currentlyFocusedItem && currentlyFocusedItem.isGroupItem; let itemToBeSelected: IComboBoxItem | undefined; this._filteredItems.forEach(item => { if (!shouldSelectionBeCleared && !itemToBeSelected) { - itemToBeSelected = ((!item.isGroupItem && (item.text === this.value && item === currentlyFocusedItem)) ? item : item?.items?.find(i => i.text === this.value && i.focused)); + if (currentlySelectedItem) { + const currentIndex = allItems.indexOf(currentlySelectedItem); + const previousItem = allItems[currentIndex - 1]; + const nextItemInList = allItems[currentIndex + 1]; + itemToBeSelected = ((!item.isGroupItem && (item.text === this.value) && (item === previousItem || item === nextItemInList)) ? item : item?.items?.find(i => i.text === this.value && (i === previousItem || i === nextItemInList))); + } else { + itemToBeSelected = ((!item.isGroupItem && (item.text === this.value)) ? item : item?.items?.find(i => i.text === this.value)); + } } });