diff --git a/packages/main/cypress/specs/Menu.cy.tsx b/packages/main/cypress/specs/Menu.cy.tsx index a5f81c24b38f..d0e0ace3e496 100644 --- a/packages/main/cypress/specs/Menu.cy.tsx +++ b/packages/main/cypress/specs/Menu.cy.tsx @@ -1055,8 +1055,6 @@ describe("Menu interaction", () => { .should("have.attr", "accessible-name", "Select an option from the menu"); }); - /* The test is valid, but currently it is not stable. It will be reviewed further and stabilized afterwards. */ - it("Menu items - navigation in endContent", () => { cy.mount( <> @@ -1071,6 +1069,10 @@ describe("Menu interaction", () => { > ); + // Move mouse to opener button to avoid interference with menu item hover behavior + cy.get("[ui5-button]") + .realHover(); + cy.get("[ui5-menu]") .ui5MenuOpen(); @@ -1079,24 +1081,34 @@ describe("Menu interaction", () => { cy.get("@items") .first() - .should("be.focused"); + .should("be.focused") + .realPress("ArrowRight"); - cy.realPress("ArrowRight"); - cy.get("@buttons").first().should("be.focused"); + cy.get("@buttons") + .first() + .should("be.focused") + .realPress("ArrowRight"); - cy.realPress("ArrowRight"); - cy.get("@buttons").last().should("be.focused"); + cy.get("@buttons") + .last() + .should("be.focused") + .realPress("ArrowRight"); - cy.realPress("ArrowRight"); - cy.get("@buttons").last().should("be.focused"); + cy.get("@buttons") + .last() + .should("be.focused") + .realPress("ArrowLeft"); - cy.realPress("ArrowLeft"); - cy.get("@buttons").first().should("be.focused"); + cy.get("@buttons") + .first() + .should("be.focused") + .realPress("ArrowLeft"); - cy.realPress("ArrowLeft"); - cy.get("@buttons").first().should("be.focused"); + cy.get("@buttons") + .first() + .should("be.focused") + .realPress("ArrowDown"); - cy.realPress("ArrowDown"); cy.get("@items").last().should("be.focused"); }); }); @@ -1162,4 +1174,155 @@ describe("Menu - getFocusDomRef", () => { expect(menu.getFocusDomRef()).equal(clickedItem.getFocusDomRef()); }); }); +}); + +describe("Menu - Submenu Focus Behavior", () => { + it("should not move focus when submenu opens via mouse hover", () => { + cy.mount( + <> + +
+ > + ); + + cy.get("[ui5-menu]") + .ui5MenuOpen(); + + cy.get("[ui5-menu] > [ui5-menu-item]") + .as("items"); + + cy.get("@items") + .first() + .should("be.visible") + .as("parentItem"); + + // Hover item to open submenu + cy.get("@parentItem").realHover(); + + cy.get("@parentItem") + .shadow() + .find("[ui5-responsive-popover]") + .should("have.attr", "open"); + + // Verify focus not moved to submenu + cy.get("@parentItem") + .should("be.focused"); + + cy.get("[ui5-menu-item] > [ui5-menu-item]") + .as("submenuitems"); + + cy.get("@submenuitems") + .first() + .should("be.visible") + .as("childItem"); + + cy.get("@childItem") + .should("not.be.focused"); + }); + + it("should close submenu when hover moves to another item", () => { + cy.mount( + <> + + + > + ); + + cy.get("[ui5-menu]") + .ui5MenuOpen(); + + cy.get("[ui5-menu] > [ui5-menu-item]") + .as("items"); + + cy.get("@items") + .first() + .should("be.visible") + .as("parentItem"); + + // Hover item to open submenu + cy.get("@parentItem").realHover(); + + cy.get("@parentItem") + .shadow() + .find("[ui5-responsive-popover]") + .as("submenuPopover"); + + cy.get("@submenuPopover") + .should("have.attr", "open"); + + // Hover over another top-level item + cy.get("@items") + .last() + .should("be.visible") + .as("lastItem"); + + cy.get("@lastItem") + .realHover(); + + // The original submenu should be closed + cy.get("@submenuPopover") + .should("not.have.attr", "open"); + }); + + it("should move focus when submenu opens via keyboard", () => { + cy.mount( + <> + + + > + ); + + cy.get("[ui5-menu]") + .ui5MenuOpen(); + + cy.get("[ui5-menu] > [ui5-menu-item]") + .as("items"); + + cy.get("@items") + .first() + .should("be.visible") + .as("parentItem"); + + // Open submenu with keyboard + cy.get("@parentItem") + .should("be.focused") + .realPress("ArrowRight"); + cy.get("@parentItem") + .shadow() + .find("[ui5-responsive-popover]") + .should("have.attr", "open"); + + // Verify focus is moved to submenu + cy.get("@parentItem") + .should("not.be.focused"); + + cy.get("[ui5-menu-item] > [ui5-menu-item]") + .as("submenuitems"); + + cy.get("@submenuitems") + .first() + .should("be.visible") + .as("childItem"); + + cy.get("@childItem") + .should("be.focused"); + }); }); \ No newline at end of file diff --git a/packages/main/src/Menu.ts b/packages/main/src/Menu.ts index 1f3e1c1f6521..a11c114dcd8e 100644 --- a/packages/main/src/Menu.ts +++ b/packages/main/src/Menu.ts @@ -351,7 +351,7 @@ class Menu extends UI5Element { this.open = false; } - _openItemSubMenu(item: MenuItem) { + _openItemSubMenu(item: MenuItem, openedByMouse = false) { clearTimeout(this._timeout); if (!item._popover || item._popover.open) { @@ -365,6 +365,7 @@ class Menu extends UI5Element { item._popover.opener = item; item._popover.open = true; item.selected = true; + item._openedByMouse = openedByMouse; } _itemMouseOver(e: MouseEvent) { @@ -377,7 +378,7 @@ class Menu extends UI5Element { return; } - item.focus(); + item.getFocusDomRef()?.focus(); // Opens submenu with 300ms delay this._startOpenTimeout(item); @@ -413,7 +414,7 @@ class Menu extends UI5Element { this._timeout = setTimeout(() => { this._closeOtherSubMenus(item); - this._openItemSubMenu(item); + this._openItemSubMenu(item, true); }, MENU_OPEN_DELAY); } @@ -455,7 +456,7 @@ class Menu extends UI5Element { } if (shouldOpenMenu) { - this._openItemSubMenu(item); + this._openItemSubMenu(item, false); } else if (isTabNextPrevious) { this._close(); } diff --git a/packages/main/src/MenuItem.ts b/packages/main/src/MenuItem.ts index c80800c7f11b..4a11b959c3ff 100644 --- a/packages/main/src/MenuItem.ts +++ b/packages/main/src/MenuItem.ts @@ -319,6 +319,7 @@ class MenuItem extends ListItem implements IMenuItem { _itemNavigation: ItemNavigation; _shiftPressed: boolean = false; + _openedByMouse = false; constructor() { super(); @@ -535,7 +536,8 @@ class MenuItem extends ListItem implements IMenuItem { if (!isInstanceOfMenuItem(item)) { return; } - item.focus(); + + item.getFocusDomRef()?.focus(); this._closeOtherSubMenus(item); } @@ -622,7 +624,9 @@ class MenuItem extends ListItem implements IMenuItem { } _afterPopoverOpen() { - this._allMenuItems[0]?.focus(); + if (!this._openedByMouse) { + this._allMenuItems[0]?.focus(); + } this.fireDecoratorEvent("open"); } @@ -644,6 +648,7 @@ class MenuItem extends ListItem implements IMenuItem { } _afterPopoverClose() { + this._openedByMouse = false; this.fireDecoratorEvent("close"); }