From f6d72f9c3ff141389f5116a95e6393e410644978 Mon Sep 17 00:00:00 2001 From: Elliott Marquez Date: Thu, 27 Apr 2023 10:09:29 -0700 Subject: [PATCH] fix(menu): fix submenu closing when already opened and all menus closing when hovering over menuitem PiperOrigin-RevId: 527611715 --- menu/lib/menu.ts | 14 +++++++++++++- menu/lib/shared.ts | 20 ++++++++++++++++++++ menu/lib/submenuitem/sub-menu-item.ts | 19 ++++++++++++++++++- 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/menu/lib/menu.ts b/menu/lib/menu.ts index e2764bcf1d..0041b27dda 100644 --- a/menu/lib/menu.ts +++ b/menu/lib/menu.ts @@ -287,7 +287,9 @@ export abstract class Menu extends LitElement { @close-menu=${this.onCloseMenu} @deactivate-items=${this.onDeactivateItems} @deactivate-typeahead=${this.handleDeactivateTypeahead} - @activate-typeahead=${this.handleActivateTypeahead}>`; + @activate-typeahead=${this.handleActivateTypeahead} + @stay-open-on-focusout=${this.handleStayOpenOnFocusout} + @close-on-focusout=${this.handleCloseOnFocusout}>`; } /** @@ -681,6 +683,16 @@ export abstract class Menu extends LitElement { this.typeaheadActive = true; } + private handleStayOpenOnFocusout(e:Event) { + e.stopPropagation(); + this.stayOpenOnFocusout = true; + } + + private handleCloseOnFocusout(e:Event) { + e.stopPropagation(); + this.stayOpenOnFocusout = false; + } + override focus() { this.listElement?.focus(); } diff --git a/menu/lib/shared.ts b/menu/lib/shared.ts index 11fb36eb74..ead7fb1b4e 100644 --- a/menu/lib/shared.ts +++ b/menu/lib/shared.ts @@ -82,6 +82,26 @@ export class CloseMenuEvent extends Event { } } +/** + * The event that signals to the menu that it should stay open on the focusout + * event. + */ +export class StayOpenOnFocusoutEvent extends Event { + constructor() { + super('stay-open-on-focusout', {bubbles: true, composed: true}); + } +} + +/** + * The event that signals to the menu that it should close open on the focusout + * event. + */ +export class CloseOnFocusoutEvent extends Event { + constructor() { + super('close-on-focusout', {bubbles: true, composed: true}); + } +} + /** * The default close menu event used by md-menu. To create your own `close-menu` * event, you should subclass the `CloseMenuEvent` instead. diff --git a/menu/lib/submenuitem/sub-menu-item.ts b/menu/lib/submenuitem/sub-menu-item.ts index 8bb197212b..f01d8b09a2 100644 --- a/menu/lib/submenuitem/sub-menu-item.ts +++ b/menu/lib/submenuitem/sub-menu-item.ts @@ -10,7 +10,7 @@ import {property, queryAssignedElements} from 'lit/decorators.js'; import {List} from '../../../list/lib/list.js'; import {Corner, Menu} from '../menu.js'; import {MenuItemEl} from '../menuitem/menu-item.js'; -import {ActivateTypeaheadEvent, CLOSE_REASON, CloseMenuEvent, DeactivateItemsEvent, DeactivateTypeaheadEvent, KEYDOWN_CLOSE_KEYS, NAVIGABLE_KEY, SELECTION_KEY} from '../shared.js'; +import {ActivateTypeaheadEvent, CLOSE_REASON, CloseMenuEvent, CloseOnFocusoutEvent, DeactivateItemsEvent, DeactivateTypeaheadEvent, KEYDOWN_CLOSE_KEYS, NAVIGABLE_KEY, SELECTION_KEY, StayOpenOnFocusoutEvent} from '../shared.js'; function stopPropagation(e: Event) { e.stopPropagation(); @@ -23,6 +23,12 @@ function stopPropagation(e: Event) { * to deactivate the typeahead functionality when a submenu opens * @fires activate-typeahead {DeactivateItemsEvent} Requests the parent menu to * activate the typeahead functionality when a submenu closes + * @fires stay-open-on-focusout {StayOpenOnFocusoutEvent} Requests the parent + * menu to stay open when focusout event is fired or has a `null` + * `relatedTarget` when submenu is opened. + * @fires close-on-focusout {CloseOnFocusoutEvent} Requests the parent + * menu to close when focusout event is fired or has a `null` + * `relatedTarget` When submenu is closed. */ export class SubMenuItem extends MenuItemEl { /** @@ -152,6 +158,8 @@ export class SubMenuItem extends MenuItemEl { private onCloseSubmenu(e: CloseMenuEvent) { e.itemPath.push(this); + // Restore focusout behavior + this.dispatchEvent(new CloseOnFocusoutEvent()); this.dispatchEvent(new ActivateTypeaheadEvent()); // Escape should only close one menu not all of the menus unlike space or // click selection which should close all menus. @@ -206,9 +214,16 @@ export class SubMenuItem extends MenuItemEl { // keyboard after hover. menu.defaultFocus = 'LIST_ROOT'; menu.skipRestoreFocus = true; + menu.stayOpenOnOutsideClick = true; + menu.stayOpenOnFocusout = true; // Menu could already be opened because of mouse interaction const menuAlreadyOpen = menu.open; + // We want the parent to stay open in the case such that a middle submenu + // has a submenuitem hovered which opens a third submenut. Then if you hover + // on yet another middle menu-item (not submenuitem) then focusout Event's + // relatedTarget will be `null` thus, causing all the menus to close + this.dispatchEvent(new StayOpenOnFocusoutEvent()); menu.show(); // Deactivate other items. This can be the case if the user has tabbed @@ -238,6 +253,8 @@ export class SubMenuItem extends MenuItemEl { this.dispatchEvent(new ActivateTypeaheadEvent()); menu.quick = true; menu.close(); + // Restore focusout behavior. + this.dispatchEvent(new CloseOnFocusoutEvent()); this.active = false; this.selected = false; menu.addEventListener('closed', onClosed, {once: true});