diff --git a/packages/fiori/cypress/specs/Search.cy.tsx b/packages/fiori/cypress/specs/Search.cy.tsx
index bd20b91d2dec..dc2ab16399a0 100644
--- a/packages/fiori/cypress/specs/Search.cy.tsx
+++ b/packages/fiori/cypress/specs/Search.cy.tsx
@@ -88,6 +88,76 @@ describe("Properties", () => {
.should("be.focused");
});
+ it("items slot arrow navigation with groups and headerText", () => {
+ cy.mount(
+
+
+
+
+
+
+ );
+
+ cy.get("[ui5-search]")
+ .shadow()
+ .find("input")
+ .realClick();
+
+ cy.get("[ui5-search]")
+ .realPress("L");
+
+ cy.get("[ui5-search]")
+ .should("be.focused");
+
+ cy.get("[ui5-search]")
+ .realPress("ArrowDown");
+
+ cy.get("ui5-search-item-group")
+ .shadow()
+ .find("[ui5-li-group-header]")
+ .should("be.focused");
+
+ cy.get("[ui5-search]")
+ .realPress("ArrowUp");
+
+ cy.get("[ui5-search]")
+ .should("be.focused");
+ });
+
+ it("items slot arrow navigation with groups and no headerText", () => {
+ cy.mount(
+
+
+
+
+
+
+ );
+
+ cy.get("[ui5-search]")
+ .shadow()
+ .find("input")
+ .realClick();
+
+ cy.get("[ui5-search]")
+ .realPress("L");
+
+ cy.get("[ui5-search]")
+ .should("be.focused");
+
+ cy.get("[ui5-search]")
+ .realPress("ArrowDown");
+
+ cy.get("ui5-search-item").eq(0)
+ .should("be.focused");
+
+ cy.get("[ui5-search]")
+ .realPress("ArrowUp");
+
+ cy.get("[ui5-search]")
+ .should("be.focused");
+ })
+
it("items should be shown instead of illustration of both present ", () => {
cy.mount(
diff --git a/packages/fiori/src/Search.ts b/packages/fiori/src/Search.ts
index 2c62d802f753..b9c877efb1a4 100644
--- a/packages/fiori/src/Search.ts
+++ b/packages/fiori/src/Search.ts
@@ -336,7 +336,7 @@ class Search extends SearchField {
return StartsWithPerTerm(str, this._flattenItems.filter(item => !this._isGroupItem(item)), "text");
}
- _isGroupItem(item: ISearchSuggestionItem) {
+ _isGroupItem(item: HTMLElement): item is SearchItemGroup {
return item.hasAttribute("ui5-search-item-group");
}
@@ -354,7 +354,8 @@ class Search extends SearchField {
}
_handleArrowDown() {
- const firstListItem = this._getItemsList()?.getSlottedNodes("items")[0];
+ const focusableItems = this._getItemsList().listItems;
+ const firstListItem = focusableItems.at(0);
if (this.open) {
this._deselectItems();
@@ -449,8 +450,13 @@ class Search extends SearchField {
}
_onItemKeydown(e: KeyboardEvent) {
- const isFirstItem = this._flattenItems[0] === e.target;
- const isLastItem = this._flattenItems[this._flattenItems.length - 1] === e.target;
+ const target = e.target as HTMLElement;
+ // if focus is on the group header (in group's shadow dom) the target is the group itself,
+ // if so using getFocusDomRef ensures the actual focused element is used
+ const focusedItem = this._isGroupItem(target) ? target?.getFocusDomRef() : target;
+ const focusableItems = this._getItemsList().listItems;
+ const isFirstItem = focusableItems.at(0) === focusedItem;
+ const isLastItem = focusableItems.at(-1) === focusedItem;
const isArrowUp = isUp(e);
const isArrowDown = isDown(e);
const isTab = isTabNext(e);
@@ -602,7 +608,7 @@ class Search extends SearchField {
get _flattenItems(): Array {
return this.getSlottedNodes("items").flatMap(item => {
- return this._isGroupItem(item) ? [item, ...item.items!] : [item];
+ return this._isGroupItem(item) ? [item, ...item.items] : [item];
});
}
diff --git a/packages/fiori/src/SearchItemGroup.ts b/packages/fiori/src/SearchItemGroup.ts
index fcc9c162f6fe..96ab8f717d6b 100644
--- a/packages/fiori/src/SearchItemGroup.ts
+++ b/packages/fiori/src/SearchItemGroup.ts
@@ -1,6 +1,5 @@
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
import ListItemGroup from "@ui5/webcomponents/dist/ListItemGroup.js";
-import type ListItemGroupHeader from "@ui5/webcomponents/dist/ListItemGroupHeader.js";
import SearchItemGroupCss from "./generated/themes/SearchItemGroup.css.js";
import ListBoxItemGroupTemplate from "@ui5/webcomponents/dist/ListBoxItemGroupTemplate.js";
@@ -26,10 +25,6 @@ class SearchItemGroup extends ListItemGroup {
get isGroupItem(): boolean {
return true;
}
-
- getFocusDomRef() {
- return this.shadowRoot!.querySelector("[ui5-li-group-header]") as ListItemGroupHeader;
- }
}
SearchItemGroup.define();
diff --git a/packages/main/cypress/specs/ListItemGroup.cy.tsx b/packages/main/cypress/specs/ListItemGroup.cy.tsx
index d06c3937b620..009c18e8dd9e 100644
--- a/packages/main/cypress/specs/ListItemGroup.cy.tsx
+++ b/packages/main/cypress/specs/ListItemGroup.cy.tsx
@@ -1,3 +1,5 @@
+import List from "../../src/List.js";
+import ListItemStandard from "../../src/ListItemStandard.js";
import ListItemGroup from "../../src/ListItemGroup.js";
describe("ListItemGroup Tests", () => {
@@ -308,4 +310,43 @@ describe("List drag and drop tests", () => {
cy.get("@list2").children().should("have.length", 4);
cy.get("@list2").find("a").should("exist").and("contain.text", "http://sap.com");
});
+});
+
+describe("Focus", () => {
+ it("getFocusDomRef should return header element if available", () => {
+ cy.mount(
+
+
+ Item 1
+ Item 2
+ Item 3
+
+
+ );
+
+ cy.get("[ui5-li-group]")
+ .then(($el) => {
+ const group = $el[0];
+ expect(group.getFocusDomRef()).to.have.attr("ui5-li-group-header");
+ });
+
+ });
+
+ it("getFocusDomRef should return list item when header is not available", () => {
+ cy.mount(
+
+
+ Item 1
+ Item 2
+ Item 3
+
+
+ );
+
+ cy.get("[ui5-li-group]")
+ .then(($el) => {
+ const group = $el[0];
+ expect(group.getFocusDomRef()).to.have.attr("ui5-li");
+ });
+ });
});
\ No newline at end of file
diff --git a/packages/main/src/ListItemGroup.ts b/packages/main/src/ListItemGroup.ts
index b97829abb761..9ddfa567fb3d 100644
--- a/packages/main/src/ListItemGroup.ts
+++ b/packages/main/src/ListItemGroup.ts
@@ -212,6 +212,10 @@ class ListItemGroup extends UI5Element {
}
return placements;
}
+
+ getFocusDomRef() {
+ return this.groupHeaderItem || this.items.at(0);
+ }
}
ListItemGroup.define();