Skip to content

Commit f869b21

Browse files
committed
feat(menu): add support for passing role to close events
1 parent be7561d commit f869b21

File tree

9 files changed

+79
-60
lines changed

9 files changed

+79
-60
lines changed

core/api.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,15 +1000,15 @@ ion-menu,prop,menuId,string | undefined,undefined,false,true
10001000
ion-menu,prop,side,"end" | "start",'start',false,true
10011001
ion-menu,prop,swipeGesture,boolean,true,false,false
10021002
ion-menu,prop,type,"overlay" | "push" | "reveal" | undefined,undefined,false,false
1003-
ion-menu,method,close,close(animated?: boolean) => Promise<boolean>
1003+
ion-menu,method,close,close(animated?: boolean, role?: string) => Promise<boolean>
10041004
ion-menu,method,isActive,isActive() => Promise<boolean>
10051005
ion-menu,method,isOpen,isOpen() => Promise<boolean>
10061006
ion-menu,method,open,open(animated?: boolean) => Promise<boolean>
1007-
ion-menu,method,setOpen,setOpen(shouldOpen: boolean, animated?: boolean) => Promise<boolean>
1007+
ion-menu,method,setOpen,setOpen(shouldOpen: boolean, animated?: boolean, role?: string) => Promise<boolean>
10081008
ion-menu,method,toggle,toggle(animated?: boolean) => Promise<boolean>
1009-
ion-menu,event,ionDidClose,void,true
1009+
ion-menu,event,ionDidClose,MenuCloseEventDetail,true
10101010
ion-menu,event,ionDidOpen,void,true
1011-
ion-menu,event,ionWillClose,void,true
1011+
ion-menu,event,ionWillClose,MenuCloseEventDetail,true
10121012
ion-menu,event,ionWillOpen,void,true
10131013
ion-menu,css-prop,--background,ios
10141014
ion-menu,css-prop,--background,md

