Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean>
ion-menu,method,close,close(animated?: boolean, role?: string) => Promise<boolean>
ion-menu,method,isActive,isActive() => Promise<boolean>
ion-menu,method,isOpen,isOpen() => Promise<boolean>
ion-menu,method,open,open(animated?: boolean) => Promise<boolean>
ion-menu,method,setOpen,setOpen(shouldOpen: boolean, animated?: boolean) => Promise<boolean>
ion-menu,method,setOpen,setOpen(shouldOpen: boolean, animated?: boolean, role?: string) => Promise<boolean>
ion-menu,method,toggle,toggle(animated?: boolean) => Promise<boolean>
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
Expand Down
16 changes: 8 additions & 8 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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<boolean>;
"close": (animated?: boolean, role?: string) => Promise<boolean>;
/**
* 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`.
*/
Expand Down Expand Up @@ -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<boolean>;
"setOpen": (shouldOpen: boolean, animated?: boolean, role?: string) => Promise<boolean>;
/**
* Which side of the view the menu should be placed.
*/
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -6364,7 +6364,7 @@ declare namespace LocalJSX {
/**
* Emitted when the menu is closed.
*/
"onIonDidClose"?: (event: IonMenuCustomEvent<void>) => void;
"onIonDidClose"?: (event: IonMenuCustomEvent<MenuCloseEventDetail>) => void;
/**
* Emitted when the menu is open.
*/
Expand All @@ -6376,7 +6376,7 @@ declare namespace LocalJSX {
/**
* Emitted when the menu is about to be closed.
*/
"onIonWillClose"?: (event: IonMenuCustomEvent<void>) => void;
"onIonWillClose"?: (event: IonMenuCustomEvent<MenuCloseEventDetail>) => void;
/**
* Emitted when the menu is about to be opened.
*/
Expand Down
8 changes: 6 additions & 2 deletions core/src/components/menu/menu-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface MenuI {
close(animated?: boolean): Promise<boolean>;
toggle(animated?: boolean): Promise<boolean>;
setOpen(shouldOpen: boolean, animated?: boolean): Promise<boolean>;
_setOpen(shouldOpen: boolean, animated?: boolean): Promise<boolean>;
_setOpen(shouldOpen: boolean, animated?: boolean, role?: string): Promise<boolean>;
}

export interface MenuControllerI {
Expand All @@ -42,14 +42,18 @@ export interface MenuControllerI {
_createAnimation(type: string, menuCmp: MenuI): Promise<Animation>;
_register(menu: MenuI): void;
_unregister(menu: MenuI): void;
_setOpen(menu: MenuI, shouldOpen: boolean, animated: boolean): Promise<boolean>;
_setOpen(menu: MenuI, shouldOpen: boolean, animated: boolean, role?: string): Promise<boolean>;
}

export interface MenuChangeEventDetail {
disabled: boolean;
open: boolean;
}

export interface MenuCloseEventDetail {
role?: string;
}

export interface MenuCustomEvent<T = any> extends CustomEvent {
detail: T;
target: HTMLIonMenuElement;
Expand Down
74 changes: 34 additions & 40 deletions core/src/components/menu/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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, ESCAPE, 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)';
Expand Down Expand Up @@ -179,7 +179,7 @@ export class Menu implements ComponentInterface, MenuI {
/**
* Emitted when the menu is about to be closed.
*/
@Event() ionWillClose!: EventEmitter<void>;
@Event() ionWillClose!: EventEmitter<MenuCloseEventDetail>;
/**
* Emitted when the menu is open.
*/
Expand All @@ -188,7 +188,7 @@ export class Menu implements ComponentInterface, MenuI {
/**
* Emitted when the menu is closed.
*/
@Event() ionDidClose!: EventEmitter<void>;
@Event() ionDidClose!: EventEmitter<MenuCloseEventDetail>;

/**
* Emitted when the menu state is changed.
Expand Down Expand Up @@ -322,23 +322,9 @@ export class Menu implements ComponentInterface, MenuI {
}
}

@Listen('click', { capture: true })
onBackdropClick(ev: any) {
// TODO(FW-2832): type (CustomEvent triggers errors which should be sorted)
if (this._isOpen && this.lastOnEnd < ev.timeStamp - 100) {
const shouldClose = ev.composedPath ? !ev.composedPath().includes(this.menuInnerEl) : false;

if (shouldClose) {
ev.preventDefault();
ev.stopPropagation();
this.close();
}
}
}

onKeydown(ev: KeyboardEvent) {
if (ev.key === 'Escape') {
this.close();
this.close(undefined, ESCAPE);
}
}

Expand Down Expand Up @@ -375,8 +361,8 @@ export class Menu implements ComponentInterface, MenuI {
* it returns `false`.
*/
@Method()
close(animated = true): Promise<boolean> {
return this.setOpen(false, animated);
close(animated = true, role?: string): Promise<boolean> {
return this.setOpen(false, animated, role);
}

/**
Expand All @@ -393,8 +379,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<boolean> {
return menuController._setOpen(this, shouldOpen, animated);
setOpen(shouldOpen: boolean, animated = true, role?: string): Promise<boolean> {
return menuController._setOpen(this, shouldOpen, animated, role);
}

private trapKeyboardFocus(ev: Event, doc: Document) {
Expand Down Expand Up @@ -438,13 +424,13 @@ export class Menu implements ComponentInterface, MenuI {
}
}

async _setOpen(shouldOpen: boolean, animated = true): Promise<boolean> {
async _setOpen(shouldOpen: boolean, animated = true, role?: string): Promise<boolean> {
// 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);
Expand All @@ -459,7 +445,7 @@ export class Menu implements ComponentInterface, MenuI {
return false;
}

this.afterAnimation(shouldOpen);
this.afterAnimation(shouldOpen, role);

return true;
}
Expand Down Expand Up @@ -542,7 +528,7 @@ export class Menu implements ComponentInterface, MenuI {
}

private onWillStart(): Promise<void> {
this.beforeAnimation(!this._isOpen);
this.beforeAnimation(!this._isOpen, GESTURE);
return this.loadAnimation();
}

Expand Down Expand Up @@ -624,11 +610,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
Expand Down Expand Up @@ -671,11 +657,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
Expand Down Expand Up @@ -731,13 +717,26 @@ 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);
}
}

private onBackdropTap = (ev: any) => {
// TODO(FW-2832): type (CustomEvent triggers errors which should be sorted)
if (this._isOpen && this.lastOnEnd < ev.timeStamp - 100) {
const shouldClose = ev.composedPath ? !ev.composedPath().includes(this.menuInnerEl) : false;

if (shouldClose) {
ev.preventDefault();
ev.stopPropagation();
this.close(undefined, BACKDROP);
}
}
};

private updateState() {
const isActive = this._isActive();
if (this.gesture) {
Expand Down Expand Up @@ -767,7 +766,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);
}
}

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

<ion-backdrop
ref={(el) => (this.backdropEl = el)}
class="menu-backdrop"
tappable={false}
stopPropagation={false}
part="backdrop"
/>
<ion-backdrop ref={(el) => (this.backdropEl = el)} class="menu-backdrop" tappable={true} part="backdrop" />
</Host>
);
}
Expand Down
16 changes: 16 additions & 0 deletions core/src/components/menu/test/basic/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
<ion-content>
<ion-list>
<ion-button id="start-menu-button">Button</ion-button>
<ion-menu-toggle>
<ion-button>Close menu</ion-button>
</ion-menu-toggle>
<ion-item>Menu Item</ion-item>
<ion-item>Menu Item</ion-item>
<ion-item>Menu Item</ion-item>
Expand Down Expand Up @@ -125,6 +128,19 @@
</ion-app>

<script>
window.addEventListener('ionWillOpen', function (e) {
console.log('ionWillOpen', e);
});
window.addEventListener('ionDidOpen', function (e) {
console.log('ionDidOpen', e);
});
window.addEventListener('ionWillClose', function (e) {
console.log('ionWillClose', e.detail);
});
window.addEventListener('ionDidClose', function (e) {
console.log('ionDidClose', e.detail);
});

async function openStart() {
// Open the menu by menu-id
await menuController.enable(true, 'start-menu');
Expand Down
4 changes: 2 additions & 2 deletions core/src/utils/menu-controller/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ const createMenuController = (): MenuControllerI => {
}
};

const _setOpen = async (menu: MenuI, shouldOpen: boolean, animated: boolean): Promise<boolean> => {
const _setOpen = async (menu: MenuI, shouldOpen: boolean, animated: boolean, role: string): Promise<boolean> => {
if (isAnimatingSync()) {
return false;
}
Expand All @@ -184,7 +184,7 @@ const createMenuController = (): MenuControllerI => {
await openedMenu.setOpen(false, false);
}
}
return menu._setOpen(shouldOpen, animated);
return menu._setOpen(shouldOpen, animated, role);
};

const _createAnimation = (type: string, menuCmp: MenuI) => {
Expand Down
1 change: 1 addition & 0 deletions core/src/utils/overlays.ts
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,7 @@ export const safeCall = (handler: any, arg?: any) => {
};

export const BACKDROP = 'backdrop';
export const ESCAPE = 'escape';
export const GESTURE = 'gesture';
export const OVERLAY_GESTURE_PRIORITY = 39;

Expand Down
6 changes: 4 additions & 2 deletions packages/angular/src/directives/proxies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1334,6 +1334,8 @@ export class IonMenu {
}


import type { MenuCloseEventDetail as IIonMenuMenuCloseEventDetail } from '@ionic/core';

export declare interface IonMenu extends Components.IonMenu {
/**
* Emitted when the menu is about to be opened.
Expand All @@ -1342,15 +1344,15 @@ export declare interface IonMenu extends Components.IonMenu {
/**
* Emitted when the menu is about to be closed.
*/
ionWillClose: EventEmitter<CustomEvent<void>>;
ionWillClose: EventEmitter<CustomEvent<IIonMenuMenuCloseEventDetail>>;
/**
* Emitted when the menu is open.
*/
ionDidOpen: EventEmitter<CustomEvent<void>>;
/**
* Emitted when the menu is closed.
*/
ionDidClose: EventEmitter<CustomEvent<void>>;
ionDidClose: EventEmitter<CustomEvent<IIonMenuMenuCloseEventDetail>>;
}


Expand Down
Loading
Loading