diff --git a/core/api.txt b/core/api.txt index bd0490e45f..de7c353197 100644 --- a/core/api.txt +++ b/core/api.txt @@ -1000,15 +1000,15 @@ ion-menu,prop,menuId,string | undefined,undefined,false,true ion-menu,prop,side,"end" | "start",'start',false,true ion-menu,prop,swipeGesture,boolean,true,false,false ion-menu,prop,type,"overlay" | "push" | "reveal" | undefined,undefined,false,false -ion-menu,method,close,close(animated?: boolean) => Promise +ion-menu,method,close,close(animated?: boolean, role?: string) => Promise ion-menu,method,isActive,isActive() => Promise ion-menu,method,isOpen,isOpen() => Promise ion-menu,method,open,open(animated?: boolean) => Promise -ion-menu,method,setOpen,setOpen(shouldOpen: boolean, animated?: boolean) => Promise +ion-menu,method,setOpen,setOpen(shouldOpen: boolean, animated?: boolean, role?: string) => Promise ion-menu,method,toggle,toggle(animated?: boolean) => Promise -ion-menu,event,ionDidClose,void,true +ion-menu,event,ionDidClose,MenuCloseEventDetail,true ion-menu,event,ionDidOpen,void,true -ion-menu,event,ionWillClose,void,true +ion-menu,event,ionWillClose,MenuCloseEventDetail,true ion-menu,event,ionWillOpen,void,true ion-menu,css-prop,--background,ios ion-menu,css-prop,--background,md diff --git a/core/src/components.d.ts b/core/src/components.d.ts index db9530b340..f0cd3d081a 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -18,7 +18,7 @@ import { ScrollBaseDetail, ScrollDetail } from "./components/content/content-int import { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimeHourCycle, DatetimePresentation, FormatOptions, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface"; import { SpinnerTypes } from "./components/spinner/spinner-configs"; import { InputChangeEventDetail, InputInputEventDetail } from "./components/input/input-interface"; -import { MenuChangeEventDetail, MenuType, Side } from "./components/menu/menu-interface"; +import { MenuChangeEventDetail, MenuCloseEventDetail, MenuType, Side } from "./components/menu/menu-interface"; import { ModalBreakpointChangeEventDetail, ModalHandleBehavior } from "./components/modal/modal-interface"; import { NavComponent, NavComponentWithProps, NavOptions, RouterOutletOptions, SwipeGestureHandler, TransitionDoneFn, TransitionInstruction } from "./components/nav/nav-interface"; import { ViewController } from "./components/nav/view-controller"; @@ -53,7 +53,7 @@ export { ScrollBaseDetail, ScrollDetail } from "./components/content/content-int export { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimeHourCycle, DatetimePresentation, FormatOptions, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface"; export { SpinnerTypes } from "./components/spinner/spinner-configs"; export { InputChangeEventDetail, InputInputEventDetail } from "./components/input/input-interface"; -export { MenuChangeEventDetail, MenuType, Side } from "./components/menu/menu-interface"; +export { MenuChangeEventDetail, MenuCloseEventDetail, MenuType, Side } from "./components/menu/menu-interface"; export { ModalBreakpointChangeEventDetail, ModalHandleBehavior } from "./components/modal/modal-interface"; export { NavComponent, NavComponentWithProps, NavOptions, RouterOutletOptions, SwipeGestureHandler, TransitionDoneFn, TransitionInstruction } from "./components/nav/nav-interface"; export { ViewController } from "./components/nav/view-controller"; @@ -1596,7 +1596,7 @@ export namespace Components { /** * Closes the menu. If the menu is already closed or it can't be closed, it returns `false`. */ - "close": (animated?: boolean) => Promise; + "close": (animated?: boolean, role?: string) => Promise; /** * The `id` of the main content. When using a router this is typically `ion-router-outlet`. When not using a router, this is typically your main view's `ion-content`. This is not the id of the `ion-content` inside of your `ion-menu`. */ @@ -1628,7 +1628,7 @@ export namespace Components { /** * Opens or closes the button. If the operation can't be completed successfully, it returns `false`. */ - "setOpen": (shouldOpen: boolean, animated?: boolean) => Promise; + "setOpen": (shouldOpen: boolean, animated?: boolean, role?: string) => Promise; /** * Which side of the view the menu should be placed. */ @@ -3969,9 +3969,9 @@ declare global { }; interface HTMLIonMenuElementEventMap { "ionWillOpen": void; - "ionWillClose": void; + "ionWillClose": MenuCloseEventDetail; "ionDidOpen": void; - "ionDidClose": void; + "ionDidClose": MenuCloseEventDetail; "ionMenuChange": MenuChangeEventDetail; } interface HTMLIonMenuElement extends Components.IonMenu, HTMLStencilElement { @@ -6364,7 +6364,7 @@ declare namespace LocalJSX { /** * Emitted when the menu is closed. */ - "onIonDidClose"?: (event: IonMenuCustomEvent) => void; + "onIonDidClose"?: (event: IonMenuCustomEvent) => void; /** * Emitted when the menu is open. */ @@ -6376,7 +6376,7 @@ declare namespace LocalJSX { /** * Emitted when the menu is about to be closed. */ - "onIonWillClose"?: (event: IonMenuCustomEvent) => void; + "onIonWillClose"?: (event: IonMenuCustomEvent) => void; /** * Emitted when the menu is about to be opened. */ diff --git a/core/src/components/menu/menu-interface.ts b/core/src/components/menu/menu-interface.ts index f7aba9d830..44ef985f0c 100644 --- a/core/src/components/menu/menu-interface.ts +++ b/core/src/components/menu/menu-interface.ts @@ -22,7 +22,7 @@ export interface MenuI { close(animated?: boolean): Promise; toggle(animated?: boolean): Promise; setOpen(shouldOpen: boolean, animated?: boolean): Promise; - _setOpen(shouldOpen: boolean, animated?: boolean): Promise; + _setOpen(shouldOpen: boolean, animated?: boolean, role?: string): Promise; } export interface MenuControllerI { @@ -42,7 +42,7 @@ export interface MenuControllerI { _createAnimation(type: string, menuCmp: MenuI): Promise; _register(menu: MenuI): void; _unregister(menu: MenuI): void; - _setOpen(menu: MenuI, shouldOpen: boolean, animated: boolean): Promise; + _setOpen(menu: MenuI, shouldOpen: boolean, animated: boolean, role?: string): Promise; } export interface MenuChangeEventDetail { @@ -50,6 +50,10 @@ export interface MenuChangeEventDetail { open: boolean; } +export interface MenuCloseEventDetail { + role?: string; +} + export interface MenuCustomEvent extends CustomEvent { detail: T; target: HTMLIonMenuElement; diff --git a/core/src/components/menu/menu.tsx b/core/src/components/menu/menu.tsx index 622d1d3e89..d83e53abb3 100644 --- a/core/src/components/menu/menu.tsx +++ b/core/src/components/menu/menu.tsx @@ -7,14 +7,14 @@ import { shouldUseCloseWatcher } from '@utils/hardware-back-button'; import type { Attributes } from '@utils/helpers'; import { inheritAriaAttributes, assert, clamp, isEndSide as isEnd } from '@utils/helpers'; import { menuController } from '@utils/menu-controller'; -import { getPresentedOverlay } from '@utils/overlays'; +import { BACKDROP, GESTURE, getPresentedOverlay } from '@utils/overlays'; import { hostContext } from '@utils/theme'; import { config } from '../../global/config'; import { getIonMode } from '../../global/ionic-global'; import type { Animation, Gesture, GestureDetail } from '../../interface'; -import type { MenuChangeEventDetail, MenuI, MenuType, Side } from './menu-interface'; +import type { MenuChangeEventDetail, MenuCloseEventDetail, MenuI, MenuType, Side } from './menu-interface'; const iosEasing = 'cubic-bezier(0.32,0.72,0,1)'; const mdEasing = 'cubic-bezier(0.0,0.0,0.2,1)'; @@ -179,7 +179,7 @@ export class Menu implements ComponentInterface, MenuI { /** * Emitted when the menu is about to be closed. */ - @Event() ionWillClose!: EventEmitter; + @Event() ionWillClose!: EventEmitter; /** * Emitted when the menu is open. */ @@ -188,7 +188,7 @@ export class Menu implements ComponentInterface, MenuI { /** * Emitted when the menu is closed. */ - @Event() ionDidClose!: EventEmitter; + @Event() ionDidClose!: EventEmitter; /** * Emitted when the menu state is changed. @@ -331,14 +331,14 @@ export class Menu implements ComponentInterface, MenuI { if (shouldClose) { ev.preventDefault(); ev.stopPropagation(); - this.close(); + this.close(undefined, BACKDROP); } } } onKeydown(ev: KeyboardEvent) { if (ev.key === 'Escape') { - this.close(); + this.close(undefined, BACKDROP); } } @@ -375,8 +375,8 @@ export class Menu implements ComponentInterface, MenuI { * it returns `false`. */ @Method() - close(animated = true): Promise { - return this.setOpen(false, animated); + close(animated = true, role?: string): Promise { + return this.setOpen(false, animated, role); } /** @@ -393,8 +393,8 @@ export class Menu implements ComponentInterface, MenuI { * If the operation can't be completed successfully, it returns `false`. */ @Method() - setOpen(shouldOpen: boolean, animated = true): Promise { - return menuController._setOpen(this, shouldOpen, animated); + setOpen(shouldOpen: boolean, animated = true, role?: string): Promise { + return menuController._setOpen(this, shouldOpen, animated, role); } private trapKeyboardFocus(ev: Event, doc: Document) { @@ -438,13 +438,13 @@ export class Menu implements ComponentInterface, MenuI { } } - async _setOpen(shouldOpen: boolean, animated = true): Promise { + async _setOpen(shouldOpen: boolean, animated = true, role?: string): Promise { // If the menu is disabled or it is currently being animated, let's do nothing if (!this._isActive() || this.isAnimating || shouldOpen === this._isOpen) { return false; } - this.beforeAnimation(shouldOpen); + this.beforeAnimation(shouldOpen, role); await this.loadAnimation(); await this.startAnimation(shouldOpen, animated); @@ -459,7 +459,7 @@ export class Menu implements ComponentInterface, MenuI { return false; } - this.afterAnimation(shouldOpen); + this.afterAnimation(shouldOpen, role); return true; } @@ -542,7 +542,7 @@ export class Menu implements ComponentInterface, MenuI { } private onWillStart(): Promise { - this.beforeAnimation(!this._isOpen); + this.beforeAnimation(!this._isOpen, GESTURE); return this.loadAnimation(); } @@ -624,11 +624,11 @@ export class Menu implements ComponentInterface, MenuI { this.animation .easing('cubic-bezier(0.4, 0.0, 0.6, 1)') - .onFinish(() => this.afterAnimation(shouldOpen), { oneTimeCallback: true }) + .onFinish(() => this.afterAnimation(shouldOpen, GESTURE), { oneTimeCallback: true }) .progressEnd(playTo ? 1 : 0, this._isOpen ? 1 - newStepValue : newStepValue, 300); } - private beforeAnimation(shouldOpen: boolean) { + private beforeAnimation(shouldOpen: boolean, role?: string) { assert(!this.isAnimating, '_before() should not be called while animating'); // this places the menu into the correct location before it animates in @@ -671,11 +671,11 @@ export class Menu implements ComponentInterface, MenuI { if (shouldOpen) { this.ionWillOpen.emit(); } else { - this.ionWillClose.emit(); + this.ionWillClose.emit({ role }); } } - private afterAnimation(isOpen: boolean) { + private afterAnimation(isOpen: boolean, role?: string) { // keep opening/closing the menu disabled for a touch more yet // only add listeners/css if it's enabled and isOpen // and only remove listeners/css if it's not open @@ -731,7 +731,7 @@ export class Menu implements ComponentInterface, MenuI { } // emit close event - this.ionDidClose.emit(); + this.ionDidClose.emit({ role }); // undo focus trapping so multiple menus don't collide document.removeEventListener('focus', this.handleFocus, true); @@ -767,7 +767,7 @@ export class Menu implements ComponentInterface, MenuI { * If the menu is disabled then we should * forcibly close the menu even if it is open. */ - this.afterAnimation(false); + this.afterAnimation(false, GESTURE); } } diff --git a/core/src/components/menu/test/basic/index.html b/core/src/components/menu/test/basic/index.html index 34a90240ab..4e79a4fbb2 100644 --- a/core/src/components/menu/test/basic/index.html +++ b/core/src/components/menu/test/basic/index.html @@ -51,7 +51,9 @@ - Button + + Button + Menu Item Menu Item Menu Item @@ -125,6 +127,19 @@