core/src/components.d.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { ScrollBaseDetail, ScrollDetail } from "./components/content/content-int
1818
import { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimeHourCycle, DatetimePresentation, FormatOptions, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface";
1919
import { SpinnerTypes } from "./components/spinner/spinner-configs";
2020
import { InputChangeEventDetail, InputInputEventDetail } from "./components/input/input-interface";
21-
import { MenuChangeEventDetail, MenuType, Side } from "./components/menu/menu-interface";
21+
import { MenuChangeEventDetail, MenuCloseEventDetail, MenuType, Side } from "./components/menu/menu-interface";
2222
import { ModalBreakpointChangeEventDetail, ModalHandleBehavior } from "./components/modal/modal-interface";
2323
import { NavComponent, NavComponentWithProps, NavOptions, RouterOutletOptions, SwipeGestureHandler, TransitionDoneFn, TransitionInstruction } from "./components/nav/nav-interface";
2424
import { ViewController } from "./components/nav/view-controller";
@@ -53,7 +53,7 @@ export { ScrollBaseDetail, ScrollDetail } from "./components/content/content-int
5353
export { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimeHourCycle, DatetimePresentation, FormatOptions, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface";
5454
export { SpinnerTypes } from "./components/spinner/spinner-configs";
5555
export { InputChangeEventDetail, InputInputEventDetail } from "./components/input/input-interface";
56-
export { MenuChangeEventDetail, MenuType, Side } from "./components/menu/menu-interface";
56+
export { MenuChangeEventDetail, MenuCloseEventDetail, MenuType, Side } from "./components/menu/menu-interface";
5757
export { ModalBreakpointChangeEventDetail, ModalHandleBehavior } from "./components/modal/modal-interface";
5858
export { NavComponent, NavComponentWithProps, NavOptions, RouterOutletOptions, SwipeGestureHandler, TransitionDoneFn, TransitionInstruction } from "./components/nav/nav-interface";
5959
export { ViewController } from "./components/nav/view-controller";
@@ -1596,7 +1596,7 @@ export namespace Components {
15961596
/**
15971597
* Closes the menu. If the menu is already closed or it can't be closed, it returns `false`.
15981598
*/
1599-
"close": (animated?: boolean) => Promise<boolean>;
1599+
"close": (animated?: boolean, role?: string) => Promise<boolean>;
16001600
/**
16011601
* 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`.
16021602
*/
@@ -1628,7 +1628,7 @@ export namespace Components {
16281628
/**
16291629
* Opens or closes the button. If the operation can't be completed successfully, it returns `false`.
16301630
*/
1631-
"setOpen": (shouldOpen: boolean, animated?: boolean) => Promise<boolean>;
1631+
"setOpen": (shouldOpen: boolean, animated?: boolean, role?: string) => Promise<boolean>;
16321632
/**
16331633
* Which side of the view the menu should be placed.
16341634
*/
@@ -3969,9 +3969,9 @@ declare global {
39693969
};
39703970
interface HTMLIonMenuElementEventMap {
39713971
"ionWillOpen": void;
3972-
"ionWillClose": void;
3972+
"ionWillClose": MenuCloseEventDetail;
39733973
"ionDidOpen": void;
3974-
"ionDidClose": void;
3974+
"ionDidClose": MenuCloseEventDetail;
39753975
"ionMenuChange": MenuChangeEventDetail;
39763976
}
39773977
interface HTMLIonMenuElement extends Components.IonMenu, HTMLStencilElement {
@@ -6364,7 +6364,7 @@ declare namespace LocalJSX {
63646364
/**
63656365
* Emitted when the menu is closed.
63666366
*/
6367-
"onIonDidClose"?: (event: IonMenuCustomEvent<void>) => void;
6367+
"onIonDidClose"?: (event: IonMenuCustomEvent<MenuCloseEventDetail>) => void;
63686368
/**
63696369
* Emitted when the menu is open.
63706370
*/
@@ -6376,7 +6376,7 @@ declare namespace LocalJSX {
63766376
/**
63776377
* Emitted when the menu is about to be closed.
63786378
*/
6379-
"onIonWillClose"?: (event: IonMenuCustomEvent<void>) => void;
6379+
"onIonWillClose"?: (event: IonMenuCustomEvent<MenuCloseEventDetail>) => void;
63806380
/**
63816381
* Emitted when the menu is about to be opened.
63826382
*/

core/src/components/menu/menu-interface.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export interface MenuI {
2222
close(animated?: boolean): Promise<boolean>;
2323
toggle(animated?: boolean): Promise<boolean>;
2424
setOpen(shouldOpen: boolean, animated?: boolean): Promise<boolean>;
25-
_setOpen(shouldOpen: boolean, animated?: boolean): Promise<boolean>;
25+
_setOpen(shouldOpen: boolean, animated?: boolean, role?: string): Promise<boolean>;
2626
}
2727

2828
export interface MenuControllerI {
@@ -42,14 +42,18 @@ export interface MenuControllerI {
4242
_createAnimation(type: string, menuCmp: MenuI): Promise<Animation>;
4343
_register(menu: MenuI): void;
4444
_unregister(menu: MenuI): void;
45-
_setOpen(menu: MenuI, shouldOpen: boolean, animated: boolean): Promise<boolean>;
45+
_setOpen(menu: MenuI, shouldOpen: boolean, animated: boolean, role?: string): Promise<boolean>;
4646
}
4747

4848
export interface MenuChangeEventDetail {
4949
disabled: boolean;
5050
open: boolean;
5151
}
5252

53+
export interface MenuCloseEventDetail {
54+
role?: string;
55+
}
56+
5357
export interface MenuCustomEvent<T = any> extends CustomEvent {
5458
detail: T;
5559
target: HTMLIonMenuElement;

core/src/components/menu/menu.tsx

Lines changed: 34 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ import { shouldUseCloseWatcher } from '@utils/hardware-back-button';
77
import type { Attributes } from '@utils/helpers';
88
import { inheritAriaAttributes, assert, clamp, isEndSide as isEnd } from '@utils/helpers';
99
import { menuController } from '@utils/menu-controller';
10-
import { getPresentedOverlay } from '@utils/overlays';
10+
import { BACKDROP, ESCAPE, GESTURE, getPresentedOverlay } from '@utils/overlays';
1111
import { hostContext } from '@utils/theme';
1212

1313
import { config } from '../../global/config';
1414
import { getIonMode } from '../../global/ionic-global';
1515
import type { Animation, Gesture, GestureDetail } from '../../interface';
1616

17-
import type { MenuChangeEventDetail, MenuI, MenuType, Side } from './menu-interface';
17+
import type { MenuChangeEventDetail, MenuCloseEventDetail, MenuI, MenuType, Side } from './menu-interface';
1818

1919
const iosEasing = 'cubic-bezier(0.32,0.72,0,1)';
2020
const mdEasing = 'cubic-bezier(0.0,0.0,0.2,1)';
@@ -179,7 +179,7 @@ export class Menu implements ComponentInterface, MenuI {
179179
/**
180180
* Emitted when the menu is about to be closed.
181181
*/
182-
@Event() ionWillClose!: EventEmitter<void>;
182+
@Event() ionWillClose!: EventEmitter<MenuCloseEventDetail>;
183183
/**
184184
* Emitted when the menu is open.
185185
*/
@@ -188,7 +188,7 @@ export class Menu implements ComponentInterface, MenuI {
188188
/**
189189
* Emitted when the menu is closed.
190190
*/
191-
@Event() ionDidClose!: EventEmitter<void>;
191+
@Event() ionDidClose!: EventEmitter<MenuCloseEventDetail>;
192192

193193
/**
194194
* Emitted when the menu state is changed.
@@ -322,23 +322,9 @@ export class Menu implements ComponentInterface, MenuI {
322322
}
323323
}
324324

325-
@Listen('click', { capture: true })
326-
onBackdropClick(ev: any) {
327-
// TODO(FW-2832): type (CustomEvent triggers errors which should be sorted)
328-
if (this._isOpen && this.lastOnEnd < ev.timeStamp - 100) {
329-
const shouldClose = ev.composedPath ? !ev.composedPath().includes(this.menuInnerEl) : false;
330-
331-
if (shouldClose) {
332-
ev.preventDefault();
333-
ev.stopPropagation();
334-
this.close();
335-
}
336-
}
337-
}
338-
339325
onKeydown(ev: KeyboardEvent) {
340326
if (ev.key === 'Escape') {
341-
this.close();
327+
this.close(undefined, ESCAPE);
342328
}
343329
}
344330

@@ -375,8 +361,8 @@ export class Menu implements ComponentInterface, MenuI {
375361
* it returns `false`.
376362
*/
377363
@Method()
378-
close(animated = true): Promise<boolean> {
379-
return this.setOpen(false, animated);
364+
close(animated = true, role?: string): Promise<boolean> {
365+
return this.setOpen(false, animated, role);
380366
}
381367

382368
/**
@@ -393,8 +379,8 @@ export class Menu implements ComponentInterface, MenuI {
393379
* If the operation can't be completed successfully, it returns `false`.
394380
*/
395381
@Method()
396-
setOpen(shouldOpen: boolean, animated = true): Promise<boolean> {
397-
return menuController._setOpen(this, shouldOpen, animated);
382+
setOpen(shouldOpen: boolean, animated = true, role?: string): Promise<boolean> {
383+
return menuController._setOpen(this, shouldOpen, animated, role);
398384
}
399385

400386
private trapKeyboardFocus(ev: Event, doc: Document) {
@@ -438,13 +424,13 @@ export class Menu implements ComponentInterface, MenuI {
438424
}
439425
}
440426

441-
async _setOpen(shouldOpen: boolean, animated = true): Promise<boolean> {
427+
async _setOpen(shouldOpen: boolean, animated = true, role?: string): Promise<boolean> {
442428
// If the menu is disabled or it is currently being animated, let's do nothing
443429
if (!this._isActive() || this.isAnimating || shouldOpen === this._isOpen) {
444430
return false;
445431
}
446432

447-
this.beforeAnimation(shouldOpen);
433+
this.beforeAnimation(shouldOpen, role);
448434

449435
await this.loadAnimation();
450436
await this.startAnimation(shouldOpen, animated);
@@ -459,7 +445,7 @@ export class Menu implements ComponentInterface, MenuI {
459445
return false;
460446
}
461447

462-
this.afterAnimation(shouldOpen);
448+
this.afterAnimation(shouldOpen, role);
463449

464450
return true;
465451
}
@@ -542,7 +528,7 @@ export class Menu implements ComponentInterface, MenuI {
542528
}
543529

544530
private onWillStart(): Promise<void> {
545-
this.beforeAnimation(!this._isOpen);
531+
this.beforeAnimation(!this._isOpen, GESTURE);
546532
return this.loadAnimation();
547533
}
548534

@@ -624,11 +610,11 @@ export class Menu implements ComponentInterface, MenuI {
624610

625611
this.animation
626612
.easing('cubic-bezier(0.4, 0.0, 0.6, 1)')
627-
.onFinish(() => this.afterAnimation(shouldOpen), { oneTimeCallback: true })
613+
.onFinish(() => this.afterAnimation(shouldOpen, GESTURE), { oneTimeCallback: true })
628614
.progressEnd(playTo ? 1 : 0, this._isOpen ? 1 - newStepValue : newStepValue, 300);
629615
}
630616

631-
private beforeAnimation(shouldOpen: boolean) {
617+
private beforeAnimation(shouldOpen: boolean, role?: string) {
632618
assert(!this.isAnimating, '_before() should not be called while animating');
633619

634620
// this places the menu into the correct location before it animates in
@@ -671,11 +657,11 @@ export class Menu implements ComponentInterface, MenuI {
671657
if (shouldOpen) {
672658
this.ionWillOpen.emit();
673659
} else {
674-
this.ionWillClose.emit();
660+
this.ionWillClose.emit({ role });
675661
}
676662
}
677663

678-
private afterAnimation(isOpen: boolean) {
664+
private afterAnimation(isOpen: boolean, role?: string) {
679665
// keep opening/closing the menu disabled for a touch more yet
680666
// only add listeners/css if it's enabled and isOpen
681667
// and only remove listeners/css if it's not open
@@ -731,13 +717,26 @@ export class Menu implements ComponentInterface, MenuI {
731717
}
732718

733719
// emit close event
734-
this.ionDidClose.emit();
720+
this.ionDidClose.emit({ role });
735721

736722
// undo focus trapping so multiple menus don't collide
737723
document.removeEventListener('focus', this.handleFocus, true);
738724
}
739725
}
740726

727+
private onBackdropTap = (ev: any) => {
728+
// TODO(FW-2832): type (CustomEvent triggers errors which should be sorted)
729+
if (this._isOpen && this.lastOnEnd < ev.timeStamp - 100) {
730+
const shouldClose = ev.composedPath ? !ev.composedPath().includes(this.menuInnerEl) : false;
731+
732+
if (shouldClose) {
733+
ev.preventDefault();
734+
ev.stopPropagation();
735+
this.close(undefined, BACKDROP);
736+
}
737+
}
738+
};
739+
741740
private updateState() {
742741
const isActive = this._isActive();
743742
if (this.gesture) {
@@ -767,7 +766,7 @@ export class Menu implements ComponentInterface, MenuI {
767766
* If the menu is disabled then we should
768767
* forcibly close the menu even if it is open.
769768
*/
770-
this.afterAnimation(false);
769+
this.afterAnimation(false, GESTURE);
771770
}
772771
}
773772

@@ -793,18 +792,13 @@ export class Menu implements ComponentInterface, MenuI {
793792
'menu-pane-visible': isPaneVisible,
794793
'split-pane-side': hostContext('ion-split-pane', el),
795794
}}
795+
onIonBackdropTap={this.onBackdropTap}
796796
>
797797
<div class="menu-inner" part="container" ref={(el) => (this.menuInnerEl = el)}>
798798
<slot></slot>
799799
</div>
800800

801-
<ion-backdrop
802-
ref={(el) => (this.backdropEl = el)}
803-
class="menu-backdrop"
804-
tappable={false}
805-
stopPropagation={false}
806-
part="backdrop"
807-
/>
801+
<ion-backdrop ref={(el) => (this.backdropEl = el)} class="menu-backdrop" tappable={true} part="backdrop" />
808802
</Host>
809803
);
810804
}

core/src/components/menu/test/basic/index.html

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@
5252
<ion-content>
5353
<ion-list>
5454
<ion-button id="start-menu-button">Button</ion-button>
55+
<ion-menu-toggle>
56+
<ion-button>Close menu</ion-button>
57+
</ion-menu-toggle>
5558
<ion-item>Menu Item</ion-item>
5659
<ion-item>Menu Item</ion-item>
5760
<ion-item>Menu Item</ion-item>
@@ -125,6 +128,19 @@
125128
</ion-app>
126129

127130
<script>
131+
window.addEventListener('ionWillOpen', function (e) {
132+
console.log('ionWillOpen', e);
133+
});
134+
window.addEventListener('ionDidOpen', function (e) {
135+
console.log('ionDidOpen', e);
136+
});
137+
window.addEventListener('ionWillClose', function (e) {
138+
console.log('ionWillClose', e.detail);
139+
});
140+
window.addEventListener('ionDidClose', function (e) {
141+
console.log('ionDidClose', e.detail);
142+
});
143+
128144
async function openStart() {
129145
// Open the menu by menu-id
130146
await menuController.enable(true, 'start-menu');

core/src/utils/menu-controller/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ const createMenuController = (): MenuControllerI => {
174174
}
175175
};
176176

177-
const _setOpen = async (menu: MenuI, shouldOpen: boolean, animated: boolean): Promise<boolean> => {
177+
const _setOpen = async (menu: MenuI, shouldOpen: boolean, animated: boolean, role: string): Promise<boolean> => {
178178
if (isAnimatingSync()) {
179179
return false;
180180
}
@@ -184,7 +184,7 @@ const createMenuController = (): MenuControllerI => {
184184
await openedMenu.setOpen(false, false);
185185
}
186186
}
187-
return menu._setOpen(shouldOpen, animated);
187+
return menu._setOpen(shouldOpen, animated, role);
188188
};
189189

190190
const _createAnimation = (type: string, menuCmp: MenuI) => {

core/src/utils/overlays.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,7 @@ export const safeCall = (handler: any, arg?: any) => {
778778
};
779779

780780
export const BACKDROP = 'backdrop';
781+
export const ESCAPE = 'escape';
781782
export const GESTURE = 'gesture';
782783
export const OVERLAY_GESTURE_PRIORITY = 39;
783784

packages/angular/src/directives/proxies.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,6 +1334,8 @@ export class IonMenu {
13341334
}
13351335

13361336

1337+
import type { MenuCloseEventDetail as IIonMenuMenuCloseEventDetail } from '@ionic/core';
1338+
13371339
export declare interface IonMenu extends Components.IonMenu {
13381340
/**
13391341
* Emitted when the menu is about to be opened.
@@ -1342,15 +1344,15 @@ export declare interface IonMenu extends Components.IonMenu {
13421344
/**
13431345
* Emitted when the menu is about to be closed.
13441346
*/
1345-
ionWillClose: EventEmitter<CustomEvent<void>>;
1347+
ionWillClose: EventEmitter<CustomEvent<IIonMenuMenuCloseEventDetail>>;
13461348
/**
13471349
* Emitted when the menu is open.
13481350
*/
13491351
ionDidOpen: EventEmitter<CustomEvent<void>>;
13501352
/**
13511353
* Emitted when the menu is closed.
13521354
*/
1353-
ionDidClose: EventEmitter<CustomEvent<void>>;
1355+
ionDidClose: EventEmitter<CustomEvent<IIonMenuMenuCloseEventDetail>>;
13541356
}
13551357

13561358

0 commit comments

Comments
 (0)