mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-23 05:58:26 +08:00
fix(menu): setOpen must be coordinated by the controller
This commit is contained in:
@ -1,5 +1,5 @@
|
|||||||
import { Animation, AnimationBuilder, AnimationController, Menu } from '../../index';
|
|
||||||
import { Component, Method, Prop } from '@stencil/core';
|
import { Component, Method, Prop } from '@stencil/core';
|
||||||
|
import { Animation, AnimationBuilder, Menu } from '../../index';
|
||||||
|
|
||||||
import MenuOverlayAnimation from './animations/overlay';
|
import MenuOverlayAnimation from './animations/overlay';
|
||||||
import MenuPushAnimation from './animations/push';
|
import MenuPushAnimation from './animations/push';
|
||||||
@ -11,9 +11,9 @@ import MenuRevealAnimation from './animations/reveal';
|
|||||||
export class MenuController {
|
export class MenuController {
|
||||||
|
|
||||||
private menus: Menu[] = [];
|
private menus: Menu[] = [];
|
||||||
private menuAnimations: { [name: string]: AnimationBuilder } = {};
|
private menuAnimations = new Map<string, AnimationBuilder>();
|
||||||
|
|
||||||
@Prop({ connect: 'ion-animation-controller' }) animationCtrl: AnimationController;
|
@Prop({ connect: 'ion-animation-controller' }) animationCtrl: HTMLIonAnimationControllerElement;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.registerAnimation('reveal', MenuRevealAnimation);
|
this.registerAnimation('reveal', MenuRevealAnimation);
|
||||||
@ -29,11 +29,7 @@ export class MenuController {
|
|||||||
@Method()
|
@Method()
|
||||||
open(menuId?: string): Promise<boolean> {
|
open(menuId?: string): Promise<boolean> {
|
||||||
const menu = this.get(menuId);
|
const menu = this.get(menuId);
|
||||||
if (menu && !this.isAnimating()) {
|
if (menu) {
|
||||||
const openedMenu = this.getOpen();
|
|
||||||
if (openedMenu && menu !== openedMenu) {
|
|
||||||
openedMenu.setOpen(false, false);
|
|
||||||
}
|
|
||||||
return menu.open();
|
return menu.open();
|
||||||
}
|
}
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
@ -58,7 +54,6 @@ export class MenuController {
|
|||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle the menu. If it's closed, it will open, and if opened, it
|
* Toggle the menu. If it's closed, it will open, and if opened, it
|
||||||
* will close.
|
* will close.
|
||||||
@ -68,11 +63,7 @@ export class MenuController {
|
|||||||
@Method()
|
@Method()
|
||||||
toggle(menuId?: string): Promise<boolean> {
|
toggle(menuId?: string): Promise<boolean> {
|
||||||
const menu = this.get(menuId);
|
const menu = this.get(menuId);
|
||||||
if (menu && !this.isAnimating()) {
|
if (menu) {
|
||||||
const openedMenu = this.getOpen();
|
|
||||||
if (openedMenu && menu !== openedMenu) {
|
|
||||||
openedMenu.setOpen(false, false);
|
|
||||||
}
|
|
||||||
return menu.toggle();
|
return menu.toggle();
|
||||||
}
|
}
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
@ -148,14 +139,12 @@ export class MenuController {
|
|||||||
*/
|
*/
|
||||||
@Method()
|
@Method()
|
||||||
get(menuId?: string): HTMLIonMenuElement {
|
get(menuId?: string): HTMLIonMenuElement {
|
||||||
let menu: Menu;
|
|
||||||
|
|
||||||
if (menuId === 'left' || menuId === 'right') {
|
if (menuId === 'left' || menuId === 'right') {
|
||||||
// there could be more than one menu on the same side
|
// there could be more than one menu on the same side
|
||||||
// so first try to get the enabled one
|
// so first try to get the enabled one
|
||||||
menu = this.menus.find(m => m.side === menuId && !m.disabled);
|
const menu = this.find(m => m.side === menuId && !m.disabled);
|
||||||
if (menu) {
|
if (menu) {
|
||||||
return menu.getElement();
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
// didn't find a menu side that is enabled
|
// didn't find a menu side that is enabled
|
||||||
@ -169,13 +158,13 @@ export class MenuController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// return the first enabled menu
|
// return the first enabled menu
|
||||||
menu = this.menus.find(m => !m.disabled);
|
const menu = this.find(m => !m.disabled);
|
||||||
if (menu) {
|
if (menu) {
|
||||||
return menu.getElement();
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the first menu in the array, if one exists
|
// get the first menu in the array, if one exists
|
||||||
return (this.menus.length > 0 ? this.menus[0].getElement() : null);
|
return (this.menus.length > 0 ? this.menus[0].el : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -191,7 +180,7 @@ export class MenuController {
|
|||||||
*/
|
*/
|
||||||
@Method()
|
@Method()
|
||||||
getMenus(): HTMLIonMenuElement[] {
|
getMenus(): HTMLIonMenuElement[] {
|
||||||
return this.menus.map(menu => menu.getElement());
|
return this.menus.map(menu => menu.el);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -238,23 +227,43 @@ export class MenuController {
|
|||||||
.map(m => m.disabled = true);
|
.map(m => m.disabled = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
@Method()
|
||||||
|
_setOpen(menu: Menu, shouldOpen: boolean, animated: boolean): Promise<boolean> {
|
||||||
|
if (this.isAnimating()) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
if (shouldOpen) {
|
||||||
|
const openedMenu = this.getOpen();
|
||||||
|
if (openedMenu && menu !== openedMenu) {
|
||||||
|
openedMenu.setOpen(false, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return menu._setOpen(shouldOpen, animated);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
@Method()
|
@Method()
|
||||||
createAnimation(type: string, menuCmp: Menu): Promise<Animation> {
|
createAnimation(type: string, menuCmp: Menu): Promise<Animation> {
|
||||||
const animationBuilder = this.menuAnimations[type];
|
const animationBuilder = this.menuAnimations.get(type);
|
||||||
|
if (!animationBuilder) {
|
||||||
|
return Promise.reject('animation not registered');
|
||||||
|
}
|
||||||
return this.animationCtrl.create(animationBuilder, null, menuCmp);
|
return this.animationCtrl.create(animationBuilder, null, menuCmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerAnimation(name: string, cls: AnimationBuilder) {
|
private registerAnimation(name: string, animation: AnimationBuilder) {
|
||||||
this.menuAnimations[name] = cls;
|
this.menuAnimations.set(name, animation);
|
||||||
}
|
}
|
||||||
|
|
||||||
private find(predicate: (menu: Menu) => boolean): HTMLIonMenuElement {
|
private find(predicate: (menu: Menu) => boolean): HTMLIonMenuElement {
|
||||||
const instance = this.menus.find(predicate);
|
const instance = this.menus.find(predicate);
|
||||||
if (instance) {
|
if (instance) {
|
||||||
return instance.getElement();
|
return instance.el;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ export class Menu {
|
|||||||
contentEl: HTMLElement;
|
contentEl: HTMLElement;
|
||||||
menuCtrl: HTMLIonMenuControllerElement;
|
menuCtrl: HTMLIonMenuControllerElement;
|
||||||
|
|
||||||
@Element() private el: HTMLIonMenuElement;
|
@Element() el: HTMLIonMenuElement;
|
||||||
|
|
||||||
@State() isRightSide = false;
|
@State() isRightSide = false;
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ export class Menu {
|
|||||||
/**
|
/**
|
||||||
* The content's id the menu should use.
|
* The content's id the menu should use.
|
||||||
*/
|
*/
|
||||||
@Prop() content: string;
|
@Prop() contentId: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An id for the menu.
|
* An id for the menu.
|
||||||
@ -56,11 +56,12 @@ export class Menu {
|
|||||||
@Prop({ mutable: true }) type = 'overlay';
|
@Prop({ mutable: true }) type = 'overlay';
|
||||||
|
|
||||||
@Watch('type')
|
@Watch('type')
|
||||||
typeChanged(type: string) {
|
typeChanged(type: string, oldType: string | null) {
|
||||||
if (this.contentEl) {
|
const contentEl = this.contentEl;
|
||||||
this.contentEl.classList.remove('menu-content-' + this.type);
|
if (contentEl && oldType) {
|
||||||
this.contentEl.classList.add('menu-content-' + type);
|
contentEl.classList.remove(`menu-content-${oldType}`);
|
||||||
this.contentEl.removeAttribute('style');
|
contentEl.classList.add(`menu-content-${type}`);
|
||||||
|
contentEl.removeAttribute('style');
|
||||||
}
|
}
|
||||||
if (this.menuInnerEl) {
|
if (this.menuInnerEl) {
|
||||||
// Remove effects of previous animations
|
// Remove effects of previous animations
|
||||||
@ -132,8 +133,8 @@ export class Menu {
|
|||||||
assert(!!this.menuCtrl, 'menucontroller was not initialized');
|
assert(!!this.menuCtrl, 'menucontroller was not initialized');
|
||||||
|
|
||||||
const el = this.el;
|
const el = this.el;
|
||||||
const contentQuery = (this.content)
|
const contentQuery = (this.contentId)
|
||||||
? '#' + this.content
|
? '#' + this.contentId
|
||||||
: '[main]';
|
: '[main]';
|
||||||
const parent = el.parentElement;
|
const parent = el.parentElement;
|
||||||
const content = this.contentEl = parent.querySelector(contentQuery) as HTMLElement;
|
const content = this.contentEl = parent.querySelector(contentQuery) as HTMLElement;
|
||||||
@ -141,13 +142,10 @@ export class Menu {
|
|||||||
// requires content element
|
// requires content element
|
||||||
return console.error('Menu: must have a "content" element to listen for drag events on.');
|
return console.error('Menu: must have a "content" element to listen for drag events on.');
|
||||||
}
|
}
|
||||||
this.menuInnerEl = el.querySelector('.menu-inner') as HTMLElement;
|
|
||||||
this.backdropEl = el.querySelector('.menu-backdrop') as HTMLElement;
|
|
||||||
|
|
||||||
// add menu's content classes
|
// add menu's content classes
|
||||||
content.classList.add('menu-content');
|
content.classList.add('menu-content');
|
||||||
|
|
||||||
this.typeChanged(this.type);
|
this.typeChanged(this.type, null);
|
||||||
this.sideChanged();
|
this.sideChanged();
|
||||||
|
|
||||||
let isEnabled = !this.disabled;
|
let isEnabled = !this.disabled;
|
||||||
@ -188,17 +186,32 @@ export class Menu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getElement(): HTMLIonMenuElement {
|
|
||||||
return this.el;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Method()
|
@Method()
|
||||||
isOpen(): boolean {
|
isOpen(): boolean {
|
||||||
return this._isOpen;
|
return this._isOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Method()
|
||||||
|
open(animated = true): Promise<boolean> {
|
||||||
|
return this.setOpen(true, animated);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Method()
|
||||||
|
close(animated = true): Promise<boolean> {
|
||||||
|
return this.setOpen(false, animated);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Method()
|
||||||
|
toggle(animated = true): Promise<boolean> {
|
||||||
|
return this.setOpen(!this._isOpen, animated);
|
||||||
|
}
|
||||||
|
|
||||||
@Method()
|
@Method()
|
||||||
setOpen(shouldOpen: boolean, animated = true): Promise<boolean> {
|
setOpen(shouldOpen: boolean, animated = true): Promise<boolean> {
|
||||||
|
return this.menuCtrl._setOpen(this, shouldOpen, animated);
|
||||||
|
}
|
||||||
|
|
||||||
|
_setOpen(shouldOpen: boolean, animated = true): Promise<boolean> {
|
||||||
// If the menu is disabled or it is currenly being animated, let's do nothing
|
// If the menu is disabled or it is currenly being animated, let's do nothing
|
||||||
if (!this.isActive() || this.isAnimating || (shouldOpen === this._isOpen)) {
|
if (!this.isActive() || this.isAnimating || (shouldOpen === this._isOpen)) {
|
||||||
return Promise.resolve(this._isOpen);
|
return Promise.resolve(this._isOpen);
|
||||||
@ -211,18 +224,8 @@ export class Menu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Method()
|
@Method()
|
||||||
open(): Promise<boolean> {
|
isActive(): boolean {
|
||||||
return this.setOpen(true);
|
return !this.disabled && !this.isPane;
|
||||||
}
|
|
||||||
|
|
||||||
@Method()
|
|
||||||
close(): Promise<boolean> {
|
|
||||||
return this.setOpen(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Method()
|
|
||||||
toggle(): Promise<boolean> {
|
|
||||||
return this.setOpen(!this._isOpen);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadAnimation(): Promise<void> {
|
private loadAnimation(): Promise<void> {
|
||||||
@ -259,10 +262,6 @@ export class Menu {
|
|||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
private isActive(): boolean {
|
|
||||||
return !this.disabled && !this.isPane;
|
|
||||||
}
|
|
||||||
|
|
||||||
private canSwipe(): boolean {
|
private canSwipe(): boolean {
|
||||||
return this.swipeEnabled &&
|
return this.swipeEnabled &&
|
||||||
!this.isAnimating &&
|
!this.isAnimating &&
|
||||||
@ -428,24 +427,23 @@ export class Menu {
|
|||||||
|
|
||||||
hostData() {
|
hostData() {
|
||||||
const isRightSide = this.isRightSide;
|
const isRightSide = this.isRightSide;
|
||||||
const typeClass = 'menu-type-' + this.type;
|
|
||||||
return {
|
return {
|
||||||
role: 'complementary',
|
role: 'complementary',
|
||||||
class: {
|
class: {
|
||||||
|
[`menu-type-${this.type}`]: true,
|
||||||
'menu-enabled': !this.disabled,
|
'menu-enabled': !this.disabled,
|
||||||
'menu-side-right': isRightSide,
|
'menu-side-right': isRightSide,
|
||||||
'menu-side-left': !isRightSide,
|
'menu-side-left': !isRightSide,
|
||||||
[typeClass]: true,
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return ([
|
return ([
|
||||||
<div class='menu-inner page-inner'>
|
<div class='menu-inner page-inner' ref={el => this.menuInnerEl = el}>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>,
|
</div>,
|
||||||
<ion-backdrop class='menu-backdrop'></ion-backdrop> ,
|
<ion-backdrop class='menu-backdrop' ref={el => this.backdropEl = el}></ion-backdrop> ,
|
||||||
<ion-gesture {...{
|
<ion-gesture {...{
|
||||||
'canStart': this.canStart.bind(this),
|
'canStart': this.canStart.bind(this),
|
||||||
'onWillStart': this.onWillStart.bind(this),
|
'onWillStart': this.onWillStart.bind(this),
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
#### content
|
#### contentId
|
||||||
|
|
||||||
string
|
string
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ see the `menuType` in the [config](../../config/Config). Available options:
|
|||||||
|
|
||||||
## Attributes
|
## Attributes
|
||||||
|
|
||||||
#### content
|
#### content-id
|
||||||
|
|
||||||
string
|
string
|
||||||
|
|
||||||
@ -144,6 +144,9 @@ Emitted when the menu is open.
|
|||||||
#### close()
|
#### close()
|
||||||
|
|
||||||
|
|
||||||
|
#### isActive()
|
||||||
|
|
||||||
|
|
||||||
#### isOpen()
|
#### isOpen()
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user