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..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); - 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 : item?.items?.find(i => i.text === this.value)); + 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)); + } } });