mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-24 14:58:36 +08:00
fix(ion-menu): finish ion-menu and ion-split-pane
This commit is contained in:
7
packages/core/src/components.d.ts
vendored
7
packages/core/src/components.d.ts
vendored
@ -1541,7 +1541,7 @@ declare global {
|
|||||||
_register?: any,
|
_register?: any,
|
||||||
_unregister?: any,
|
_unregister?: any,
|
||||||
_setActiveMenu?: any,
|
_setActiveMenu?: any,
|
||||||
create?: any,
|
createAnimation?: any,
|
||||||
animationCtrl?: any
|
animationCtrl?: any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1572,6 +1572,11 @@ declare global {
|
|||||||
mode?: string,
|
mode?: string,
|
||||||
color?: string,
|
color?: string,
|
||||||
|
|
||||||
|
isOpen?: any,
|
||||||
|
setOpen?: any,
|
||||||
|
open?: any,
|
||||||
|
close?: any,
|
||||||
|
toggle?: any,
|
||||||
lazyMenuCtrl?: any,
|
lazyMenuCtrl?: any,
|
||||||
content?: string,
|
content?: string,
|
||||||
menuId?: string,
|
menuId?: string,
|
||||||
|
@ -8,7 +8,6 @@ import { PanRecognizer } from './recognizers';
|
|||||||
})
|
})
|
||||||
export class Gesture {
|
export class Gesture {
|
||||||
|
|
||||||
@Element() private el: HTMLElement;
|
|
||||||
private detail: GestureDetail = {};
|
private detail: GestureDetail = {};
|
||||||
private positions: number[] = [];
|
private positions: number[] = [];
|
||||||
private ctrl: GestureController;
|
private ctrl: GestureController;
|
||||||
@ -21,13 +20,8 @@ export class Gesture {
|
|||||||
private hasFiredStart = true;
|
private hasFiredStart = true;
|
||||||
private isMoveQueued = false;
|
private isMoveQueued = false;
|
||||||
private blocker: BlockerDelegate;
|
private blocker: BlockerDelegate;
|
||||||
private fireOnMoveFunc: any;
|
|
||||||
|
|
||||||
@Event() private ionGestureMove: EventEmitter;
|
@Element() private el: HTMLElement;
|
||||||
@Event() private ionGestureStart: EventEmitter;
|
|
||||||
@Event() private ionGestureEnd: EventEmitter;
|
|
||||||
@Event() private ionGestureNotCaptured: EventEmitter;
|
|
||||||
@Event() private ionPress: EventEmitter;
|
|
||||||
|
|
||||||
@Prop() enabled: boolean = true;
|
@Prop() enabled: boolean = true;
|
||||||
@Prop() attachTo: ElementRef = 'child';
|
@Prop() attachTo: ElementRef = 'child';
|
||||||
@ -49,9 +43,12 @@ export class Gesture {
|
|||||||
@Prop() onPress: GestureCallback;
|
@Prop() onPress: GestureCallback;
|
||||||
@Prop() notCaptured: GestureCallback;
|
@Prop() notCaptured: GestureCallback;
|
||||||
|
|
||||||
constructor() {
|
@Event() private ionGestureMove: EventEmitter;
|
||||||
this.fireOnMoveFunc = this.fireOnMove.bind(this);
|
@Event() private ionGestureStart: EventEmitter;
|
||||||
}
|
@Event() private ionGestureEnd: EventEmitter;
|
||||||
|
@Event() private ionGestureNotCaptured: EventEmitter;
|
||||||
|
@Event() private ionPress: EventEmitter;
|
||||||
|
|
||||||
|
|
||||||
protected ionViewDidLoad() {
|
protected ionViewDidLoad() {
|
||||||
// in this case, we already know the GestureController and Gesture are already
|
// in this case, we already know the GestureController and Gesture are already
|
||||||
@ -203,7 +200,7 @@ export class Gesture {
|
|||||||
if (!this.isMoveQueued && this.hasFiredStart) {
|
if (!this.isMoveQueued && this.hasFiredStart) {
|
||||||
this.isMoveQueued = true;
|
this.isMoveQueued = true;
|
||||||
this.calcGestureData(ev);
|
this.calcGestureData(ev);
|
||||||
Context.dom.write(this.fireOnMoveFunc);
|
Context.dom.write(this.fireOnMove.bind(this));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -221,6 +218,11 @@ export class Gesture {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fireOnMove() {
|
private fireOnMove() {
|
||||||
|
// Since fireOnMove is called inside a RAF, onEnd() might be called,
|
||||||
|
// we must double check hasCapturedPan
|
||||||
|
if (!this.hasCapturedPan) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const detail = this.detail;
|
const detail = this.detail;
|
||||||
this.isMoveQueued = false;
|
this.isMoveQueued = false;
|
||||||
if (this.onMove) {
|
if (this.onMove) {
|
||||||
@ -312,6 +314,7 @@ export class Gesture {
|
|||||||
private reset() {
|
private reset() {
|
||||||
this.hasCapturedPan = false;
|
this.hasCapturedPan = false;
|
||||||
this.hasStartedPan = false;
|
this.hasStartedPan = false;
|
||||||
|
this.isMoveQueued = false;
|
||||||
this.hasFiredStart = true;
|
this.hasFiredStart = true;
|
||||||
this.gesture && this.gesture.release();
|
this.gesture && this.gesture.release();
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import baseAnimation from './base';
|
|||||||
*/
|
*/
|
||||||
export default function(Animation: Animation, _: HTMLElement, menu: Menu): Animation {
|
export default function(Animation: Animation, _: HTMLElement, menu: Menu): Animation {
|
||||||
let closedX: string, openedX: string;
|
let closedX: string, openedX: string;
|
||||||
const width = menu.getWidth();
|
const width = menu.width;
|
||||||
if (menu.isRightSide) {
|
if (menu.isRightSide) {
|
||||||
// right side
|
// right side
|
||||||
closedX = 8 + width + 'px';
|
closedX = 8 + width + 'px';
|
||||||
@ -22,11 +22,11 @@ export default function(Animation: Animation, _: HTMLElement, menu: Menu): Anima
|
|||||||
}
|
}
|
||||||
|
|
||||||
const menuAni = new Animation()
|
const menuAni = new Animation()
|
||||||
.addElement(menu.getMenuElement())
|
.addElement(menu.menuInnerEl)
|
||||||
.fromTo('translateX', closedX, openedX);
|
.fromTo('translateX', closedX, openedX);
|
||||||
|
|
||||||
const backdropApi = new Animation()
|
const backdropApi = new Animation()
|
||||||
.addElement(menu.getBackdropElement())
|
.addElement(menu.backdropEl)
|
||||||
.fromTo('opacity', 0.01, 0.35);
|
.fromTo('opacity', 0.01, 0.35);
|
||||||
|
|
||||||
return baseAnimation(Animation)
|
return baseAnimation(Animation)
|
||||||
|
@ -10,7 +10,7 @@ import baseAnimation from './base';
|
|||||||
export default function(Animation: Animation, _: HTMLElement, menu: Menu): Animation {
|
export default function(Animation: Animation, _: HTMLElement, menu: Menu): Animation {
|
||||||
|
|
||||||
let contentOpenedX: string, menuClosedX: string, menuOpenedX: string;
|
let contentOpenedX: string, menuClosedX: string, menuOpenedX: string;
|
||||||
const width = menu.getWidth();
|
const width = menu.width;
|
||||||
|
|
||||||
if (menu.isRightSide) {
|
if (menu.isRightSide) {
|
||||||
contentOpenedX = -width + 'px';
|
contentOpenedX = -width + 'px';
|
||||||
@ -23,11 +23,11 @@ export default function(Animation: Animation, _: HTMLElement, menu: Menu): Anima
|
|||||||
menuClosedX = -width + 'px';
|
menuClosedX = -width + 'px';
|
||||||
}
|
}
|
||||||
const menuAni = new Animation()
|
const menuAni = new Animation()
|
||||||
.addElement(menu.getMenuElement())
|
.addElement(menu.menuInnerEl)
|
||||||
.fromTo('translateX', menuClosedX, menuOpenedX);
|
.fromTo('translateX', menuClosedX, menuOpenedX);
|
||||||
|
|
||||||
const contentAni = new Animation()
|
const contentAni = new Animation()
|
||||||
.addElement(menu.getContentElement())
|
.addElement(menu.contentEl)
|
||||||
.fromTo('translateX', '0px', contentOpenedX);
|
.fromTo('translateX', '0px', contentOpenedX);
|
||||||
|
|
||||||
return baseAnimation(Animation)
|
return baseAnimation(Animation)
|
||||||
|
@ -8,10 +8,10 @@ import baseAnimation from './base';
|
|||||||
* The menu itself, which is under the content, does not move.
|
* The menu itself, which is under the content, does not move.
|
||||||
*/
|
*/
|
||||||
export default function(Animation: Animation, _: HTMLElement, menu: Menu): Animation {
|
export default function(Animation: Animation, _: HTMLElement, menu: Menu): Animation {
|
||||||
const openedX = (menu.getWidth() * (menu.isRightSide ? -1 : 1)) + 'px';
|
const openedX = (menu.width * (menu.isRightSide ? -1 : 1)) + 'px';
|
||||||
|
|
||||||
const contentOpen = new Animation()
|
const contentOpen = new Animation()
|
||||||
.addElement(menu.getContentElement())
|
.addElement(menu.contentEl)
|
||||||
.fromTo('translateX', '0px', openedX);
|
.fromTo('translateX', '0px', openedX);
|
||||||
|
|
||||||
return baseAnimation(Animation)
|
return baseAnimation(Animation)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Animation, AnimationBuilder, AnimationController, Menu } from '../../index';
|
import { Animation, AnimationBuilder, AnimationController, Menu } from '../../index';
|
||||||
import { Component, Method, Prop } from '@stencil/core';
|
import { Component, Method, Prop } from '@stencil/core';
|
||||||
|
import { HTMLIonMenuElement } from '../../index';
|
||||||
|
|
||||||
import MenuOverlayAnimation from './animations/overlay';
|
import MenuOverlayAnimation from './animations/overlay';
|
||||||
import MenuRevealAnimation from './animations/reveal';
|
import MenuRevealAnimation from './animations/reveal';
|
||||||
@ -48,22 +49,13 @@ export class MenuController {
|
|||||||
*/
|
*/
|
||||||
@Method()
|
@Method()
|
||||||
close(menuId?: string): Promise<boolean> {
|
close(menuId?: string): Promise<boolean> {
|
||||||
let menu: Menu;
|
const menu = (menuId)
|
||||||
|
? this.get(menuId)
|
||||||
if (menuId) {
|
: this.getOpen();
|
||||||
// find the menu by its id
|
|
||||||
menu = this.get(menuId);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// find the menu that is open
|
|
||||||
menu = this.getOpen();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (menu) {
|
if (menu) {
|
||||||
// close the menu
|
|
||||||
return menu.close();
|
return menu.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,9 +88,12 @@ export class MenuController {
|
|||||||
* @return {Menu} Returns the instance of the menu, which is useful for chaining.
|
* @return {Menu} Returns the instance of the menu, which is useful for chaining.
|
||||||
*/
|
*/
|
||||||
@Method()
|
@Method()
|
||||||
enable(shouldEnable: boolean, menuId?: string): Menu {
|
enable(shouldEnable: boolean, menuId?: string): HTMLIonMenuElement {
|
||||||
const menu = this.get(menuId);
|
const menu = this.get(menuId);
|
||||||
return (menu && menu.enable(shouldEnable)) || null;
|
if (menu) {
|
||||||
|
menu.enabled = shouldEnable;
|
||||||
|
}
|
||||||
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -108,9 +103,12 @@ export class MenuController {
|
|||||||
* @return {Menu} Returns the instance of the menu, which is useful for chaining.
|
* @return {Menu} Returns the instance of the menu, which is useful for chaining.
|
||||||
*/
|
*/
|
||||||
@Method()
|
@Method()
|
||||||
swipeEnable(shouldEnable: boolean, menuId?: string): Menu {
|
swipeEnable(shouldEnable: boolean, menuId?: string): HTMLIonMenuElement {
|
||||||
const menu = this.get(menuId);
|
const menu = this.get(menuId);
|
||||||
return (menu && menu.swipeEnable(shouldEnable)) || null;
|
if (menu) {
|
||||||
|
menu.swipeEnabled = shouldEnable;
|
||||||
|
}
|
||||||
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -123,9 +121,8 @@ export class MenuController {
|
|||||||
if (menuId) {
|
if (menuId) {
|
||||||
var menu = this.get(menuId);
|
var menu = this.get(menuId);
|
||||||
return menu && menu.isOpen() || false;
|
return menu && menu.isOpen() || false;
|
||||||
} else {
|
|
||||||
return !!this.getOpen();
|
|
||||||
}
|
}
|
||||||
|
return !!this.getOpen();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -135,7 +132,10 @@ export class MenuController {
|
|||||||
@Method()
|
@Method()
|
||||||
isEnabled(menuId?: string): boolean {
|
isEnabled(menuId?: string): boolean {
|
||||||
const menu = this.get(menuId);
|
const menu = this.get(menuId);
|
||||||
return menu && menu.enabled || false;
|
if (menu) {
|
||||||
|
return menu.enabled;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -148,7 +148,7 @@ export class MenuController {
|
|||||||
* @return {Menu} Returns the instance of the menu if found, otherwise `null`.
|
* @return {Menu} Returns the instance of the menu if found, otherwise `null`.
|
||||||
*/
|
*/
|
||||||
@Method()
|
@Method()
|
||||||
get(menuId?: string): Menu {
|
get(menuId?: string): HTMLIonMenuElement {
|
||||||
var menu: Menu;
|
var menu: Menu;
|
||||||
|
|
||||||
if (menuId === 'left' || menuId === 'right') {
|
if (menuId === 'left' || menuId === 'right') {
|
||||||
@ -156,43 +156,43 @@ export class MenuController {
|
|||||||
// 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.enabled);
|
menu = this.menus.find(m => m.side === menuId && m.enabled);
|
||||||
if (menu) {
|
if (menu) {
|
||||||
return menu;
|
return menu.el;
|
||||||
}
|
}
|
||||||
|
|
||||||
// didn't find a menu side that is enabled
|
// didn't find a menu side that is enabled
|
||||||
// so try to get the first menu side found
|
// so try to get the first menu side found
|
||||||
return this.menus.find(m => m.side === menuId) || null;
|
return this.find(m => m.side === menuId) || null;
|
||||||
|
|
||||||
} else if (menuId) {
|
} else if (menuId) {
|
||||||
// the menuId was not left or right
|
// the menuId was not left or right
|
||||||
// so try to get the menu by its "id"
|
// so try to get the menu by its "id"
|
||||||
return this.menus.find(m => m.menuId === menuId) || null;
|
return this.find(m => m.menuId === menuId) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// return the first enabled menu
|
// return the first enabled menu
|
||||||
menu = this.menus.find(m => m.enabled);
|
menu = this.menus.find(m => m.enabled);
|
||||||
if (menu) {
|
if (menu) {
|
||||||
return menu;
|
return menu.el;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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] : null);
|
return (this.menus.length > 0 ? this.menus[0].el : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {Menu} Returns the instance of the menu already opened, otherwise `null`.
|
* @return {Menu} Returns the instance of the menu already opened, otherwise `null`.
|
||||||
*/
|
*/
|
||||||
@Method()
|
@Method()
|
||||||
getOpen(): Menu {
|
getOpen(): HTMLIonMenuElement {
|
||||||
return this.menus.find(m => m.isOpen());
|
return this.find(m => m.isOpen());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {Array<Menu>} Returns an array of all menu instances.
|
* @return {Array<Menu>} Returns an array of all menu instances.
|
||||||
*/
|
*/
|
||||||
@Method()
|
@Method()
|
||||||
getMenus(): Menu[] {
|
getMenus(): HTMLIonMenuElement[] {
|
||||||
return this.menus;
|
return this.menus.map(menu => menu.el);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -201,7 +201,7 @@ export class MenuController {
|
|||||||
*/
|
*/
|
||||||
@Method()
|
@Method()
|
||||||
isAnimating(): boolean {
|
isAnimating(): boolean {
|
||||||
return this.menus.some(menu => menu.isAnimating());
|
return this.menus.some(menu => menu.isAnimating);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -236,24 +236,28 @@ export class MenuController {
|
|||||||
const side = menu.side;
|
const side = menu.side;
|
||||||
this.menus
|
this.menus
|
||||||
.filter(m => m.side === side && m !== menu)
|
.filter(m => m.side === side && m !== menu)
|
||||||
.map(m => m.enable(false));
|
.map(m => m.enabled = false);
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
registerAnimation(name: string, cls: AnimationBuilder) {
|
|
||||||
this.menuAnimations[name] = cls;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
@Method()
|
@Method()
|
||||||
create(type: string, menuCmp: Menu): Promise<Animation> {
|
createAnimation(type: string, menuCmp: Menu): Promise<Animation> {
|
||||||
const animationBuilder = this.menuAnimations[type];
|
const animationBuilder = this.menuAnimations[type];
|
||||||
return this.animationCtrl.create(animationBuilder, null, menuCmp);
|
return this.animationCtrl.create(animationBuilder, null, menuCmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private registerAnimation(name: string, cls: AnimationBuilder) {
|
||||||
|
this.menuAnimations[name] = cls;
|
||||||
|
}
|
||||||
|
|
||||||
|
private find(predicate: (menu: Menu) => boolean): HTMLIonMenuElement {
|
||||||
|
const instance = this.menus.find(predicate);
|
||||||
|
if (instance) {
|
||||||
|
return instance.el;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,18 +15,21 @@ $menu-ios-box-shadow-color: rgba(0, 0, 0, .25) !default;
|
|||||||
$menu-ios-box-shadow: 0 0 10px $menu-ios-box-shadow-color !default;
|
$menu-ios-box-shadow: 0 0 10px $menu-ios-box-shadow-color !default;
|
||||||
|
|
||||||
|
|
||||||
.menu-ios {
|
.menu-ios .menu-inner {
|
||||||
background: $menu-ios-background;
|
background: $menu-ios-background;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-ios .menu-content-reveal {
|
.menu-ios.menu-type-overlay .menu-inner {
|
||||||
box-shadow: $menu-ios-box-shadow;
|
box-shadow: $menu-ios-box-shadow;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-ios .menu-content-push {
|
// iOS Menu Content
|
||||||
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
.app-ios .menu-content-reveal {
|
||||||
box-shadow: $menu-ios-box-shadow;
|
box-shadow: $menu-ios-box-shadow;
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-menu[type=overlay] .menu-ios {
|
.app-ios .menu-content-push {
|
||||||
box-shadow: $menu-ios-box-shadow;
|
box-shadow: $menu-ios-box-shadow;
|
||||||
}
|
}
|
||||||
|
@ -19,14 +19,17 @@ $menu-md-box-shadow: 0 0 10px $menu-md-box-shadow-color !default;
|
|||||||
background: $menu-md-background;
|
background: $menu-md-background;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-md .menu-content-reveal {
|
.menu-md.menu-type-overlay .menu-inner {
|
||||||
box-shadow: $menu-md-box-shadow;
|
box-shadow: $menu-md-box-shadow;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-md .menu-content-push {
|
// MD Menu Content
|
||||||
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
.app-md .menu-content-reveal {
|
||||||
box-shadow: $menu-md-box-shadow;
|
box-shadow: $menu-md-box-shadow;
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-menu[type=overlay] .menu-md {
|
.app-md .menu-content-push {
|
||||||
box-shadow: $menu-md-box-shadow;
|
box-shadow: $menu-md-box-shadow;
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ ion-menu.show-menu {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-menu[side=left] > .menu-inner {
|
.menu-side-left > .menu-inner {
|
||||||
@include multi-dir() {
|
@include multi-dir() {
|
||||||
// scss-lint:disable PropertySpelling
|
// scss-lint:disable PropertySpelling
|
||||||
right: auto;
|
right: auto;
|
||||||
@ -54,7 +54,7 @@ ion-menu[side=left] > .menu-inner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-menu[side=right] > .menu-inner {
|
.menu-side-right > .menu-inner {
|
||||||
@include multi-dir() {
|
@include multi-dir() {
|
||||||
// scss-lint:disable PropertySpelling
|
// scss-lint:disable PropertySpelling
|
||||||
right: 0;
|
right: 0;
|
||||||
@ -62,10 +62,6 @@ ion-menu[side=right] > .menu-inner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-menu[side=end] > .menu-inner {
|
|
||||||
@include position-horizontal(auto, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-menu ion-backdrop {
|
ion-menu ion-backdrop {
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
display: none;
|
display: none;
|
||||||
@ -84,6 +80,7 @@ ion-menu ion-backdrop {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.menu-content-open ion-pane,
|
.menu-content-open ion-pane,
|
||||||
|
.menu-content-open .ion-pane,
|
||||||
.menu-content-open ion-content,
|
.menu-content-open ion-content,
|
||||||
.menu-content-open .toolbar {
|
.menu-content-open .toolbar {
|
||||||
// the containing element itself should be clickable but
|
// the containing element itself should be clickable but
|
||||||
@ -106,11 +103,11 @@ ion-menu ion-backdrop {
|
|||||||
// The content slides over to reveal the menu underneath.
|
// The content slides over to reveal the menu underneath.
|
||||||
// The menu itself, which is under the content, does not move.
|
// The menu itself, which is under the content, does not move.
|
||||||
|
|
||||||
ion-menu[type=reveal] {
|
ion-menu.menu-type-reveal {
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-menu[type=reveal].show-menu .menu-inner {
|
ion-menu.menu-type-reveal.show-menu .menu-inner {
|
||||||
@include transform(translate3d(0, 0, 0));
|
@include transform(translate3d(0, 0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,10 +117,11 @@ ion-menu[type=reveal].show-menu .menu-inner {
|
|||||||
// The menu slides over the content. The content
|
// The menu slides over the content. The content
|
||||||
// itself, which is under the menu, does not move.
|
// itself, which is under the menu, does not move.
|
||||||
|
|
||||||
ion-menu[type=overlay] {
|
ion-menu.menu-type-overlay {
|
||||||
z-index: $z-index-menu-overlay;
|
z-index: $z-index-menu-overlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-menu[type=overlay] .show-backdrop {
|
ion-menu.menu-type-overlay .show-backdrop {
|
||||||
display: block;
|
display: block;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Component, Element, Event, EventEmitter, Listen, Prop, PropDidChange } from '@stencil/core';
|
import { Component, Element, Event, EventEmitter, Listen, Method, Prop, PropDidChange, PropWillChange } from '@stencil/core';
|
||||||
import { Animation, Config, SplitPaneAlert } from '../../index';
|
import { Animation, Config, GestureDetail, HTMLIonMenuElement, SplitPaneAlert } from '../../index';
|
||||||
import { MenuController } from './menu-controller';
|
import { MenuController } from './menu-controller';
|
||||||
import { Side, assert, checkEdgeSide, isRightSide } from '../../utils/helpers';
|
import { Side, assert, checkEdgeSide, isRightSide } from '../../utils/helpers';
|
||||||
|
|
||||||
@ -20,38 +20,27 @@ export type Lazy<T> = T &
|
|||||||
})
|
})
|
||||||
export class Menu {
|
export class Menu {
|
||||||
|
|
||||||
private _backdropEle: HTMLElement;
|
private gestureBlocker: string;
|
||||||
private _menuInnerEle: HTMLElement;
|
private animation: Animation;
|
||||||
private _unregCntClick: Function;
|
private isPane = false;
|
||||||
private _unregBdClick: Function;
|
|
||||||
private _activeBlock: string;
|
|
||||||
|
|
||||||
private _cntElm: HTMLElement;
|
|
||||||
private _animation: Animation;
|
|
||||||
private _init = false;
|
|
||||||
private _isPane = false;
|
|
||||||
private _isAnimating: boolean = false;
|
|
||||||
private _isOpen: boolean = false;
|
private _isOpen: boolean = false;
|
||||||
private _width: number = null;
|
private lastOnEnd = 0;
|
||||||
|
|
||||||
mode: string;
|
mode: string;
|
||||||
color: string;
|
color: string;
|
||||||
|
isAnimating: boolean = false;
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
isRightSide: boolean = false;
|
isRightSide: boolean = false;
|
||||||
|
width: number = null;
|
||||||
|
|
||||||
@Element() private el: HTMLElement;
|
backdropEl: HTMLElement;
|
||||||
|
menuInnerEl: HTMLElement;
|
||||||
|
contentEl: HTMLElement;
|
||||||
|
menuCtrl: MenuController;
|
||||||
|
|
||||||
@Event() ionDrag: EventEmitter;
|
@Element() el: HTMLIonMenuElement;
|
||||||
@Event() ionOpen: EventEmitter;
|
|
||||||
@Event() ionClose: EventEmitter;
|
|
||||||
|
|
||||||
@Prop({ context: 'config' }) config: Config;
|
@Prop({ context: 'config' }) config: Config;
|
||||||
|
|
||||||
@Prop({ connect: 'ion-menu-controller' }) lazyMenuCtrl: Lazy<MenuController>;
|
@Prop({ connect: 'ion-menu-controller' }) lazyMenuCtrl: Lazy<MenuController>;
|
||||||
menuCtrl: MenuController;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @input {string} The content's id the menu should use.
|
* @input {string} The content's id the menu should use.
|
||||||
@ -68,22 +57,48 @@ export class Menu {
|
|||||||
* see the `menuType` in the [config](../../config/Config). Available options:
|
* see the `menuType` in the [config](../../config/Config). Available options:
|
||||||
* `"overlay"`, `"reveal"`, `"push"`.
|
* `"overlay"`, `"reveal"`, `"push"`.
|
||||||
*/
|
*/
|
||||||
@Prop() type: string = 'overlay';
|
@Prop({ mutable: true }) type: string = 'overlay';
|
||||||
|
@PropWillChange('type')
|
||||||
|
typeChanged(type: string) {
|
||||||
|
if (this.contentEl) {
|
||||||
|
this.contentEl.classList.remove('menu-content-' + this.type);
|
||||||
|
this.contentEl.classList.add('menu-content-' + type);
|
||||||
|
this.contentEl.removeAttribute('style');
|
||||||
|
}
|
||||||
|
if (this.menuInnerEl) {
|
||||||
|
// Remove effects of previous animations
|
||||||
|
this.menuInnerEl.removeAttribute('style');
|
||||||
|
}
|
||||||
|
this.animation = null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @input {boolean} If true, the menu is enabled. Default `true`.
|
* @input {boolean} If true, the menu is enabled. Default `true`.
|
||||||
*/
|
*/
|
||||||
@Prop({ mutable: true }) enabled: boolean;
|
@Prop({ mutable: true }) enabled: boolean;
|
||||||
|
@PropDidChange('enabled')
|
||||||
|
enabledChanged() {
|
||||||
|
this.updateState();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @input {string} Which side of the view the menu should be placed. Default `"start"`.
|
* @input {string} Which side of the view the menu should be placed. Default `"start"`.
|
||||||
*/
|
*/
|
||||||
@Prop() side: Side = 'start';
|
@Prop() side: Side = 'start';
|
||||||
|
@PropDidChange('side')
|
||||||
|
sideChanged() {
|
||||||
|
const isRTL = false;
|
||||||
|
this.isRightSide = isRightSide(this.side, isRTL);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @input {boolean} If true, swiping the menu is enabled. Default `true`.
|
* @input {boolean} If true, swiping the menu is enabled. Default `true`.
|
||||||
*/
|
*/
|
||||||
@Prop() swipeEnabled: boolean = true;
|
@Prop() swipeEnabled: boolean = true;
|
||||||
|
@PropDidChange('swipeEnabled')
|
||||||
|
swipeEnabledChange() {
|
||||||
|
this.updateState();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @input {boolean} If true, the menu will persist on child pages.
|
* @input {boolean} If true, the menu will persist on child pages.
|
||||||
@ -96,57 +111,36 @@ export class Menu {
|
|||||||
@Prop() maxEdgeStart: number = 50;
|
@Prop() maxEdgeStart: number = 50;
|
||||||
|
|
||||||
|
|
||||||
// @PropDidChange('side')
|
@Event() ionDrag: EventEmitter;
|
||||||
// sideChanged(side: Side) {
|
@Event() ionOpen: EventEmitter;
|
||||||
// // TODO: const isRTL = this._plt.isRTL;
|
@Event() ionClose: EventEmitter;
|
||||||
// const isRTL = false;
|
|
||||||
// // this.isRightSide = isRightSide(side, isRTL);
|
|
||||||
// }
|
|
||||||
@Listen('body:ionSplitPaneDidChange')
|
|
||||||
splitPaneChanged(ev: SplitPaneAlert) {
|
|
||||||
this._isPane = ev.detail.splitPane.isPane(this.el);
|
|
||||||
this._updateState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@PropDidChange('enabled')
|
|
||||||
enabledChanged() {
|
|
||||||
this._updateState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@PropDidChange('swipeEnabled')
|
|
||||||
swipeEnabledChange() {
|
|
||||||
this._updateState();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ionViewWillLoad() {
|
protected ionViewWillLoad() {
|
||||||
return this.lazyMenuCtrl.componentOnReady()
|
return this.lazyMenuCtrl.componentOnReady()
|
||||||
.then(menu => this.menuCtrl = menu);
|
.then(menu => this.menuCtrl = menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
protected ionViewDidLoad() {
|
protected ionViewDidLoad() {
|
||||||
assert(!!this.menuCtrl, 'menucontroller was not initialized');
|
assert(!!this.menuCtrl, 'menucontroller was not initialized');
|
||||||
|
|
||||||
this._menuInnerEle = this.el.querySelector('.menu-inner') as HTMLElement;
|
const el = this.el;
|
||||||
this._backdropEle = this.el.querySelector('.menu-backdrop') as HTMLElement;
|
|
||||||
|
|
||||||
const contentQuery = (this.content)
|
const contentQuery = (this.content)
|
||||||
? '> #' + this.content
|
? '#' + this.content
|
||||||
: '[main]';
|
: '[main]';
|
||||||
const parent = this.el.parentElement;
|
const parent = el.parentElement;
|
||||||
const content = this._cntElm = parent.querySelector(contentQuery) as HTMLElement;
|
const content = this.contentEl = parent.querySelector(contentQuery) as HTMLElement;
|
||||||
if (!content || !content.tagName) {
|
if (!content || !content.tagName) {
|
||||||
// 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.');
|
||||||
}
|
}
|
||||||
// TODO: make PropDidChange work
|
this.menuInnerEl = el.querySelector('.menu-inner') as HTMLElement;
|
||||||
this.isRightSide = isRightSide(this.side, false);
|
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');
|
||||||
content.classList.add('menu-content-' + this.type);
|
|
||||||
|
this.typeChanged(this.type);
|
||||||
|
this.sideChanged();
|
||||||
|
|
||||||
let isEnabled = this.enabled;
|
let isEnabled = this.enabled;
|
||||||
if (isEnabled === true || typeof isEnabled === 'undefined') {
|
if (isEnabled === true || typeof isEnabled === 'undefined') {
|
||||||
@ -159,100 +153,88 @@ export class Menu {
|
|||||||
this.menuCtrl._register(this);
|
this.menuCtrl._register(this);
|
||||||
|
|
||||||
// mask it as enabled / disabled
|
// mask it as enabled / disabled
|
||||||
this.enable(isEnabled);
|
this.enabled = isEnabled;
|
||||||
this._init = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hostData() {
|
protected ionViewDidUnload() {
|
||||||
return {
|
this.menuCtrl._unregister(this);
|
||||||
'role': 'navigation',
|
this.animation && this.animation.destroy();
|
||||||
'side': this.getSide(),
|
|
||||||
'type': this.type,
|
this.menuCtrl = this.animation = null;
|
||||||
class: {
|
this.contentEl = this.backdropEl = this.menuInnerEl = null;
|
||||||
'menu-enabled': this._canOpen()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getSide(): string {
|
@Listen('body:ionSplitPaneDidChange')
|
||||||
return this.isRightSide ? 'right' : 'left';
|
splitPaneChanged(ev: SplitPaneAlert) {
|
||||||
|
this.isPane = ev.detail.splitPane.isPane(this.el);
|
||||||
|
this.updateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
@Listen('body:click', { enabled: false, capture: true })
|
||||||
return ([
|
|
||||||
<div class='menu-inner'>
|
|
||||||
<slot></slot>
|
|
||||||
</div>,
|
|
||||||
<ion-backdrop class='menu-backdrop'></ion-backdrop> ,
|
|
||||||
<ion-gesture {...{
|
|
||||||
'canStart': this.canStart.bind(this),
|
|
||||||
'onWillStart': this._swipeWillStart.bind(this),
|
|
||||||
'onStart': this._swipeStart.bind(this),
|
|
||||||
'onMove': this._swipeProgress.bind(this),
|
|
||||||
'onEnd': this._swipeEnd.bind(this),
|
|
||||||
'maxEdgeStart': this.maxEdgeStart,
|
|
||||||
'edge': this.side,
|
|
||||||
'enabled': this._canOpen() && this.swipeEnabled,
|
|
||||||
'gestureName': 'menu-swipe',
|
|
||||||
'gesturePriority': 10,
|
|
||||||
'type': 'pan',
|
|
||||||
'direction': 'x',
|
|
||||||
'threshold': 10,
|
|
||||||
'attachTo': 'body',
|
|
||||||
'disableScroll': true,
|
|
||||||
'block': this._activeBlock
|
|
||||||
}}></ion-gesture>
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
onBackdropClick(ev: UIEvent) {
|
onBackdropClick(ev: UIEvent) {
|
||||||
ev.preventDefault();
|
const el = ev.target as HTMLElement;
|
||||||
ev.stopPropagation();
|
if (!el.closest('.menu-inner') && this.lastOnEnd < (ev.timeStamp - 100)) {
|
||||||
this.close();
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Method()
|
||||||
* @hidden
|
isOpen(): boolean {
|
||||||
*/
|
return this._isOpen;
|
||||||
private prepareAnimation(): Promise<void> {
|
}
|
||||||
const width = this._menuInnerEle.offsetWidth;
|
|
||||||
if (width === this._width) {
|
@Method()
|
||||||
|
setOpen(shouldOpen: boolean, animated: boolean = true): Promise<boolean> {
|
||||||
|
// If the menu is disabled or it is currenly being animated, let's do nothing
|
||||||
|
if (!this.isActive() || this.isAnimating || (shouldOpen === this._isOpen)) {
|
||||||
|
return Promise.resolve(this._isOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.beforeAnimation();
|
||||||
|
return this.loadAnimation()
|
||||||
|
.then(() => this.startAnimation(shouldOpen, animated))
|
||||||
|
.then(() => this.afterAnimation(shouldOpen));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Method()
|
||||||
|
open(): Promise<boolean> {
|
||||||
|
return this.setOpen(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Method()
|
||||||
|
close(): Promise<boolean> {
|
||||||
|
return this.setOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Method()
|
||||||
|
toggle(): Promise<boolean> {
|
||||||
|
return this.setOpen(!this._isOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadAnimation(): Promise<void> {
|
||||||
|
// Menu swipe animation takes the menu's inner width as parameter,
|
||||||
|
// If `offsetWidth` changes, we need to create a new animation.
|
||||||
|
const width = this.menuInnerEl.offsetWidth;
|
||||||
|
if (width === this.width && this.animation !== null) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
if (this._animation) {
|
// Destroy existing animation
|
||||||
this._animation.destroy();
|
this.animation && this.animation.destroy();
|
||||||
this._animation = null;
|
this.animation = null;
|
||||||
}
|
this.width = width;
|
||||||
this._width = width;
|
|
||||||
return this.menuCtrl.create(this.type, this).then(ani => {
|
// Create new animation
|
||||||
this._animation = ani;
|
return this.menuCtrl.createAnimation(this.type, this).then(ani => {
|
||||||
|
this.animation = ani;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private startAnimation(shouldOpen: boolean, animated: boolean): Promise<Animation> {
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
setOpen(shouldOpen: boolean, animated: boolean = true): Promise<boolean> {
|
|
||||||
// If the menu is disabled or it is currenly being animated, let's do nothing
|
|
||||||
if ((shouldOpen === this._isOpen) || !this._canOpen() || this._isAnimating) {
|
|
||||||
return Promise.resolve(this._isOpen);
|
|
||||||
}
|
|
||||||
this._before();
|
|
||||||
return this.prepareAnimation()
|
|
||||||
.then(() => this._startAnimation(shouldOpen, animated))
|
|
||||||
.then(() => {
|
|
||||||
this._after(shouldOpen);
|
|
||||||
return this._isOpen;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_startAnimation(shouldOpen: boolean, animated: boolean): Promise<Animation> {
|
|
||||||
let done;
|
let done;
|
||||||
const promise = new Promise<Animation>(resolve => done = resolve);
|
const promise = new Promise<Animation>(resolve => done = resolve);
|
||||||
const ani = this._animation
|
const ani = this.animation
|
||||||
.onFinish(done, {oneTimeCallback: true, clearExistingCallacks: true })
|
.onFinish(done, {oneTimeCallback: true, clearExistingCallacks: true })
|
||||||
.reverse(!shouldOpen);
|
.reverse(!shouldOpen);
|
||||||
|
|
||||||
@ -265,98 +247,86 @@ export class Menu {
|
|||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
_forceClosing() {
|
private isActive(): boolean {
|
||||||
assert(this._isOpen, 'menu cannot be closed');
|
return this.enabled && !this.isPane;
|
||||||
|
|
||||||
this._isAnimating = true;
|
|
||||||
this._startAnimation(false, false);
|
|
||||||
this._after(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getWidth(): number {
|
private canSwipe(): boolean {
|
||||||
return this._width;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
canSwipe(): boolean {
|
|
||||||
return this.swipeEnabled &&
|
return this.swipeEnabled &&
|
||||||
!this._isAnimating &&
|
!this.isAnimating &&
|
||||||
this._canOpen();
|
this.isActive();
|
||||||
// TODO: && this._app.isEnabled();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private canStart(detail: GestureDetail): boolean {
|
||||||
* @hidden
|
if (!this.canSwipe()) {
|
||||||
*/
|
return false;
|
||||||
isAnimating(): boolean {
|
}
|
||||||
return this._isAnimating;
|
if (this._isOpen) {
|
||||||
|
return true;
|
||||||
|
} else if (this.menuCtrl.getOpen()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return checkEdgeSide(detail.currentX, this.isRightSide, this.maxEdgeStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private onWillStart(): Promise<void> {
|
||||||
* @hidden
|
this.beforeAnimation();
|
||||||
*/
|
return this.loadAnimation();
|
||||||
isOpen(): boolean {
|
|
||||||
return this._isOpen;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_swipeWillStart(): Promise<void> {
|
private onDragStart() {
|
||||||
this._before();
|
assert(!!this.animation, '_type is undefined');
|
||||||
return this.prepareAnimation();
|
if (!this.isAnimating) {
|
||||||
}
|
assert(false, 'isAnimating has to be true');
|
||||||
|
|
||||||
_swipeStart() {
|
|
||||||
assert(!!this._animation, '_type is undefined');
|
|
||||||
if (!this._isAnimating) {
|
|
||||||
assert(false, '_isAnimating has to be true');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the cloned animation should not use an easing curve during seek
|
// the cloned animation should not use an easing curve during seek
|
||||||
this._animation
|
this.animation
|
||||||
.reverse(this._isOpen)
|
.reverse(this._isOpen)
|
||||||
.progressStart();
|
.progressStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
_swipeProgress(slide: any) {
|
private onDragMove(detail: GestureDetail) {
|
||||||
assert(!!this._animation, '_type is undefined');
|
assert(!!this.animation, '_type is undefined');
|
||||||
if (!this._isAnimating) {
|
if (!this.isAnimating) {
|
||||||
assert(false, '_isAnimating has to be true');
|
assert(false, 'isAnimating has to be true');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const delta = computeDelta(slide.deltaX, this._isOpen, this.isRightSide);
|
const delta = computeDelta(detail.deltaX, this._isOpen, this.isRightSide);
|
||||||
const stepValue = delta / this._width;
|
const stepValue = delta / this.width;
|
||||||
this._animation.progressStep(stepValue);
|
this.animation.progressStep(stepValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
_swipeEnd(slide: any) {
|
private onDragEnd(detail: GestureDetail) {
|
||||||
assert(!!this._animation, '_type is undefined');
|
assert(!!this.animation, '_type is undefined');
|
||||||
if (!this._isAnimating) {
|
if (!this.isAnimating) {
|
||||||
assert(false, '_isAnimating has to be true');
|
assert(false, 'isAnimating has to be true');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
console.log('end');
|
||||||
|
|
||||||
|
const isOpen = this._isOpen;
|
||||||
const isRightSide = this.isRightSide;
|
const isRightSide = this.isRightSide;
|
||||||
const delta = computeDelta(slide.deltaX, this._isOpen, isRightSide);
|
const delta = computeDelta(detail.deltaX, isOpen, isRightSide);
|
||||||
const width = this._width;
|
const width = this.width;
|
||||||
const stepValue = delta / width;
|
const stepValue = delta / width;
|
||||||
const velocity = slide.velocityX;
|
const velocity = detail.velocityX;
|
||||||
const z = width / 2;
|
const z = width / 2.0;
|
||||||
const shouldCompleteRight = (velocity >= 0)
|
const shouldCompleteRight = (velocity >= 0)
|
||||||
&& (velocity > 0.2 || slide.deltaX > z);
|
&& (velocity > 0.2 || detail.deltaX > z);
|
||||||
|
|
||||||
const shouldCompleteLeft = (velocity <= 0)
|
const shouldCompleteLeft = (velocity <= 0)
|
||||||
&& (velocity < -0.2 || slide.deltaX < -z);
|
&& (velocity < -0.2 || detail.deltaX < -z);
|
||||||
|
|
||||||
const opening = !this._isOpen;
|
const shouldComplete = (isOpen)
|
||||||
const shouldComplete = (opening)
|
? isRightSide ? shouldCompleteRight : shouldCompleteLeft
|
||||||
? isRightSide ? shouldCompleteLeft : shouldCompleteRight
|
: isRightSide ? shouldCompleteLeft : shouldCompleteRight;
|
||||||
: isRightSide ? shouldCompleteRight : shouldCompleteLeft;
|
|
||||||
|
|
||||||
let isOpen = (opening && shouldComplete);
|
let shouldOpen = (!isOpen && shouldComplete);
|
||||||
if (!opening && !shouldComplete) {
|
if (isOpen && !shouldComplete) {
|
||||||
isOpen = true;
|
shouldOpen = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const missing = shouldComplete ? 1 - stepValue : stepValue;
|
const missing = shouldComplete ? 1 - stepValue : stepValue;
|
||||||
@ -367,25 +337,24 @@ export class Menu {
|
|||||||
realDur = Math.min(dur, 380);
|
realDur = Math.min(dur, 380);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._animation
|
this.lastOnEnd = detail.timeStamp;
|
||||||
.onFinish(() => this._after(isOpen), { clearExistingCallacks: true })
|
this.animation
|
||||||
|
.onFinish(() => this.afterAnimation(shouldOpen), { clearExistingCallacks: true })
|
||||||
.progressEnd(shouldComplete, stepValue, realDur);
|
.progressEnd(shouldComplete, stepValue, realDur);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _before() {
|
private beforeAnimation() {
|
||||||
assert(!this._isAnimating, '_before() should not be called while animating');
|
assert(!this.isAnimating, '_before() should not be called while animating');
|
||||||
|
|
||||||
// this places the menu into the correct location before it animates in
|
// this places the menu into the correct location before it animates in
|
||||||
// this css class doesn't actually kick off any animations
|
// this css class doesn't actually kick off any animations
|
||||||
this.el.classList.add('show-menu');
|
this.el.classList.add(SHOW_MENU);
|
||||||
this._backdropEle.classList.add('show-backdrop');
|
this.backdropEl.classList.add(SHOW_BACKDROP);
|
||||||
|
this.isAnimating = true;
|
||||||
this.resize();
|
|
||||||
this._isAnimating = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _after(isOpen: boolean) {
|
private afterAnimation(isOpen: boolean): boolean {
|
||||||
assert(this._isAnimating, '_before() should be called while animating');
|
assert(this.isAnimating, '_before() should be called while animating');
|
||||||
|
|
||||||
// TODO: this._app.setEnabled(false, 100);
|
// TODO: this._app.setEnabled(false, 100);
|
||||||
|
|
||||||
@ -394,193 +363,105 @@ export class Menu {
|
|||||||
// and only remove listeners/css if it's not open
|
// and only remove listeners/css if it's not open
|
||||||
// emit opened/closed events
|
// emit opened/closed events
|
||||||
this._isOpen = isOpen;
|
this._isOpen = isOpen;
|
||||||
this._isAnimating = false;
|
this.isAnimating = false;
|
||||||
|
|
||||||
// add/remove backdrop click listeners
|
// add/remove backdrop click listeners
|
||||||
this._backdropClick(isOpen);
|
Context.enableListener(this, 'body:click', isOpen);
|
||||||
|
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
// disable swipe to go back gesture
|
// disable swipe to go back gesture
|
||||||
this._activeBlock = GESTURE_BLOCKER;
|
this.gestureBlocker = GESTURE_BLOCKER;
|
||||||
|
|
||||||
// add css class
|
// add css class
|
||||||
this._cntElm.classList.add('menu-content-open');
|
this.contentEl.classList.add(MENU_CONTENT_OPEN);
|
||||||
|
|
||||||
// emit open event
|
// emit open event
|
||||||
this.ionOpen.emit({ menu: this });
|
this.ionOpen.emit({ menu: this });
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// enable swipe to go back gesture
|
// enable swipe to go back gesture
|
||||||
this._activeBlock = null;
|
this.gestureBlocker = null;
|
||||||
|
|
||||||
// remove css classes
|
// remove css classes
|
||||||
this.el.classList.remove('show-menu');
|
this.el.classList.remove(SHOW_MENU);
|
||||||
this._cntElm.classList.remove('menu-content-open');
|
this.contentEl.classList.remove(MENU_CONTENT_OPEN);
|
||||||
this._backdropEle.classList.remove('show-menu');
|
this.backdropEl.classList.remove(SHOW_BACKDROP);
|
||||||
|
|
||||||
// emit close event
|
// emit close event
|
||||||
this.ionClose.emit({ menu: this });
|
this.ionClose.emit({ menu: this });
|
||||||
}
|
}
|
||||||
|
return isOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private updateState() {
|
||||||
* @hidden
|
const isActive = this.isActive();
|
||||||
*/
|
|
||||||
open(): Promise<boolean> {
|
|
||||||
return this.setOpen(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
close(): Promise<boolean> {
|
|
||||||
return this.setOpen(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
resize() {
|
|
||||||
// TODO
|
|
||||||
// const content: Content | Nav = this.menuContent
|
|
||||||
// ? this.menuContent
|
|
||||||
// : this.menuNav;
|
|
||||||
// content && content.resize();
|
|
||||||
}
|
|
||||||
|
|
||||||
canStart(detail: any): boolean {
|
|
||||||
if (!this.canSwipe()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this._isOpen) {
|
|
||||||
return true;
|
|
||||||
} else if (this.getMenuController().getOpen()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return checkEdgeSide(detail.currentX, this.isRightSide, this.maxEdgeStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
toggle(): Promise<boolean> {
|
|
||||||
return this.setOpen(!this._isOpen);
|
|
||||||
}
|
|
||||||
|
|
||||||
_canOpen(): boolean {
|
|
||||||
return this.enabled && !this._isPane;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
// @PropDidChange('swipeEnabled')
|
|
||||||
// @PropDidChange('enabled')
|
|
||||||
_updateState() {
|
|
||||||
const canOpen = this._canOpen();
|
|
||||||
|
|
||||||
// Close menu inmediately
|
// Close menu inmediately
|
||||||
if (!canOpen && this._isOpen) {
|
if (!isActive && this._isOpen) {
|
||||||
assert(this._init, 'menu must be initialized');
|
|
||||||
// close if this menu is open, and should not be enabled
|
// close if this menu is open, and should not be enabled
|
||||||
this._forceClosing();
|
this.forceClosing();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.enabled && this.menuCtrl) {
|
if (this.enabled && this.menuCtrl) {
|
||||||
this.menuCtrl._setActiveMenu(this);
|
this.menuCtrl._setActiveMenu(this);
|
||||||
}
|
}
|
||||||
|
assert(!this.isAnimating, 'can not be animating');
|
||||||
if (!this._init) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._isOpen || (this._isPane && this.enabled)) {
|
|
||||||
this.resize();
|
|
||||||
}
|
|
||||||
assert(!this._isAnimating, 'can not be animating');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private forceClosing() {
|
||||||
* @hidden
|
assert(this._isOpen, 'menu cannot be closed');
|
||||||
*/
|
|
||||||
enable(shouldEnable: boolean): Menu {
|
this.isAnimating = true;
|
||||||
this.enabled = shouldEnable;
|
this.startAnimation(false, false);
|
||||||
return this;
|
this.afterAnimation(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected hostData() {
|
||||||
* @internal
|
const typeClass = 'menu-type-' + this.type;
|
||||||
*/
|
return {
|
||||||
initPane(): boolean {
|
role: 'navigation',
|
||||||
return false;
|
class: {
|
||||||
|
'menu-enabled': this.isActive(),
|
||||||
|
'menu-side-right': this.isRightSide,
|
||||||
|
'menu-side-left': !this.isRightSide,
|
||||||
|
[typeClass]: true,
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected render() {
|
||||||
* @hidden
|
return ([
|
||||||
*/
|
<div class='menu-inner page-inner'>
|
||||||
swipeEnable(shouldEnable: boolean): Menu {
|
<slot></slot>
|
||||||
this.swipeEnabled = shouldEnable;
|
</div>,
|
||||||
return this;
|
<ion-backdrop class='menu-backdrop'></ion-backdrop> ,
|
||||||
|
<ion-gesture {...{
|
||||||
|
'canStart': this.canStart.bind(this),
|
||||||
|
'onWillStart': this.onWillStart.bind(this),
|
||||||
|
'onStart': this.onDragStart.bind(this),
|
||||||
|
'onMove': this.onDragMove.bind(this),
|
||||||
|
'onEnd': this.onDragEnd.bind(this),
|
||||||
|
'maxEdgeStart': this.maxEdgeStart,
|
||||||
|
'edge': this.side,
|
||||||
|
'enabled': this.isActive() && this.swipeEnabled,
|
||||||
|
'gestureName': 'menu-swipe',
|
||||||
|
'gesturePriority': 10,
|
||||||
|
'type': 'pan',
|
||||||
|
'direction': 'x',
|
||||||
|
'threshold': 10,
|
||||||
|
'attachTo': 'body',
|
||||||
|
'disableScroll': true,
|
||||||
|
'block': this.gestureBlocker
|
||||||
|
}}></ion-gesture>
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
getMenuElement(): HTMLElement {
|
|
||||||
return this.el.querySelector('.menu-inner') as HTMLElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
getContentElement(): HTMLElement {
|
|
||||||
return this._cntElm;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
getBackdropElement(): HTMLElement {
|
|
||||||
return this._backdropEle;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
getMenuController(): MenuController {
|
|
||||||
return this.menuCtrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _backdropClick(shouldAdd: boolean) {
|
|
||||||
const onBackdropClick = this.onBackdropClick.bind(this);
|
|
||||||
|
|
||||||
if (shouldAdd && !this._unregBdClick) {
|
|
||||||
this._unregBdClick = Context.addListener(this._backdropEle, 'click', onBackdropClick, { capture: true });
|
|
||||||
this._unregCntClick = Context.addListener(this._backdropEle, 'click', onBackdropClick, { capture: true });
|
|
||||||
|
|
||||||
} else if (!shouldAdd && this._unregBdClick) {
|
|
||||||
this._unregBdClick();
|
|
||||||
this._unregCntClick();
|
|
||||||
this._unregBdClick = this._unregCntClick = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
protected ionViewDidUnload() {
|
|
||||||
this._backdropClick(false);
|
|
||||||
|
|
||||||
this.menuCtrl._unregister(this);
|
|
||||||
this._animation && this._animation.destroy();
|
|
||||||
|
|
||||||
this.menuCtrl = this._animation = this._cntElm = this._backdropEle = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeDelta(deltaX: number, isOpen: boolean, isRightSide: boolean): number {
|
function computeDelta(deltaX: number, isOpen: boolean, isRightSide: boolean): number {
|
||||||
return Math.max(0, (isOpen !== isRightSide) ? -deltaX : deltaX);
|
return Math.max(0, (isOpen !== isRightSide) ? -deltaX : deltaX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SHOW_MENU = 'show-menu';
|
||||||
|
const SHOW_BACKDROP = 'show-backdrop';
|
||||||
|
const MENU_CONTENT_OPEN = 'menu-content-open';
|
||||||
const GESTURE_BLOCKER = 'goback-swipe';
|
const GESTURE_BLOCKER = 'goback-swipe';
|
||||||
|
@ -64,8 +64,15 @@
|
|||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content padding>
|
<ion-content padding>
|
||||||
<ion-button onclick="openLeft()">Open left menu</ion-button>
|
<p>
|
||||||
<ion-button onclick="openRight()">Open right menu</ion-button>
|
<ion-button onclick="openLeft()">Open left menu</ion-button>
|
||||||
|
<ion-button onclick="openRight()">Open right menu</ion-button>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<ion-button onclick="setPush()">Set Push</ion-button>
|
||||||
|
<ion-button onclick="setOverlay()">Set Overlay</ion-button>
|
||||||
|
<ion-button onclick="setReveal()">Set Reveal</ion-button>
|
||||||
|
</p>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
||||||
</ion-page>
|
</ion-page>
|
||||||
@ -83,6 +90,18 @@
|
|||||||
console.log('Open right menu');
|
console.log('Open right menu');
|
||||||
menu.open('right');
|
menu.open('right');
|
||||||
}
|
}
|
||||||
|
function setPush() {
|
||||||
|
menu.get('left').type = 'push';
|
||||||
|
menu.get('right').type = 'push';
|
||||||
|
}
|
||||||
|
function setOverlay() {
|
||||||
|
menu.get('left').type = 'overlay';
|
||||||
|
menu.get('right').type = 'overlay';
|
||||||
|
}
|
||||||
|
function setReveal() {
|
||||||
|
menu.get('left').type = 'reveal';
|
||||||
|
menu.get('right').type = 'reveal';
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Reference in New Issue
Block a user