diff --git a/packages/fiori/cypress/specs/ShellBar.cy.tsx b/packages/fiori/cypress/specs/ShellBar.cy.tsx
index 4ac34c011211..86402d5712c7 100644
--- a/packages/fiori/cypress/specs/ShellBar.cy.tsx
+++ b/packages/fiori/cypress/specs/ShellBar.cy.tsx
@@ -684,4 +684,74 @@ describe("Keyboard Navigation", () => {
.find(".ui5-shellbar-logo-area")
.should("not.exist");
});
+
+ it("Test arrow navigation within search input respects cursor position", () => {
+ cy.mount(
+
+
+
+
+
+ );
+ cy.wait(RESIZE_THROTTLE_RATE);
+
+ function placeAtStartOfInput() {
+ cy.get("[ui5-shellbar] [slot='searchField']")
+ .shadow()
+ .find("input")
+ .then($input => {
+ $input[0].setSelectionRange(0, 0);
+ });
+ }
+ function placeAtEndOfInput() {
+ cy.get("[ui5-shellbar] [slot='searchField']")
+ .shadow()
+ .find("input")
+ .then($input => {
+ const inputLength = $input.val().toString().length;
+ $input[0].setSelectionRange(inputLength, inputLength);
+ });
+ }
+ function placeInMiddleOfInput() {
+ cy.get("[ui5-shellbar] [slot='searchField']")
+ .shadow()
+ .find("input")
+ .then($input => {
+ const inputLength = $input.val().toString().length;
+ const middlePosition = Math.floor(inputLength / 2);
+ $input[0].setSelectionRange(middlePosition, middlePosition);
+ });
+ }
+
+ // Focus the search input
+ cy.get("[ui5-shellbar] [slot='searchField']")
+ .realClick()
+ .shadow()
+ .find("input")
+ .as("nativeInput");
+
+ placeAtStartOfInput();
+ // Press left arrow - should move focus away from input since cursor is at start
+ cy.get("@nativeInput").type("{leftArrow}");
+ // Verify focus is now on the button
+ cy.get("[ui5-shellbar] [ui5-button]").should("be.focused");
+
+
+ placeAtEndOfInput();
+ // Press right arrow - should move focus away from input since cursor is at end
+ cy.get("@nativeInput").type("{rightArrow}");
+ // Verify focus is now on the ShellBarItem
+ cy.get("[ui5-shellbar]")
+ .shadow()
+ .find(".ui5-shellbar-custom-item")
+ .should("be.focused");
+
+ placeInMiddleOfInput();
+ // Press left arrow - should stay focused on input since cursor is in the middle
+ cy.get("@nativeInput").type("{leftArrow}");
+ cy.get("@nativeInput").should("be.focused");
+ // Press right arrow - should stay focused on input since cursor is in the middle
+ cy.get("@nativeInput").type("{rightArrow}");
+ cy.get("@nativeInput").should("be.focused");
+ });
});
diff --git a/packages/fiori/src/ShellBar.ts b/packages/fiori/src/ShellBar.ts
index e225caa12c60..e846e84160ac 100644
--- a/packages/fiori/src/ShellBar.ts
+++ b/packages/fiori/src/ShellBar.ts
@@ -14,6 +14,7 @@ import {
isRight,
} from "@ui5/webcomponents-base/dist/Keys.js";
import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js";
+import { getTabbableElements } from "@ui5/webcomponents-base/dist/util/TabbableElements.js";
import ListItemStandard from "@ui5/webcomponents/dist/ListItemStandard.js";
import List from "@ui5/webcomponents/dist/List.js";
import type { ListItemClickEventDetail } from "@ui5/webcomponents/dist/List.js";
@@ -647,12 +648,32 @@ class ShellBar extends UI5Element {
}
_onKeyDown(e: KeyboardEvent) {
- const items = this._getVisibleAndInteractiveItems();
+ if (!isLeft(e) && !isRight(e)) {
+ return;
+ }
+
+ const domRef = this.getDomRef();
+ if (!domRef) {
+ // If the component is not rendered yet, we should not handle the keydown event
+ return;
+ }
+
const activeElement = getActiveElement();
+ if (!activeElement) {
+ return;
+ }
+
+ // Check if the active elements should "steal" the navigation
+ if (this._allowChildNavigation(activeElement as HTMLElement, e)) {
+ return;
+ }
+
+ const items = getTabbableElements(domRef).filter(el => this._isVisible(el));
const currentIndex = items.findIndex(el => el === activeElement);
- if (isLeft(e) || isRight(e)) {
- e.preventDefault();// Prevent the default behavior to avoid any further automatic focus movemen
+ // Only handle arrow navigation if the focus is on a ShellBar item
+ if (currentIndex !== -1) {
+ e.preventDefault();
// Focus navigation based on the key pressed
if (isLeft(e)) {
@@ -663,6 +684,28 @@ class ShellBar extends UI5Element {
}
}
+ private _allowChildNavigation(activeElement: HTMLElement, e: KeyboardEvent): boolean {
+ if (activeElement.tagName === "INPUT" || activeElement.tagName === "TEXTAREA") {
+ return this._allowInputNavigation(activeElement as HTMLInputElement | HTMLTextAreaElement, e);
+ }
+
+ return false; // Default to false for other elements
+ }
+
+ private _allowInputNavigation(inputElement: HTMLInputElement | HTMLTextAreaElement, e: KeyboardEvent): boolean {
+ const cursorPosition = inputElement.selectionStart || 0;
+ const textLength = inputElement.value.length;
+
+ // Allow internal navigation if cursor is not at the boundaries
+ if ((isLeft(e) && cursorPosition > 0)
+ || (isRight(e) && cursorPosition < textLength)) {
+ return true;
+ }
+
+ // Let ShellBar handle navigation if at boundaries
+ return false;
+ }
+
_focusNextItem(items: HTMLElement[], currentIndex: number) {
if (currentIndex < items.length - 1) {
(items[currentIndex + 1]).focus(); // Focus the next element
@@ -681,26 +724,6 @@ class ShellBar extends UI5Element {
return style.display !== "none" && style.visibility !== "hidden" && element.offsetWidth > 0 && element.offsetHeight > 0;
}
- _getNavigableContent() {
- const elements = [
- ...this.startButton,
- ...this.logo,
- ...this.shadowRoot!.querySelectorAll(".ui5-shellbar-logo"),
- ...this.shadowRoot!.querySelectorAll(".ui5-shellbar-logo-area"),
- ...this.shadowRoot!.querySelectorAll(".ui5-shellbar-menu-button"),
- ...this.contentItems,
- ...this._getRightChildItems(),
- ] as HTMLElement[];
-
- return elements.map((element: HTMLElement) => {
- const component = element as UI5Element;
- if (component.isUI5Element) {
- return component.getFocusDomRef();
- }
- return element;
- }).filter(el => !!el);
- }
-
_getRightChildItems() {
return [
...this.searchField,
@@ -710,15 +733,6 @@ class ShellBar extends UI5Element {
] as HTMLElement[];
}
- _getVisibleAndInteractiveItems() {
- const items = this._getNavigableContent();
- const visibleAndInteractiveItems = items.filter(item => {
- return this._isVisible(item) && item.tabIndex === 0;
- });
-
- return visibleAndInteractiveItems;
- }
-
_menuItemPress(e: CustomEvent) {
const shouldContinue = this.fireDecoratorEvent("menu-item-click", {
item: e.detail.item,