mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-20 12:29:55 +08:00
Merge branch 'master' into 3.0
This commit is contained in:
@ -351,6 +351,18 @@ export class Animation {
|
||||
});
|
||||
}
|
||||
|
||||
syncPlay() {
|
||||
// If the animation was already invalidated (it did finish), do nothing
|
||||
if (!this.plt) {
|
||||
return;
|
||||
}
|
||||
const opts = { duration: 0 };
|
||||
this._isAsync = false;
|
||||
this._clearAsync();
|
||||
this._playInit(opts);
|
||||
this._playDomInspect(opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* DOM WRITE
|
||||
@ -569,7 +581,6 @@ export class Animation {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -589,7 +600,6 @@ export class Animation {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -89,8 +89,9 @@ export class App {
|
||||
|
||||
runInDev(() => {
|
||||
// During developement, navPop can be triggered by calling
|
||||
if (!(<any>_plt.win())['HWBackButton']) {
|
||||
(<any>_plt.win())['HWBackButton'] = () => {
|
||||
const win = <any>_plt.win();
|
||||
if (!win['HWBackButton']) {
|
||||
win['HWBackButton'] = () => {
|
||||
let p = this.goBack();
|
||||
p && p.catch(() => console.debug('hardware go back cancelled'));
|
||||
return p;
|
||||
@ -227,6 +228,8 @@ export class App {
|
||||
present(enteringView: ViewController, opts: NavOptions, appPortal?: number): Promise<any> {
|
||||
const portal = this._appRoot._getPortal(appPortal);
|
||||
|
||||
// Set Nav must be set here in order to dimiss() work synchnously.
|
||||
// TODO: move _setNav() to the earlier stages of NavController. _queueTrns()
|
||||
enteringView._setNav(portal);
|
||||
|
||||
opts.keyboardClose = false;
|
||||
|
@ -255,4 +255,19 @@ export class E2EPage {
|
||||
this.navCtrl.push('page2');
|
||||
}, 500);
|
||||
}
|
||||
|
||||
presentLoadingOpenDismiss() {
|
||||
// debugger;
|
||||
const loading = this.loadingCtrl.create({
|
||||
content: 'Loading 1'
|
||||
});
|
||||
loading.present();
|
||||
loading.dismiss();
|
||||
|
||||
const loading2 = this.loadingCtrl.create({
|
||||
content: 'Loading 2'
|
||||
});
|
||||
loading2.present();
|
||||
loading2.dismiss();
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@
|
||||
<button ion-button block (click)="presentLoadingMultiple()" color="danger">Multiple Loading</button>
|
||||
<button ion-button block (click)="presentLoadingMultipleNav()" color="danger">Multiple Nav Loading</button>
|
||||
<button ion-button block (click)="presentLoadingDismissNav()">Dismiss Page Change</button>
|
||||
<button ion-button block (click)="presentLoadingOpenDismiss()">Open->Dismiss (x2)</button>
|
||||
|
||||
</ion-content>
|
||||
|
||||
|
@ -309,6 +309,23 @@ export class MenuController {
|
||||
removeArrayItem(this._menus, menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_setActiveMenu(menu: Menu) {
|
||||
assert(menu.enabled, 'menu must be enabled');
|
||||
assert(this._menus.indexOf(menu) >= 0, 'menu is not registered');
|
||||
|
||||
// if this menu should be enabled
|
||||
// then find all the other menus on this same side
|
||||
// and automatically disable other same side menus
|
||||
const side = menu.side;
|
||||
this._menus
|
||||
.filter(m => m.side === side && m !== menu)
|
||||
.map(m => m.enable(false));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
|
@ -129,7 +129,7 @@ export class MenuToggle {
|
||||
*/
|
||||
@HostListener('click')
|
||||
toggle() {
|
||||
let menu = this._menu.get(this.menuToggle);
|
||||
const menu = this._menu.get(this.menuToggle);
|
||||
menu && menu.toggle();
|
||||
}
|
||||
|
||||
@ -137,13 +137,17 @@ export class MenuToggle {
|
||||
* @private
|
||||
*/
|
||||
get isHidden() {
|
||||
const menu = this._menu.get(this.menuToggle);
|
||||
if (this._inNavbar && this._viewCtrl) {
|
||||
if (!menu || !menu._canOpen()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this._viewCtrl.isFirst()) {
|
||||
// this is the first view, so it should always show
|
||||
return false;
|
||||
}
|
||||
|
||||
let menu = this._menu.get(this.menuToggle);
|
||||
if (menu) {
|
||||
// this is not the root view, so see if this menu
|
||||
// is configured to still be enabled if it's not the root view
|
||||
|
@ -24,14 +24,14 @@ export class MenuType {
|
||||
}
|
||||
|
||||
setOpen(shouldOpen: boolean, animated: boolean, done: Function) {
|
||||
let ani = this.ani
|
||||
.onFinish(done, true)
|
||||
const ani = this.ani
|
||||
.onFinish(done, true, true)
|
||||
.reverse(!shouldOpen);
|
||||
|
||||
if (animated) {
|
||||
ani.play();
|
||||
} else {
|
||||
ani.play({ duration: 0 });
|
||||
ani.syncPlay();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ChangeDetectionStrategy, Component, ContentChild, ElementRef, EventEmitter, Input, NgZone, Output, Renderer, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, ContentChild, ElementRef, EventEmitter, forwardRef, Input, NgZone, Output, Renderer, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||
|
||||
import { App } from '../app/app';
|
||||
import { Backdrop } from '../backdrop/backdrop';
|
||||
@ -11,8 +11,10 @@ import { Keyboard } from '../../platform/keyboard';
|
||||
import { MenuContentGesture } from './menu-gestures';
|
||||
import { MenuController } from './menu-controller';
|
||||
import { MenuType } from './menu-types';
|
||||
import { Nav } from '../nav/nav';
|
||||
import { Platform } from '../../platform/platform';
|
||||
import { UIEventManager } from '../../gestures/ui-event-manager';
|
||||
import { RootNode } from '../split-pane/split-pane';
|
||||
|
||||
/**
|
||||
* @name Menu
|
||||
@ -188,19 +190,21 @@ import { UIEventManager } from '../../gestures/ui-event-manager';
|
||||
},
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
providers: [{provide: RootNode, useExisting: forwardRef(() => Menu) }]
|
||||
})
|
||||
export class Menu {
|
||||
export class Menu implements RootNode {
|
||||
|
||||
private _cntEle: HTMLElement;
|
||||
private _gesture: MenuContentGesture;
|
||||
private _type: MenuType;
|
||||
private _isEnabled: boolean = true;
|
||||
private _isEnabled: boolean;
|
||||
private _isSwipeEnabled: boolean = true;
|
||||
private _isAnimating: boolean = false;
|
||||
private _isPersistent: boolean = false;
|
||||
private _init: boolean = false;
|
||||
private _events: UIEventManager;
|
||||
private _gestureBlocker: BlockerDelegate;
|
||||
private _isPane: boolean = false;
|
||||
|
||||
/**
|
||||
* @private
|
||||
@ -217,6 +221,11 @@ export class Menu {
|
||||
*/
|
||||
@ContentChild(Content) menuContent: Content;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@ContentChild(Nav) menuNav: Nav;
|
||||
|
||||
/**
|
||||
* @input {any} A reference to the content element the menu should use.
|
||||
*/
|
||||
@ -248,8 +257,8 @@ export class Menu {
|
||||
}
|
||||
|
||||
set enabled(val: boolean) {
|
||||
this._isEnabled = isTrueProperty(val);
|
||||
this._setListeners();
|
||||
const isEnabled = isTrueProperty(val);
|
||||
this.enable(isEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -261,8 +270,8 @@ export class Menu {
|
||||
}
|
||||
|
||||
set swipeEnabled(val: boolean) {
|
||||
this._isSwipeEnabled = isTrueProperty(val);
|
||||
this._setListeners();
|
||||
const isEnabled = isTrueProperty(val);
|
||||
this.swipeEnable(isEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -344,22 +353,22 @@ export class Menu {
|
||||
// add the gestures
|
||||
this._gesture = new MenuContentGesture(this._plt, this, this._gestureCtrl, this._domCtrl);
|
||||
|
||||
// register listeners if this menu is enabled
|
||||
// check if more than one menu is on the same side
|
||||
let hasEnabledSameSideMenu = this._menuCtrl.getMenus().some(m => {
|
||||
return m.side === this.side && m.enabled;
|
||||
});
|
||||
if (hasEnabledSameSideMenu) {
|
||||
// auto-disable if another menu on the same side is already enabled
|
||||
this._isEnabled = false;
|
||||
}
|
||||
this._setListeners();
|
||||
|
||||
// add menu's content classes
|
||||
this._cntEle.classList.add('menu-content');
|
||||
this._cntEle.classList.add('menu-content-' + this.type);
|
||||
|
||||
let isEnabled = this._isEnabled;
|
||||
if (isEnabled === true || typeof isEnabled === 'undefined') {
|
||||
// check if more than one menu is on the same side
|
||||
isEnabled = !this._menuCtrl.getMenus().some(m => {
|
||||
return m.side === this.side && m.enabled;
|
||||
});
|
||||
}
|
||||
// register this menu with the app's menu controller
|
||||
this._menuCtrl._register(this);
|
||||
|
||||
// mask it as enabled / disabled
|
||||
this.enable(isEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -371,27 +380,6 @@ export class Menu {
|
||||
this._menuCtrl.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private _setListeners() {
|
||||
if (!this._init) {
|
||||
return;
|
||||
}
|
||||
const gesture = this._gesture;
|
||||
// only listen/unlisten if the menu has initialized
|
||||
if (this._isEnabled && this._isSwipeEnabled && !gesture.isListening) {
|
||||
// should listen, but is not currently listening
|
||||
console.debug('menu, gesture listen', this.side);
|
||||
gesture.listen();
|
||||
|
||||
} else if (gesture.isListening && (!this._isEnabled || !this._isSwipeEnabled)) {
|
||||
// should not listen, but is currently listening
|
||||
console.debug('menu, gesture unlisten', this.side);
|
||||
gesture.unlisten();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@ -411,13 +399,11 @@ export class Menu {
|
||||
*/
|
||||
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._isEnabled || this._isAnimating) {
|
||||
if ((shouldOpen === this.isOpen) || !this._canOpen() || this._isAnimating) {
|
||||
return Promise.resolve(this.isOpen);
|
||||
}
|
||||
|
||||
this._before();
|
||||
|
||||
return new Promise(resolve => {
|
||||
this._before();
|
||||
this._getType().setOpen(shouldOpen, animated, () => {
|
||||
this._after(shouldOpen);
|
||||
resolve(this.isOpen);
|
||||
@ -425,13 +411,21 @@ export class Menu {
|
||||
});
|
||||
}
|
||||
|
||||
_forceClosing() {
|
||||
assert(this.isOpen, 'menu cannot be closed');
|
||||
this._isAnimating = true;
|
||||
this._getType().setOpen(false, false, () => {
|
||||
this._after(false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
canSwipe(): boolean {
|
||||
return this._isEnabled &&
|
||||
this._isSwipeEnabled &&
|
||||
return this._isSwipeEnabled &&
|
||||
!this._isAnimating &&
|
||||
this._canOpen() &&
|
||||
this._app.isEnabled();
|
||||
}
|
||||
|
||||
@ -442,6 +436,7 @@ export class Menu {
|
||||
return this._isAnimating;
|
||||
}
|
||||
|
||||
|
||||
_swipeBeforeStart() {
|
||||
if (!this.canSwipe()) {
|
||||
assert(false, 'canSwipe() has to be true');
|
||||
@ -500,7 +495,7 @@ export class Menu {
|
||||
// this css class doesn't actually kick off any animations
|
||||
this.setElementClass('show-menu', true);
|
||||
this.backdrop.setElementClass('show-backdrop', true);
|
||||
this.menuContent && this.menuContent.resize();
|
||||
this.resize();
|
||||
this._keyboard.close();
|
||||
this._isAnimating = true;
|
||||
}
|
||||
@ -554,6 +549,16 @@ export class Menu {
|
||||
return this.setOpen(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
resize() {
|
||||
const content: Content | Nav = this.menuContent
|
||||
? this.menuContent
|
||||
: this.menuNav;
|
||||
content && content.resize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@ -561,38 +566,81 @@ export class Menu {
|
||||
return this.setOpen(!this.isOpen);
|
||||
}
|
||||
|
||||
_canOpen(): boolean {
|
||||
return this._isEnabled && !this._isPane;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_updateState() {
|
||||
const canOpen = this._canOpen();
|
||||
|
||||
// Close menu inmediately
|
||||
if (!canOpen && this.isOpen) {
|
||||
assert(this._init, 'menu must be initialized');
|
||||
// close if this menu is open, and should not be enabled
|
||||
this._forceClosing();
|
||||
}
|
||||
|
||||
if (this._isEnabled && this._menuCtrl) {
|
||||
this._menuCtrl._setActiveMenu(this);
|
||||
}
|
||||
|
||||
if (!this._init) {
|
||||
return;
|
||||
}
|
||||
|
||||
const gesture = this._gesture;
|
||||
// only listen/unlisten if the menu has initialized
|
||||
if (canOpen && this._isSwipeEnabled && !gesture.isListening) {
|
||||
// should listen, but is not currently listening
|
||||
console.debug('menu, gesture listen', this.side);
|
||||
gesture.listen();
|
||||
|
||||
} else if (gesture.isListening && (!canOpen || !this._isSwipeEnabled)) {
|
||||
// should not listen, but is currently listening
|
||||
console.debug('menu, gesture unlisten', this.side);
|
||||
gesture.unlisten();
|
||||
}
|
||||
|
||||
if (this.isOpen || (this._isPane && this._isEnabled)) {
|
||||
this.resize();
|
||||
}
|
||||
assert(!this._isAnimating, 'can not be animating');
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
enable(shouldEnable: boolean): Menu {
|
||||
this.enabled = shouldEnable;
|
||||
if (!shouldEnable && this.isOpen) {
|
||||
// close if this menu is open, and should not be enabled
|
||||
this.close();
|
||||
}
|
||||
|
||||
if (shouldEnable) {
|
||||
// if this menu should be enabled
|
||||
// then find all the other menus on this same side
|
||||
// and automatically disable other same side menus
|
||||
this._menuCtrl.getMenus()
|
||||
.filter(m => m.side === this.side && m !== this)
|
||||
.map(m => m.enabled = false);
|
||||
}
|
||||
|
||||
// TODO
|
||||
// what happens if menu is disabled while swipping?
|
||||
|
||||
this._isEnabled = shouldEnable;
|
||||
this.setElementClass('menu-enabled', shouldEnable);
|
||||
this._updateState();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
initPane(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
paneChanged(isPane: boolean) {
|
||||
this._isPane = isPane;
|
||||
this._updateState();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
swipeEnable(shouldEnable: boolean): Menu {
|
||||
this.swipeEnabled = shouldEnable;
|
||||
// TODO
|
||||
// what happens if menu swipe is disabled while swipping?
|
||||
this._isSwipeEnabled = shouldEnable;
|
||||
this._updateState();
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -652,6 +700,13 @@ export class Menu {
|
||||
this._renderer.setElementAttribute(this._elementRef.nativeElement, attributeName, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
getElementRef(): ElementRef {
|
||||
return this._elementRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
|
@ -38,17 +38,11 @@ export class E2EApp {
|
||||
|
||||
menu1Active() {
|
||||
this.menuCtrl.enable(true, 'menu1');
|
||||
this.menuCtrl.enable(false, 'menu2');
|
||||
this.menuCtrl.enable(false, 'menu3');
|
||||
}
|
||||
menu2Active() {
|
||||
this.menuCtrl.enable(false, 'menu1');
|
||||
this.menuCtrl.enable(true, 'menu2');
|
||||
this.menuCtrl.enable(false, 'menu3');
|
||||
}
|
||||
menu3Active() {
|
||||
this.menuCtrl.enable(false, 'menu1');
|
||||
this.menuCtrl.enable(false, 'menu2');
|
||||
this.menuCtrl.enable(true, 'menu3');
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,10 @@ import { IonicApp, IonicModule, MenuController } from '../../../..';
|
||||
templateUrl: 'page1.html'
|
||||
})
|
||||
export class Page1 {
|
||||
activeMenu: string;
|
||||
activeMenu: string = 'none';
|
||||
|
||||
constructor(private menu: MenuController) { }
|
||||
|
||||
constructor(private menu: MenuController) {
|
||||
this.menu1Active();
|
||||
}
|
||||
menu1Active() {
|
||||
this.activeMenu = 'menu1';
|
||||
this.menu.enable(true, 'menu1');
|
||||
|
@ -1,4 +1,4 @@
|
||||
<ion-menu [content]="content" id="menu1">
|
||||
<ion-menu [content]="content" id="menu1" enabled="false">
|
||||
|
||||
<ion-header>
|
||||
<ion-toolbar color="secondary">
|
||||
@ -17,7 +17,7 @@
|
||||
</ion-menu>
|
||||
|
||||
|
||||
<ion-menu [content]="content" id="menu2">
|
||||
<ion-menu [content]="content" id="menu2" enabled="false">
|
||||
|
||||
<ion-header>
|
||||
<ion-toolbar color="danger">
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { AfterViewInit, Component, ComponentFactoryResolver, ElementRef, Input, Optional, NgZone, Renderer, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
|
||||
import { AfterViewInit, Component, ComponentFactoryResolver, ElementRef, forwardRef, Input, Optional, NgZone, Renderer, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
|
||||
|
||||
import { App } from '../app/app';
|
||||
import { Config } from '../../config/config';
|
||||
@ -13,6 +13,7 @@ import { NavOptions } from '../../navigation/nav-util';
|
||||
import { Platform } from '../../platform/platform';
|
||||
import { TransitionController } from '../../transitions/transition-controller';
|
||||
import { ViewController } from '../../navigation/view-controller';
|
||||
import { RootNode } from '../split-pane/split-pane';
|
||||
|
||||
/**
|
||||
* @name Nav
|
||||
@ -52,8 +53,9 @@ import { ViewController } from '../../navigation/view-controller';
|
||||
'<div #viewport nav-viewport></div>' +
|
||||
'<div class="nav-decor"></div>',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
providers: [{provide: RootNode, useExisting: forwardRef(() => Nav) }]
|
||||
})
|
||||
export class Nav extends NavControllerBase implements AfterViewInit {
|
||||
export class Nav extends NavControllerBase implements AfterViewInit, RootNode {
|
||||
private _root: any;
|
||||
private _hasInit: boolean = false;
|
||||
|
||||
@ -162,8 +164,19 @@ export class Nav extends NavControllerBase implements AfterViewInit {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
destroy() {
|
||||
ngOnDestroy() {
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
initPane(): boolean {
|
||||
const isMain = this._elementRef.nativeElement.hasAttribute('main');
|
||||
return isMain;
|
||||
}
|
||||
|
||||
paneChanged(isPane: boolean) {
|
||||
if (isPane) {
|
||||
this.resize();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -49,5 +49,9 @@ export class OverlayPortal extends NavControllerBase {
|
||||
this._zIndexOffset = (val || 0);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -62,6 +62,11 @@
|
||||
<div>Aenean rhoncus urna at interdum blandit. Donec ac massa nec libero vehicula tincidunt. Sed sit amet hendrerit risus. Aliquam vitae vestibulum ipsum, non feugiat orci. Vivamus eu rutrum elit. Nulla dapibus tortor non dignissim pretium. Nulla in luctus turpis. Etiam non mattis tortor, at aliquet ex. Nunc ut ante varius, auctor dui vel, volutpat elit. Nunc laoreet augue sit amet ultrices porta. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum pellentesque lobortis est, ut tincidunt ligula mollis sit amet. In porta risus arcu, quis pellentesque dolor mattis non. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;</div>
|
||||
</div>
|
||||
|
||||
|
||||
<button ion-button block color="secondary" (click)="presentToast()">
|
||||
Open Toast
|
||||
</button>
|
||||
|
||||
</ion-content>
|
||||
|
||||
|
||||
|
@ -477,7 +477,7 @@ export class Refresher {
|
||||
}
|
||||
|
||||
_setListeners(shouldListen: boolean) {
|
||||
this._events.destroy();
|
||||
this._events.unlistenAll();
|
||||
this._pointerEvents = null;
|
||||
if (shouldListen) {
|
||||
this._pointerEvents = this._events.pointerEvents({
|
||||
@ -503,9 +503,9 @@ export class Refresher {
|
||||
* @private
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
this._setListeners(false);
|
||||
this._events.destroy();
|
||||
this._gesture.destroy();
|
||||
this._setListeners(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -890,7 +890,14 @@ export class Slides extends Ion {
|
||||
|
||||
|
||||
|
||||
constructor(config: Config, private _plt: Platform, zone: NgZone, @Optional() viewCtrl: ViewController, elementRef: ElementRef, renderer: Renderer) {
|
||||
constructor(
|
||||
config: Config,
|
||||
private _plt: Platform,
|
||||
zone: NgZone,
|
||||
@Optional() viewCtrl: ViewController,
|
||||
elementRef: ElementRef,
|
||||
renderer: Renderer,
|
||||
) {
|
||||
super(config, elementRef, renderer, 'slides');
|
||||
|
||||
this._zone = zone;
|
||||
@ -964,6 +971,12 @@ export class Slides extends Ion {
|
||||
}
|
||||
}
|
||||
|
||||
resize() {
|
||||
if (this._init) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition to the specified slide.
|
||||
*
|
||||
|
@ -85,7 +85,6 @@ export function initEvents(s: Slides, plt: Platform): Function {
|
||||
|
||||
// onresize
|
||||
let resizeObs = plt.resize.subscribe(() => onResize(s, plt, false));
|
||||
|
||||
// Next, Prev, Index
|
||||
if (s.nextButton) {
|
||||
plt.registerListener(s.nextButton, 'click', (ev) => {
|
||||
@ -817,7 +816,18 @@ function onTouchEnd(s: Slides, plt: Platform, ev: SlideUIEvent) {
|
||||
/*=========================
|
||||
Resize Handler
|
||||
===========================*/
|
||||
let resizeId: number;
|
||||
function onResize(s: Slides, plt: Platform, forceUpdatePagination: boolean) {
|
||||
// TODO: hacky, we should use Resize Observer in the future
|
||||
if (resizeId) {
|
||||
plt.cancelTimeout(resizeId);
|
||||
resizeId = null;
|
||||
}
|
||||
resizeId = plt.timeout(() => doResize(s, plt, forceUpdatePagination), 200);
|
||||
}
|
||||
|
||||
function doResize(s: Slides, plt: Platform, forceUpdatePagination: boolean) {
|
||||
resizeId = null;
|
||||
// Disable locks on resize
|
||||
var allowSwipeToPrev = s._allowSwipeToPrev;
|
||||
var allowSwipeToNext = s._allowSwipeToNext;
|
||||
|
12
src/components/split-pane/split-pane.ios.scss
Normal file
12
src/components/split-pane/split-pane.ios.scss
Normal file
@ -0,0 +1,12 @@
|
||||
|
||||
@import "../../themes/ionic.globals.ios";
|
||||
|
||||
// Split Pane
|
||||
// --------------------------------------------------
|
||||
|
||||
.split-pane-ios.split-pane-visible >.split-pane-side {
|
||||
min-width: 270px;
|
||||
max-width: 28%;
|
||||
|
||||
border-right: $hairlines-width solid $list-ios-border-color;
|
||||
}
|
13
src/components/split-pane/split-pane.md.scss
Normal file
13
src/components/split-pane/split-pane.md.scss
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
@import "../../themes/ionic.globals.md";
|
||||
|
||||
// Split Pane
|
||||
// --------------------------------------------------
|
||||
|
||||
.split-pane-md.split-pane-visible >.split-pane-side {
|
||||
min-width: 270px;
|
||||
max-width: 28%;
|
||||
|
||||
border-right: 1px solid $list-md-border-color;
|
||||
}
|
||||
|
74
src/components/split-pane/split-pane.scss
Normal file
74
src/components/split-pane/split-pane.scss
Normal file
@ -0,0 +1,74 @@
|
||||
|
||||
@import "../../themes/ionic.globals";
|
||||
|
||||
// Split Pane
|
||||
// --------------------------------------------------
|
||||
|
||||
ion-split-pane {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
|
||||
display: flex;
|
||||
|
||||
flex-wrap: nowrap;
|
||||
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
.split-pane-side:not(ion-menu) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.split-pane-visible >.split-pane-side,
|
||||
.split-pane-visible >.split-pane-main {
|
||||
// scss-lint:disable ImportantRule
|
||||
position: relative;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 0;
|
||||
|
||||
flex: 1;
|
||||
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.split-pane-visible >.split-pane-side {
|
||||
flex-shrink: 0;
|
||||
|
||||
order: -1;
|
||||
}
|
||||
|
||||
.split-pane-visible >.split-pane-main,
|
||||
.split-pane-visible >ion-nav.split-pane-side,
|
||||
.split-pane-visible >ion-tabs.split-pane-side,
|
||||
.split-pane-visible >ion-menu.menu-enabled {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.split-pane-visible >ion-split-pane.split-pane-side,
|
||||
.split-pane-visible >ion-split-pane.split-pane-main {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.split-pane-visible >ion-menu.menu-enabled {
|
||||
>.menu-inner {
|
||||
// scss-lint:disable ImportantRule
|
||||
right: 0;
|
||||
left: 0;
|
||||
|
||||
width: auto;
|
||||
|
||||
box-shadow: none !important;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
>.ion-backdrop {
|
||||
// scss-lint:disable ImportantRule
|
||||
display: hidden !important;
|
||||
}
|
||||
}
|
332
src/components/split-pane/split-pane.ts
Normal file
332
src/components/split-pane/split-pane.ts
Normal file
@ -0,0 +1,332 @@
|
||||
import { ContentChildren, Directive, ElementRef, EventEmitter, forwardRef, Input, Output, QueryList, NgZone, Renderer } from '@angular/core';
|
||||
import { Ion } from '../ion';
|
||||
import { assert } from '../../util/util';
|
||||
import { Config } from '../../config/config';
|
||||
import { Platform } from '../../platform/platform';
|
||||
|
||||
const QUERY: { [key: string]: string } = {
|
||||
xs: '(min-width: 0px)',
|
||||
sm: '(min-width: 576px)',
|
||||
md: '(min-width: 768px)',
|
||||
lg: '(min-width: 992px)',
|
||||
xl: '(min-width: 1200px)',
|
||||
never: ''
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export abstract class RootNode {
|
||||
abstract getElementRef(): ElementRef;
|
||||
abstract initPane(): boolean;
|
||||
abstract paneChanged?(visible: boolean): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name SplitPane
|
||||
*
|
||||
* @description
|
||||
* SplitPane is a component that makes it possible to create multi-view layout.
|
||||
* Similar to iPad apps, SplitPane allows UI elements, like Menus, to be
|
||||
* displayed as the viewport increases.
|
||||
*
|
||||
* If the devices screen size is below a certain size, the SplitPane will
|
||||
* collapse and the menu will become hidden again. This is especially useful when
|
||||
* creating an app that will be served over a browser or deployed through the app
|
||||
* store to phones and tablets.
|
||||
*
|
||||
* @usage
|
||||
* To use SplitPane, simply add the component around your root component.
|
||||
* In this example, we'll be using a sidemenu layout, similar to what is
|
||||
* provided from the sidemenu starter template.
|
||||
*
|
||||
* ```html
|
||||
* <ion-split-pane>
|
||||
* <!-- our side menu -->
|
||||
* <ion-menu [content]="content">
|
||||
* <ion-header>
|
||||
* <ion-toolbar>
|
||||
* <ion-title>Menu</ion-title>
|
||||
* </ion-toolbar>
|
||||
* </ion-header>
|
||||
* </ion-menu>
|
||||
*
|
||||
* <!-- the main content -->
|
||||
* <ion-nav [root]="root" main #content></ion-nav>
|
||||
* </ion-split-pane>
|
||||
* ```
|
||||
*
|
||||
* Here, SplitPane will look for the element with the `main` attribute and make
|
||||
* that the central component on larger screens. The `main` component can be any
|
||||
* Ionic component (`ion-nav` or `ion-tabs`) except `ion-menu`.
|
||||
*
|
||||
* ### Setting breakpoints
|
||||
*
|
||||
* By default, SplitPane will expand when the screen is larger than 768px.
|
||||
* If you want to customize this, use the `when` input. The `when` input can
|
||||
* accept any valid media query, as it uses `matchMedia()` underneath.
|
||||
*
|
||||
* ```
|
||||
* <ion-split-pane when="(min-width: 475px)">
|
||||
*
|
||||
* <!-- our side menu -->
|
||||
* <ion-menu [content]="content">
|
||||
* ....
|
||||
* </ion-menu>
|
||||
*
|
||||
* <!-- the main content -->
|
||||
* <ion-nav [root]="root" main #content></ion-nav>
|
||||
* </ion-split-pane>
|
||||
* ```
|
||||
*
|
||||
* SplitPane also provides some predefined media queries that can be used.
|
||||
*
|
||||
* ```html
|
||||
* <!-- could be "xs", "sm", "md", "lg", or "xl" -->
|
||||
* <ion-split-pane when="lg">
|
||||
* ...
|
||||
* </ion-split-pane>
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* | Size | Value | Description |
|
||||
* |------|-----------------------|-----------------------------------------------------------------------|
|
||||
* | `xs` | `(min-width: 0px)` | Show the split-pane when the min-width is 0px (meaning, always) |
|
||||
* | `sm` | `(min-width: 576px)` | Show the split-pane when the min-width is 576px |
|
||||
* | `md` | `(min-width: 768px)` | Show the split-pane when the min-width is 768px (default break point) |
|
||||
* | `lg` | `(min-width: 992px)` | Show the split-pane when the min-width is 992px |
|
||||
* | `xl` | `(min-width: 1200px)` | Show the split-pane when the min-width is 1200px |
|
||||
*
|
||||
* You can also pass in boolean values that will trigger SplitPane when the value
|
||||
* or expression evaluates to true.
|
||||
*
|
||||
*
|
||||
* ```html
|
||||
* <ion-split-pane [when]="isLarge">
|
||||
* ...
|
||||
* </ion-split-pane>
|
||||
* ```
|
||||
*
|
||||
* ```ts
|
||||
* class MyClass {
|
||||
* public isLarge = false;
|
||||
* constructor(){}
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Or
|
||||
*
|
||||
* ```html
|
||||
* <ion-split-pane [when]="shouldShow()">
|
||||
* ...
|
||||
* </ion-split-pane>
|
||||
* ```
|
||||
*
|
||||
* ```ts
|
||||
* class MyClass {
|
||||
* constructor(){}
|
||||
* shouldShow(){
|
||||
* if(conditionA){
|
||||
* return true
|
||||
* } else {
|
||||
* return false
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
@Directive({
|
||||
selector: 'ion-split-pane',
|
||||
providers: [{provide: RootNode, useExisting: forwardRef(() => SplitPane) }]
|
||||
})
|
||||
export class SplitPane extends Ion implements RootNode {
|
||||
|
||||
_rmListener: any;
|
||||
_visible: boolean = false;
|
||||
_init: boolean = false;
|
||||
_mediaQuery: string | boolean = QUERY['md'];
|
||||
_children: RootNode[];
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
sideContent: RootNode = null;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
mainContent: RootNode = null;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@ContentChildren(RootNode, { descendants: false })
|
||||
set _setchildren(query: QueryList<RootNode>) {
|
||||
const children = this._children = query.filter((child => child !== this));
|
||||
children.forEach(child => {
|
||||
var isMain = child.initPane();
|
||||
this._setPaneCSSClass(child.getElementRef(), isMain);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @input {string | boolean} When the split-pane should be shown.
|
||||
* Can be a CSS media query expression, or a shortcut expression.
|
||||
* Can aslo be a boolean expression.
|
||||
*/
|
||||
@Input()
|
||||
set when(query: string | boolean) {
|
||||
if (typeof query === 'boolean') {
|
||||
this._mediaQuery = query;
|
||||
} else {
|
||||
const defaultQuery = QUERY[query];
|
||||
this._mediaQuery = (defaultQuery)
|
||||
? defaultQuery
|
||||
: query;
|
||||
}
|
||||
this._update();
|
||||
}
|
||||
get when(): string | boolean {
|
||||
return this._mediaQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* @output {any} Expression to be called when the split-pane visibility has changed
|
||||
*/
|
||||
@Output() ionChange: EventEmitter<SplitPane> = new EventEmitter<SplitPane>();
|
||||
|
||||
constructor(
|
||||
private _zone: NgZone,
|
||||
private _plt: Platform,
|
||||
config: Config,
|
||||
elementRef: ElementRef,
|
||||
renderer: Renderer,
|
||||
) {
|
||||
super(config, elementRef, renderer, 'split-pane');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_register(node: RootNode, isMain: boolean, callback: Function): boolean {
|
||||
if (this.getElementRef().nativeElement !== node.getElementRef().nativeElement.parentNode) {
|
||||
return false;
|
||||
}
|
||||
this._setPaneCSSClass(node.getElementRef(), isMain);
|
||||
if (callback) {
|
||||
this.ionChange.subscribe(callback);
|
||||
}
|
||||
if (isMain) {
|
||||
if (this.mainContent) {
|
||||
console.error('split pane: main content was already set');
|
||||
}
|
||||
this.mainContent = node;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ngAfterViewInit() {
|
||||
this._init = true;
|
||||
this._update();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_update() {
|
||||
if (!this._init) {
|
||||
return;
|
||||
}
|
||||
// Unlisten
|
||||
this._rmListener && this._rmListener();
|
||||
this._rmListener = null;
|
||||
|
||||
const query = this._mediaQuery;
|
||||
if (typeof query === 'boolean') {
|
||||
this._setVisible(query);
|
||||
return;
|
||||
}
|
||||
if (query && query.length > 0) {
|
||||
// Listen
|
||||
const callback = (query: MediaQueryList) => this._setVisible(query.matches);
|
||||
const mediaList = this._plt.win().matchMedia(query);
|
||||
mediaList.addListener(callback);
|
||||
this._setVisible(mediaList.matches);
|
||||
this._rmListener = function () {
|
||||
mediaList.removeListener(callback);
|
||||
};
|
||||
} else {
|
||||
this._setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_updateChildren() {
|
||||
this.mainContent = null;
|
||||
this.sideContent = null;
|
||||
const visible = this._visible;
|
||||
this._children.forEach(child => child.paneChanged && child.paneChanged(visible));
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_setVisible(visible: boolean) {
|
||||
if (this._visible === visible) {
|
||||
return;
|
||||
}
|
||||
this._visible = visible;
|
||||
this.setElementClass('split-pane-visible', visible);
|
||||
this._updateChildren();
|
||||
this._zone.run(() => {
|
||||
this.ionChange.emit(this);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
isVisible(): boolean {
|
||||
return this._visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setElementClass(className: string, add: boolean) {
|
||||
this._renderer.setElementClass(this._elementRef.nativeElement, className, add);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_setPaneCSSClass(elementRef: ElementRef, isMain: boolean) {
|
||||
const ele = elementRef.nativeElement;
|
||||
this._renderer.setElementClass(ele, 'split-pane-main', isMain);
|
||||
this._renderer.setElementClass(ele, 'split-pane-side', !isMain);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
assert(this._rmListener, 'at this point _rmListerner should be valid');
|
||||
|
||||
this._rmListener && this._rmListener();
|
||||
this._rmListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
initPane(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
12
src/components/split-pane/split-pane.wp.scss
Normal file
12
src/components/split-pane/split-pane.wp.scss
Normal file
@ -0,0 +1,12 @@
|
||||
|
||||
@import "../../themes/ionic.globals.wp";
|
||||
|
||||
// Split Pane
|
||||
// --------------------------------------------------
|
||||
|
||||
.split-pane-wp.split-pane-visible >.split-pane-side {
|
||||
min-width: 270px;
|
||||
max-width: 28%;
|
||||
|
||||
border-right: 1px solid $list-wp-border-color;
|
||||
}
|
120
src/components/split-pane/test/basic/app.module.ts
Normal file
120
src/components/split-pane/test/basic/app.module.ts
Normal file
@ -0,0 +1,120 @@
|
||||
import { Component, NgModule } from '@angular/core';
|
||||
import { IonicApp, IonicModule, NavController, MenuController, SplitPane } from '../../../../../ionic-angular';
|
||||
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ion-header>
|
||||
<ion-navbar><ion-title>Navigation</ion-title></ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-list>
|
||||
<ion-item>Hola 1</ion-item>
|
||||
<ion-item>Hola 2</ion-item>
|
||||
<ion-item>Hola 3</ion-item>
|
||||
<button ion-item (click)="push()">Push</button>
|
||||
<ion-item>Hola</ion-item>
|
||||
<ion-item>Hola</ion-item>
|
||||
<ion-item>Hola</ion-item>
|
||||
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
`
|
||||
})
|
||||
export class SidePage {
|
||||
constructor(public navCtrl: NavController) { }
|
||||
push() {
|
||||
this.navCtrl.push(SidePage);
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ion-header>
|
||||
<ion-navbar>
|
||||
<button ion-button menuToggle>
|
||||
<ion-icon name="menu"></ion-icon>
|
||||
</button>
|
||||
<ion-title>Page 2</ion-title>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<h1>Page 2</h1>
|
||||
</ion-content>
|
||||
`
|
||||
})
|
||||
export class E2EPage2 {}
|
||||
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ion-header>
|
||||
<ion-navbar>
|
||||
<button ion-button menuToggle>
|
||||
<ion-icon name="menu"></ion-icon>
|
||||
</button>
|
||||
<ion-title>Navigation</ion-title>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<h1>Page 1</h1>
|
||||
<button ion-button (click)="push()">Push</button>
|
||||
<button ion-button (click)="menu()">Disable/enable menu</button>
|
||||
<div f></div>
|
||||
<div f></div>
|
||||
<div f></div>
|
||||
<div f></div>
|
||||
|
||||
</ion-content>
|
||||
`
|
||||
})
|
||||
export class E2EPage {
|
||||
constructor(
|
||||
public navCtrl: NavController,
|
||||
public menuCtrl: MenuController,
|
||||
) { }
|
||||
|
||||
push() {
|
||||
this.navCtrl.push(E2EPage2);
|
||||
}
|
||||
|
||||
menu() {
|
||||
this.menuCtrl.enable(!this.menuCtrl.isEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
templateUrl: 'main.html'
|
||||
})
|
||||
export class E2EApp {
|
||||
root = E2EPage;
|
||||
root2 = SidePage;
|
||||
|
||||
splitPaneChanged(splitPane: SplitPane) {
|
||||
console.log('Split pane changed, visible: ', splitPane.isVisible());
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
E2EApp,
|
||||
E2EPage,
|
||||
E2EPage2,
|
||||
SidePage,
|
||||
],
|
||||
imports: [
|
||||
IonicModule.forRoot(E2EApp, {
|
||||
swipeBackEnabled: true
|
||||
})
|
||||
],
|
||||
bootstrap: [IonicApp],
|
||||
entryComponents: [
|
||||
E2EApp,
|
||||
E2EPage,
|
||||
E2EPage2,
|
||||
SidePage,
|
||||
]
|
||||
})
|
||||
export class AppModule {}
|
||||
|
1
src/components/split-pane/test/basic/e2e.ts
Normal file
1
src/components/split-pane/test/basic/e2e.ts
Normal file
@ -0,0 +1 @@
|
||||
|
7
src/components/split-pane/test/basic/main.html
Normal file
7
src/components/split-pane/test/basic/main.html
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
<ion-split-pane (ionChange)="splitPaneChanged($event)">
|
||||
<ion-menu [content]="content">
|
||||
<ion-nav [root]="root2"></ion-nav>
|
||||
</ion-menu>
|
||||
<ion-nav [root]="root" main #content></ion-nav>
|
||||
</ion-split-pane>
|
102
src/components/split-pane/test/menus/app.module.ts
Normal file
102
src/components/split-pane/test/menus/app.module.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import { Component, NgModule } from '@angular/core';
|
||||
import { IonicApp, IonicModule, NavController, MenuController, SplitPane } from '../../../../../ionic-angular';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ion-header>
|
||||
<ion-navbar>
|
||||
<button ion-button menuToggle>
|
||||
<ion-icon name="menu"></ion-icon>
|
||||
</button>
|
||||
<ion-title>Page 2</ion-title>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<h1>Page 2</h1>
|
||||
</ion-content>
|
||||
`
|
||||
})
|
||||
export class E2EPage2 {}
|
||||
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ion-header>
|
||||
<ion-navbar>
|
||||
<button ion-button menuToggle>
|
||||
<ion-icon name="menu"></ion-icon>
|
||||
</button>
|
||||
<ion-title>Navigation</ion-title>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<h1>Page 1</h1>
|
||||
<button ion-button (click)="push()">Push</button>
|
||||
<button ion-button (click)="menu1Active()">Enable menu 1</button>
|
||||
<button ion-button (click)="menu2Active()">Enable menu 2</button>
|
||||
<button ion-button (click)="menu3Active()">Enable menu 3</button>
|
||||
<button ion-button (click)="disableAll()">Disable all</button>
|
||||
|
||||
<div f></div>
|
||||
<div f></div>
|
||||
<div f></div>
|
||||
<div f></div>
|
||||
|
||||
</ion-content>
|
||||
`
|
||||
})
|
||||
export class E2EPage {
|
||||
constructor(
|
||||
public navCtrl: NavController,
|
||||
public menuCtrl: MenuController,
|
||||
) { }
|
||||
|
||||
push() {
|
||||
this.navCtrl.push(E2EPage2);
|
||||
}
|
||||
menu1Active() {
|
||||
this.menuCtrl.enable(true, 'menu1');
|
||||
}
|
||||
menu2Active() {
|
||||
this.menuCtrl.enable(true, 'menu2');
|
||||
}
|
||||
menu3Active() {
|
||||
this.menuCtrl.enable(true, 'menu3');
|
||||
}
|
||||
disableAll() {
|
||||
this.menuCtrl.enable(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
templateUrl: 'main.html'
|
||||
})
|
||||
export class E2EApp {
|
||||
root = E2EPage;
|
||||
|
||||
splitPaneChanged(splitPane: SplitPane) {
|
||||
console.log('Split pane changed, visible: ', splitPane.isVisible());
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
E2EApp,
|
||||
E2EPage,
|
||||
E2EPage2,
|
||||
],
|
||||
imports: [
|
||||
IonicModule.forRoot(E2EApp, {
|
||||
swipeBackEnabled: true
|
||||
})
|
||||
],
|
||||
bootstrap: [IonicApp],
|
||||
entryComponents: [
|
||||
E2EApp,
|
||||
E2EPage,
|
||||
E2EPage2,
|
||||
]
|
||||
})
|
||||
export class AppModule {}
|
||||
|
1
src/components/split-pane/test/menus/e2e.ts
Normal file
1
src/components/split-pane/test/menus/e2e.ts
Normal file
@ -0,0 +1 @@
|
||||
|
52
src/components/split-pane/test/menus/main.html
Normal file
52
src/components/split-pane/test/menus/main.html
Normal file
@ -0,0 +1,52 @@
|
||||
|
||||
<ion-split-pane (ionChange)="splitPaneChanged($event)">
|
||||
<ion-menu [content]="content" id="menu1">
|
||||
|
||||
<ion-header>
|
||||
<ion-toolbar color="secondary">
|
||||
<ion-title>Menu 1</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<ion-list>
|
||||
<ion-item>Example</ion-item>
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
|
||||
</ion-menu>
|
||||
|
||||
|
||||
<ion-menu [content]="content" id="menu2">
|
||||
|
||||
<ion-header>
|
||||
<ion-toolbar color="danger">
|
||||
<ion-title>Menu 2</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<ion-list>
|
||||
<ion-item>Example</ion-item>
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
|
||||
</ion-menu>
|
||||
|
||||
<ion-menu [content]="content" id="menu3">
|
||||
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary">
|
||||
<ion-title>Menu 3</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<ion-list>
|
||||
<ion-item>Example</ion-item>
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
</ion-menu>
|
||||
|
||||
<ion-nav [root]="root" main #content></ion-nav>
|
||||
</ion-split-pane>
|
162
src/components/split-pane/test/nested/app.module.ts
Normal file
162
src/components/split-pane/test/nested/app.module.ts
Normal file
@ -0,0 +1,162 @@
|
||||
import { Component, NgModule } from '@angular/core';
|
||||
import { IonicApp, IonicModule, NavController } from '../../../../../ionic-angular';
|
||||
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ion-header>
|
||||
<ion-navbar><ion-title>Nested 1</ion-title></ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<button ion-button (click)="push()">Push</button>
|
||||
<div f></div>
|
||||
<div f></div>
|
||||
<div f></div>
|
||||
<div f></div>
|
||||
</ion-content>
|
||||
`
|
||||
})
|
||||
export class E2ENested {
|
||||
constructor(
|
||||
public navCtrl: NavController,
|
||||
) { }
|
||||
|
||||
push() {
|
||||
this.navCtrl.push(E2ENested);
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ion-header>
|
||||
<ion-navbar><ion-title>Nested 2</ion-title></ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<button ion-button (click)="push()">Push</button>
|
||||
<div f></div>
|
||||
<div f></div>
|
||||
<div f></div>
|
||||
<div f></div>
|
||||
</ion-content>
|
||||
`
|
||||
})
|
||||
export class E2ENested2 {
|
||||
constructor(
|
||||
public navCtrl: NavController,
|
||||
) { }
|
||||
|
||||
push() {
|
||||
this.navCtrl.push(E2ENested2);
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ion-header>
|
||||
<ion-navbar>
|
||||
<button ion-button menuToggle>
|
||||
<ion-icon name="menu"></ion-icon>
|
||||
</button>
|
||||
<ion-title>Nested 3</ion-title>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<button ion-button (click)="push()">Push</button>
|
||||
<div f></div>
|
||||
<div f></div>
|
||||
<div f></div>
|
||||
<div f></div>
|
||||
</ion-content>
|
||||
`
|
||||
})
|
||||
export class E2ENested3 {
|
||||
constructor(
|
||||
public navCtrl: NavController,
|
||||
) { }
|
||||
|
||||
push() {
|
||||
this.navCtrl.push(E2ENested3);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ion-header>
|
||||
<ion-navbar><ion-title>Navigation</ion-title></ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-list>
|
||||
<ion-item>Hola</ion-item>
|
||||
<ion-item>Hola</ion-item>
|
||||
<ion-item>Hola</ion-item>
|
||||
<button ion-item (click)="push()">Push</button>
|
||||
<ion-item>Hola</ion-item>
|
||||
<ion-item>Hola</ion-item>
|
||||
<ion-item>Hola</ion-item>
|
||||
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
`
|
||||
})
|
||||
export class SidePage {
|
||||
constructor(public navCtrl: NavController) { }
|
||||
push() {
|
||||
this.navCtrl.push(SidePage);
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ion-split-pane>
|
||||
<ion-nav [root]="root"></ion-nav>
|
||||
|
||||
<ion-split-pane when="lg" main >
|
||||
<ion-nav [root]="root2"></ion-nav>
|
||||
<ion-nav [root]="root3" main ></ion-nav>
|
||||
</ion-split-pane>
|
||||
|
||||
</ion-split-pane>
|
||||
`
|
||||
})
|
||||
export class E2EPage {
|
||||
root = E2ENested;
|
||||
root2 = E2ENested2;
|
||||
root3 = E2ENested3;
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
templateUrl: 'main.html'
|
||||
})
|
||||
export class E2EApp {
|
||||
root = E2EPage;
|
||||
root2 = SidePage;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
E2EApp,
|
||||
E2EPage,
|
||||
SidePage,
|
||||
E2ENested,
|
||||
E2ENested2,
|
||||
E2ENested3
|
||||
],
|
||||
imports: [
|
||||
IonicModule.forRoot(E2EApp, {
|
||||
swipeBackEnabled: true
|
||||
})
|
||||
],
|
||||
bootstrap: [IonicApp],
|
||||
entryComponents: [
|
||||
E2EApp,
|
||||
E2EPage,
|
||||
SidePage,
|
||||
E2ENested,
|
||||
E2ENested2,
|
||||
E2ENested3
|
||||
]
|
||||
})
|
||||
export class AppModule {}
|
||||
|
1
src/components/split-pane/test/nested/e2e.ts
Normal file
1
src/components/split-pane/test/nested/e2e.ts
Normal file
@ -0,0 +1 @@
|
||||
|
10
src/components/split-pane/test/nested/main.html
Normal file
10
src/components/split-pane/test/nested/main.html
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
<ion-split-pane when="sm">
|
||||
|
||||
<ion-menu [content]="content">
|
||||
<ion-nav [root]="root2"></ion-nav>
|
||||
</ion-menu>
|
||||
|
||||
<ion-nav [root]="root" main #content></ion-nav>
|
||||
|
||||
</ion-split-pane>
|
98
src/components/split-pane/test/tabs/app.module.ts
Normal file
98
src/components/split-pane/test/tabs/app.module.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import { Component, NgModule } from '@angular/core';
|
||||
import { IonicApp, IonicModule, NavController, MenuController } from '../../../../../ionic-angular';
|
||||
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ion-header>
|
||||
<ion-navbar><ion-title>Navigation</ion-title></ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-slides style="background: black"
|
||||
pager="true"
|
||||
effect="flip">
|
||||
|
||||
<ion-slide style="background: red; color: white;">
|
||||
<h1>Slide 1</h1>
|
||||
</ion-slide>
|
||||
|
||||
<ion-slide style="background: white; color: blue;">
|
||||
<h1>Slide 2</h1>
|
||||
</ion-slide>
|
||||
|
||||
<ion-slide style="background: blue; color: white;">
|
||||
<h1>Slide 3</h1>
|
||||
</ion-slide>
|
||||
|
||||
</ion-slides>
|
||||
</ion-content>
|
||||
`
|
||||
})
|
||||
export class SidePage {
|
||||
constructor(public navCtrl: NavController) { }
|
||||
push() {
|
||||
this.navCtrl.push(SidePage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ion-header>
|
||||
<ion-navbar>
|
||||
<button ion-button menuToggle>
|
||||
<ion-icon name="menu"></ion-icon>
|
||||
</button>
|
||||
<ion-title>Navigation</ion-title>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<button ion-button (click)="push()">Push</button>
|
||||
<div f></div>
|
||||
<div f></div>
|
||||
<div f></div>
|
||||
<div f></div>
|
||||
|
||||
</ion-content>
|
||||
`
|
||||
})
|
||||
export class E2EPage {
|
||||
constructor(
|
||||
public navCtrl: NavController,
|
||||
public menuCtrl: MenuController,
|
||||
) { }
|
||||
|
||||
push() {
|
||||
this.navCtrl.push(E2EPage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
templateUrl: 'main.html'
|
||||
})
|
||||
export class E2EApp {
|
||||
root = E2EPage;
|
||||
root2 = SidePage;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
E2EApp,
|
||||
E2EPage,
|
||||
SidePage,
|
||||
],
|
||||
imports: [
|
||||
IonicModule.forRoot(E2EApp, {
|
||||
swipeBackEnabled: true
|
||||
})
|
||||
],
|
||||
bootstrap: [IonicApp],
|
||||
entryComponents: [
|
||||
E2EApp,
|
||||
E2EPage,
|
||||
SidePage,
|
||||
]
|
||||
})
|
||||
export class AppModule {}
|
||||
|
1
src/components/split-pane/test/tabs/e2e.ts
Normal file
1
src/components/split-pane/test/tabs/e2e.ts
Normal file
@ -0,0 +1 @@
|
||||
|
8
src/components/split-pane/test/tabs/main.html
Normal file
8
src/components/split-pane/test/tabs/main.html
Normal file
@ -0,0 +1,8 @@
|
||||
|
||||
<ion-split-pane>
|
||||
<ion-nav [root]="root2" #content main></ion-nav>
|
||||
<ion-tabs>
|
||||
<ion-tab [root]="root" tabTitle="Page1"></ion-tab>
|
||||
<ion-tab [root]="root" tabTitle="Page2"></ion-tab>
|
||||
</ion-tabs>
|
||||
</ion-split-pane>
|
@ -1,4 +1,4 @@
|
||||
import { Directive, ElementRef, EventEmitter, HostListener, Input, OnInit, Output, Renderer } from '@angular/core';
|
||||
import { Component, ElementRef, EventEmitter, HostListener, Input, OnInit, Output, Renderer } from '@angular/core';
|
||||
|
||||
import { Config } from '../../config/config';
|
||||
import { Ion } from '../ion';
|
||||
@ -7,8 +7,13 @@ import { Tab } from './tab';
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@Directive({
|
||||
@Component({
|
||||
selector: '.tab-button',
|
||||
template:
|
||||
'<ion-icon *ngIf="tab.tabIcon" [name]="tab.tabIcon" [isActive]="tab.isSelected" class="tab-button-icon"></ion-icon>' +
|
||||
'<span *ngIf="tab.tabTitle" class="tab-button-text">{{tab.tabTitle}}</span>' +
|
||||
'<ion-badge *ngIf="tab.tabBadge" class="tab-badge" [color]="tab.tabBadgeStyle">{{tab.tabBadge}}</ion-badge>' +
|
||||
'<div class="button-effect"></div>',
|
||||
host: {
|
||||
'[attr.id]': 'tab._btnId',
|
||||
'[attr.aria-controls]': 'tab._tabId',
|
||||
@ -18,7 +23,9 @@ import { Tab } from './tab';
|
||||
'[class.has-title-only]': 'hasTitleOnly',
|
||||
'[class.icon-only]': 'hasIconOnly',
|
||||
'[class.has-badge]': 'hasBadge',
|
||||
'[class.disable-hover]': 'disHover'
|
||||
'[class.disable-hover]': 'disHover',
|
||||
'[class.tab-disabled]': '!tab.enabled',
|
||||
'[class.tab-hidden]': '!tab.show',
|
||||
}
|
||||
})
|
||||
export class TabButton extends Ion implements OnInit {
|
||||
|
@ -314,15 +314,22 @@ export class Tab extends NavControllerBase {
|
||||
// to refresh the tabbar and content dimensions to be sure
|
||||
// they're lined up correctly
|
||||
this._dom.read(() => {
|
||||
this.resize();
|
||||
});
|
||||
done(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
resize() {
|
||||
const active = this.getActive();
|
||||
if (!active) {
|
||||
return;
|
||||
}
|
||||
const content = active.getIONContent();
|
||||
content && content.resize();
|
||||
});
|
||||
done(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -385,7 +392,7 @@ export class Tab extends NavControllerBase {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
destroy() {
|
||||
ngOnDestroy() {
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { AfterViewInit, Component, ElementRef, EventEmitter, Input, Output, Optional, Renderer, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
|
||||
import { AfterViewInit, Component, ElementRef, EventEmitter, forwardRef, Input, Output, Optional, Renderer, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
|
||||
|
||||
import { App } from '../app/app';
|
||||
import { Config } from '../../config/config';
|
||||
@ -8,6 +8,7 @@ import { isBlank, assert } from '../../util/util';
|
||||
import { NavController } from '../../navigation/nav-controller';
|
||||
import { NavControllerBase } from '../../navigation/nav-controller-base';
|
||||
import { getComponent, NavOptions, DIRECTION_SWITCH } from '../../navigation/nav-util';
|
||||
import { RootNode } from '../split-pane/split-pane';
|
||||
import { Platform } from '../../platform/platform';
|
||||
import { Tab } from './tab';
|
||||
import { TabHighlight } from './tab-highlight';
|
||||
@ -150,19 +151,15 @@ import { ViewController } from '../../navigation/view-controller';
|
||||
selector: 'ion-tabs',
|
||||
template:
|
||||
'<div class="tabbar" role="tablist" #tabbar>' +
|
||||
'<a *ngFor="let t of _tabs" [tab]="t" class="tab-button" [class.tab-disabled]="!t.enabled" [class.tab-hidden]="!t.show" role="tab" href="#" (ionSelect)="select($event)">' +
|
||||
'<ion-icon *ngIf="t.tabIcon" [name]="t.tabIcon" [isActive]="t.isSelected" class="tab-button-icon"></ion-icon>' +
|
||||
'<span *ngIf="t.tabTitle" class="tab-button-text">{{t.tabTitle}}</span>' +
|
||||
'<ion-badge *ngIf="t.tabBadge" class="tab-badge" [color]="t.tabBadgeStyle">{{t.tabBadge}}</ion-badge>' +
|
||||
'<div class="button-effect"></div>' +
|
||||
'</a>' +
|
||||
'<a *ngFor="let t of _tabs" [tab]="t" class="tab-button" role="tab" href="#" (ionSelect)="select(t)"></a>' +
|
||||
'<div class="tab-highlight"></div>' +
|
||||
'</div>' +
|
||||
'<ng-content></ng-content>' +
|
||||
'<div #portal tab-portal></div>',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
providers: [{provide: RootNode, useExisting: forwardRef(() => Tabs) }]
|
||||
})
|
||||
export class Tabs extends Ion implements AfterViewInit {
|
||||
export class Tabs extends Ion implements AfterViewInit, RootNode {
|
||||
/** @internal */
|
||||
_ids: number = -1;
|
||||
/** @internal */
|
||||
@ -562,6 +559,31 @@ export class Tabs extends Ion implements AfterViewInit {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
resize() {
|
||||
const tab = this.getSelected();
|
||||
tab && tab.resize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
initPane(): boolean {
|
||||
const isMain = this._elementRef.nativeElement.hasAttribute('main');
|
||||
return isMain;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
paneChanged(isPane: boolean) {
|
||||
if (isPane) {
|
||||
this.resize();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let tabIds = -1;
|
||||
|
@ -127,6 +127,7 @@ import { HideWhen } from './components/show-hide-when/hide-when';
|
||||
import { Slide } from './components/slides/slide';
|
||||
import { Slides } from './components/slides/slides';
|
||||
import { Spinner } from './components/spinner/spinner';
|
||||
import { SplitPane } from './components/split-pane/split-pane';
|
||||
import { Tab } from './components/tabs/tab';
|
||||
import { Tabs } from './components/tabs/tabs';
|
||||
import { TabButton } from './components/tabs/tab-button';
|
||||
@ -241,6 +242,7 @@ export { HideWhen } from './components/show-hide-when/hide-when';
|
||||
export { Slide } from './components/slides/slide';
|
||||
export { Slides } from './components/slides/slides';
|
||||
export { Spinner } from './components/spinner/spinner';
|
||||
export { SplitPane, RootNode } from './components/split-pane/split-pane';
|
||||
export { Tab } from './components/tabs/tab';
|
||||
export { TabButton } from './components/tabs/tab-button';
|
||||
export { TabHighlight } from './components/tabs/tab-highlight';
|
||||
@ -313,6 +315,9 @@ export { reorderArray } from './util/util';
|
||||
export { Animation, AnimationOptions, EffectProperty, EffectState, PlayOptions } from './animations/animation';
|
||||
export { PageTransition } from './transitions/page-transition';
|
||||
export { Transition } from './transitions/transition';
|
||||
export { PlatformConfigToken } from './platform/platform-registry';
|
||||
export { registerModeConfigs } from './config/mode-registry';
|
||||
export { IonicGestureConfig } from './gestures/gesture-config';
|
||||
|
||||
|
||||
|
||||
@ -432,6 +437,7 @@ export { Transition } from './transitions/transition';
|
||||
Slide,
|
||||
Slides,
|
||||
Spinner,
|
||||
SplitPane,
|
||||
Tab,
|
||||
Tabs,
|
||||
TabButton,
|
||||
@ -526,6 +532,7 @@ export { Transition } from './transitions/transition';
|
||||
Slide,
|
||||
Slides,
|
||||
Spinner,
|
||||
SplitPane,
|
||||
Tab,
|
||||
Tabs,
|
||||
TabButton,
|
||||
|
@ -4,7 +4,7 @@ import { AnimationOptions } from '../animations/animation';
|
||||
import { App } from '../components/app/app';
|
||||
import { Config } from '../config/config';
|
||||
import { convertToView, convertToViews, NavOptions, DIRECTION_BACK, DIRECTION_FORWARD, INIT_ZINDEX,
|
||||
TransitionResolveFn, TransitionInstruction, STATE_INITIALIZED, STATE_LOADED, STATE_PRE_RENDERED } from './nav-util';
|
||||
TransitionResolveFn, TransitionInstruction, STATE_NEW, STATE_INITIALIZED, STATE_ATTACHED, STATE_DESTROYED } from './nav-util';
|
||||
import { setZIndex } from './nav-util';
|
||||
import { DeepLinker } from './deep-linker';
|
||||
import { DomController } from '../platform/dom-controller';
|
||||
@ -213,8 +213,8 @@ export class NavControllerBase extends Ion implements NavController {
|
||||
});
|
||||
}
|
||||
|
||||
// ti.resolve() is called when the navigation transition is finished successfully
|
||||
ti.resolve = (hasCompleted: boolean, isAsync: boolean, enteringName: string, leavingName: string, direction: string) => {
|
||||
// transition has successfully resolved
|
||||
this._trnsId = null;
|
||||
this._init = true;
|
||||
resolve && resolve(hasCompleted, isAsync, enteringName, leavingName, direction);
|
||||
@ -225,23 +225,22 @@ export class NavControllerBase extends Ion implements NavController {
|
||||
this._nextTrns();
|
||||
};
|
||||
|
||||
ti.reject = (rejectReason: any, trns: Transition) => {
|
||||
// rut row raggy, something rejected this transition
|
||||
// ti.reject() is called when the navigation transition fails. ie. it is rejected at some point.
|
||||
ti.reject = (rejectReason: any, transition: Transition) => {
|
||||
this._trnsId = null;
|
||||
this._queue.length = 0;
|
||||
|
||||
while (trns) {
|
||||
if (trns.enteringView && (trns.enteringView._state !== STATE_LOADED)) {
|
||||
// destroy the entering views and all of their hopes and dreams
|
||||
this._destroyView(trns.enteringView);
|
||||
// walk through the transition views so they are destroyed
|
||||
while (transition) {
|
||||
var enteringView = transition.enteringView;
|
||||
if (enteringView && (enteringView._state === STATE_ATTACHED)) {
|
||||
this._destroyView(enteringView);
|
||||
}
|
||||
if (!trns.parent) {
|
||||
if (transition.isRoot()) {
|
||||
this._trnsCtrl.destroy(transition.trnsId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (trns) {
|
||||
this._trnsCtrl.destroy(trns.trnsId);
|
||||
transition = transition.parent;
|
||||
}
|
||||
|
||||
reject && reject(false, false, rejectReason);
|
||||
@ -289,7 +288,19 @@ export class NavControllerBase extends Ion implements NavController {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get entering and leaving views
|
||||
// ensure any of the inserted view are used
|
||||
const insertViews = ti.insertViews;
|
||||
if (insertViews) {
|
||||
for (var i = 0; i < insertViews.length; i++) {
|
||||
var nav = insertViews[i]._nav;
|
||||
if (nav && nav !== this || insertViews[i]._state === STATE_DESTROYED) {
|
||||
ti.reject('leavingView and enteringView are null. stack is already empty');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get entering and leaving views
|
||||
const leavingView = this.getActive();
|
||||
const enteringView = this._getEnteringView(ti, leavingView);
|
||||
|
||||
@ -302,7 +313,7 @@ export class NavControllerBase extends Ion implements NavController {
|
||||
this.setTransitioning(true);
|
||||
|
||||
// Initialize enteringView
|
||||
if (enteringView && isBlank(enteringView._state)) {
|
||||
if (enteringView && enteringView._state === STATE_NEW) {
|
||||
// render the entering view, and all child navs and views
|
||||
// ******** DOM WRITE ****************
|
||||
this._viewInit(enteringView);
|
||||
@ -314,7 +325,6 @@ export class NavControllerBase extends Ion implements NavController {
|
||||
// views have been initialized, now let's test
|
||||
// to see if the transition is even allowed or not
|
||||
return this._viewTest(enteringView, leavingView, ti);
|
||||
|
||||
} else {
|
||||
return this._postViewInit(enteringView, leavingView, ti);
|
||||
}
|
||||
@ -430,7 +440,6 @@ export class NavControllerBase extends Ion implements NavController {
|
||||
// add the views to the
|
||||
for (i = 0; i < insertViews.length; i++) {
|
||||
view = insertViews[i];
|
||||
assert(view, 'view must be non null');
|
||||
this._insertViewAt(view, ti.insertStart + i);
|
||||
}
|
||||
|
||||
@ -488,6 +497,9 @@ export class NavControllerBase extends Ion implements NavController {
|
||||
* DOM WRITE
|
||||
*/
|
||||
_viewInit(enteringView: ViewController) {
|
||||
assert(enteringView, 'enteringView must be non null');
|
||||
assert(enteringView._state === STATE_NEW, 'enteringView state must be NEW');
|
||||
|
||||
// entering view has not been initialized yet
|
||||
const componentProviders = ReflectiveInjector.resolve([
|
||||
{ provide: NavController, useValue: this },
|
||||
@ -512,7 +524,7 @@ export class NavControllerBase extends Ion implements NavController {
|
||||
// render the component ref instance to the DOM
|
||||
// ******** DOM WRITE ****************
|
||||
viewport.insert(componentRef.hostView, viewport.length);
|
||||
view._state = STATE_PRE_RENDERED;
|
||||
view._state = STATE_ATTACHED;
|
||||
|
||||
if (view._cssClass) {
|
||||
// the ElementRef of the actual ion-page created
|
||||
@ -615,14 +627,12 @@ export class NavControllerBase extends Ion implements NavController {
|
||||
}
|
||||
});
|
||||
|
||||
if (enteringView && enteringView._state === STATE_INITIALIZED) {
|
||||
if (enteringView && (enteringView._state === STATE_INITIALIZED)) {
|
||||
// render the entering component in the DOM
|
||||
// this would also render new child navs/views
|
||||
// which may have their very own async canEnter/Leave tests
|
||||
// ******** DOM WRITE ****************
|
||||
this._viewAttachToDOM(enteringView, enteringView._cmp, this._viewport);
|
||||
} else {
|
||||
console.debug('enteringView state is not INITIALIZED', enteringView);
|
||||
}
|
||||
|
||||
if (!transition.hasChildren) {
|
||||
@ -773,9 +783,10 @@ export class NavControllerBase extends Ion implements NavController {
|
||||
if (existingIndex > -1) {
|
||||
// this view is already in the stack!!
|
||||
// move it to its new location
|
||||
assert(view._nav === this, 'view is not part of the nav');
|
||||
this._views.splice(index, 0, this._views.splice(existingIndex, 1)[0]);
|
||||
|
||||
} else {
|
||||
assert(!view._nav || (this._isPortal && view._nav === this), 'nav is used');
|
||||
// this is a new view to add to the stack
|
||||
// create the new entering view
|
||||
view._setNav(this);
|
||||
@ -792,6 +803,8 @@ export class NavControllerBase extends Ion implements NavController {
|
||||
}
|
||||
|
||||
_removeView(view: ViewController) {
|
||||
assert(view._state === STATE_ATTACHED || view._state === STATE_DESTROYED, 'view state should be loaded or destroyed');
|
||||
|
||||
const views = this._views;
|
||||
const index = views.indexOf(view);
|
||||
assert(index > -1, 'view must be part of the stack');
|
||||
@ -811,7 +824,7 @@ export class NavControllerBase extends Ion implements NavController {
|
||||
_cleanup(activeView: ViewController) {
|
||||
// ok, cleanup time!! Destroy all of the views that are
|
||||
// INACTIVE and come after the active view
|
||||
const activeViewIndex = this.indexOf(activeView);
|
||||
const activeViewIndex = this._views.indexOf(activeView);
|
||||
const views = this._views;
|
||||
let reorderZIndexes = false;
|
||||
let view: ViewController;
|
||||
@ -1030,7 +1043,8 @@ export class NavControllerBase extends Ion implements NavController {
|
||||
if (!view) {
|
||||
view = this.getActive();
|
||||
}
|
||||
return this._views[this.indexOf(view) - 1];
|
||||
const views = this._views;
|
||||
return views[views.indexOf(view) - 1];
|
||||
}
|
||||
|
||||
first(): ViewController {
|
||||
@ -1066,7 +1080,7 @@ export class NavControllerBase extends Ion implements NavController {
|
||||
dismissPageChangeViews() {
|
||||
for (let view of this._views) {
|
||||
if (view.data && view.data.dismissOnPageChange) {
|
||||
view.dismiss();
|
||||
view.dismiss().catch(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1075,6 +1089,15 @@ export class NavControllerBase extends Ion implements NavController {
|
||||
this._viewport = val;
|
||||
}
|
||||
|
||||
resize() {
|
||||
const active = this.getActive();
|
||||
if (!active) {
|
||||
return;
|
||||
}
|
||||
const content = active.getIONContent();
|
||||
content && content.resize();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let ctrlIds = -1;
|
||||
|
@ -609,4 +609,9 @@ export abstract class NavController {
|
||||
* @private
|
||||
*/
|
||||
abstract registerChildNav(nav: any): void;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
abstract resize(): void;
|
||||
}
|
||||
|
@ -208,10 +208,10 @@ export interface TransitionInstruction {
|
||||
requiresTransition?: boolean;
|
||||
}
|
||||
|
||||
|
||||
export const STATE_INITIALIZED = 1;
|
||||
export const STATE_PRE_RENDERED = 2;
|
||||
export const STATE_LOADED = 3;
|
||||
export const STATE_NEW = 1;
|
||||
export const STATE_INITIALIZED = 2;
|
||||
export const STATE_ATTACHED = 3;
|
||||
export const STATE_DESTROYED = 4;
|
||||
|
||||
export const INIT_ZINDEX = 100;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { mockNavController, mockView, mockViews } from '../../util/mock-providers';
|
||||
|
||||
import { STATE_ATTACHED } from '../nav-util';
|
||||
|
||||
describe('ViewController', () => {
|
||||
|
||||
@ -16,6 +16,7 @@ describe('ViewController', () => {
|
||||
});
|
||||
|
||||
// act
|
||||
viewController._state = STATE_ATTACHED;
|
||||
viewController._willEnter();
|
||||
}, 10000);
|
||||
});
|
||||
@ -33,6 +34,7 @@ describe('ViewController', () => {
|
||||
});
|
||||
|
||||
// act
|
||||
viewController._state = STATE_ATTACHED;
|
||||
viewController._didEnter();
|
||||
}, 10000);
|
||||
});
|
||||
@ -50,6 +52,7 @@ describe('ViewController', () => {
|
||||
});
|
||||
|
||||
// act
|
||||
viewController._state = STATE_ATTACHED;
|
||||
viewController._willLeave(false);
|
||||
}, 10000);
|
||||
});
|
||||
|
@ -2,10 +2,10 @@ import { ComponentRef, ElementRef, EventEmitter, Output, Renderer } from '@angul
|
||||
|
||||
import { Footer } from '../components/toolbar/toolbar-footer';
|
||||
import { Header } from '../components/toolbar/toolbar-header';
|
||||
import { isPresent } from '../util/util';
|
||||
import { isPresent, assert } from '../util/util';
|
||||
import { Navbar } from '../components/navbar/navbar';
|
||||
import { NavController } from './nav-controller';
|
||||
import { NavOptions } from './nav-util';
|
||||
import { NavOptions, STATE_NEW, STATE_INITIALIZED, STATE_ATTACHED, STATE_DESTROYED } from './nav-util';
|
||||
import { NavParams } from './nav-params';
|
||||
import { Content } from '../components/content/content';
|
||||
|
||||
@ -47,7 +47,7 @@ export class ViewController {
|
||||
_cmp: ComponentRef<any>;
|
||||
_nav: NavController;
|
||||
_zIndex: number;
|
||||
_state: number;
|
||||
_state: number = STATE_NEW;
|
||||
_cssClass: string;
|
||||
|
||||
/**
|
||||
@ -166,6 +166,7 @@ export class ViewController {
|
||||
*/
|
||||
dismiss(data?: any, role?: any, navOptions: NavOptions = {}): Promise<any> {
|
||||
if (!this._nav) {
|
||||
assert(this._state === STATE_DESTROYED, 'ViewController does not have a valid _nav');
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
if (this.isOverlay && !navOptions.minClickBlockDuration) {
|
||||
@ -228,7 +229,7 @@ export class ViewController {
|
||||
* @private
|
||||
*/
|
||||
get name(): string {
|
||||
return this.component ? this.component.name : '';
|
||||
return (this.component ? this.component.name : '');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -262,16 +263,14 @@ export class ViewController {
|
||||
// _hidden value of '' means the hidden attribute will be added
|
||||
// _hidden value of null means the hidden attribute will be removed
|
||||
// doing checks to make sure we only update the DOM when actually needed
|
||||
if (this._cmp) {
|
||||
// if it should render, then the hidden attribute should not be on the element
|
||||
if (shouldShow === this._isHidden) {
|
||||
if (this._cmp && shouldShow === this._isHidden) {
|
||||
this._isHidden = !shouldShow;
|
||||
let value = (shouldShow ? null : '');
|
||||
// ******** DOM WRITE ****************
|
||||
renderer.setElementAttribute(this.pageRef().nativeElement, 'hidden', value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
@ -412,6 +411,7 @@ export class ViewController {
|
||||
}
|
||||
|
||||
_preLoad() {
|
||||
assert(this._state === STATE_INITIALIZED, 'view state must be INITIALIZED');
|
||||
this._lifecycle('PreLoad');
|
||||
}
|
||||
|
||||
@ -421,6 +421,7 @@ export class ViewController {
|
||||
* This event is fired before the component and his children have been initialized.
|
||||
*/
|
||||
_willLoad() {
|
||||
assert(this._state === STATE_INITIALIZED, 'view state must be INITIALIZED');
|
||||
this._lifecycle('WillLoad');
|
||||
}
|
||||
|
||||
@ -433,6 +434,7 @@ export class ViewController {
|
||||
* recommended method to use when a view becomes active.
|
||||
*/
|
||||
_didLoad() {
|
||||
assert(this._state === STATE_ATTACHED, 'view state must be ATTACHED');
|
||||
this._lifecycle('DidLoad');
|
||||
}
|
||||
|
||||
@ -441,6 +443,8 @@ export class ViewController {
|
||||
* The view is about to enter and become the active view.
|
||||
*/
|
||||
_willEnter() {
|
||||
assert(this._state === STATE_ATTACHED, 'view state must be ATTACHED');
|
||||
|
||||
if (this._detached && this._cmp) {
|
||||
// ensure this has been re-attached to the change detector
|
||||
this._cmp.changeDetectorRef.reattach();
|
||||
@ -457,6 +461,8 @@ export class ViewController {
|
||||
* will fire, whether it was the first load or loaded from the cache.
|
||||
*/
|
||||
_didEnter() {
|
||||
assert(this._state === STATE_ATTACHED, 'view state must be ATTACHED');
|
||||
|
||||
this._nb && this._nb.didEnter();
|
||||
this.didEnter.emit(null);
|
||||
this._lifecycle('DidEnter');
|
||||
@ -511,6 +517,8 @@ export class ViewController {
|
||||
* DOM WRITE
|
||||
*/
|
||||
_destroy(renderer: Renderer) {
|
||||
assert(this._state !== STATE_DESTROYED, 'view state must be ATTACHED');
|
||||
|
||||
if (this._cmp) {
|
||||
if (renderer) {
|
||||
// ensure the element is cleaned up for when the view pool reuses this element
|
||||
@ -525,6 +533,7 @@ export class ViewController {
|
||||
}
|
||||
|
||||
this._nav = this._cmp = this.instance = this._cntDir = this._cntRef = this._leavingOpts = this._hdrDir = this._ftrDir = this._nb = this._onDidDismiss = this._onWillDismiss = null;
|
||||
this._state = STATE_DESTROYED;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -535,7 +544,7 @@ export class ViewController {
|
||||
const methodName = 'ionViewCan' + lifecycle;
|
||||
if (instance && instance[methodName]) {
|
||||
try {
|
||||
let result = instance[methodName]();
|
||||
var result = instance[methodName]();
|
||||
if (result === false) {
|
||||
return false;
|
||||
} else if (result instanceof Promise) {
|
||||
|
@ -190,6 +190,12 @@
|
||||
@import
|
||||
"../components/slides/slides";
|
||||
|
||||
@import
|
||||
"../components/split-pane/split-pane",
|
||||
"../components/split-pane/split-pane.ios",
|
||||
"../components/split-pane/split-pane.md",
|
||||
"../components/split-pane/split-pane.wp";
|
||||
|
||||
@import
|
||||
"../components/spinner/spinner",
|
||||
"../components/spinner/spinner.ios",
|
||||
|
@ -351,7 +351,9 @@ export function mockView(component?: any, data?: any) {
|
||||
|
||||
export function mockViews(nav: NavControllerBase, views: ViewController[]) {
|
||||
nav._views = views;
|
||||
views.forEach(v => v._setNav(nav));
|
||||
views.forEach(v => {
|
||||
v._setNav(nav);
|
||||
});
|
||||
}
|
||||
|
||||
export function mockComponentRef(): ComponentRef<any> {
|
||||
@ -499,7 +501,9 @@ export function mockMenu(): Menu {
|
||||
let app = mockApp();
|
||||
let gestureCtrl = new GestureController(app);
|
||||
let dom = mockDomController();
|
||||
return new Menu(null, null, null, null, null, null, null, gestureCtrl, dom, app);
|
||||
let elementRef = mockElementRef();
|
||||
let renderer = mockRenderer();
|
||||
return new Menu(null, elementRef, null, null, renderer, null, null, gestureCtrl, dom, app);
|
||||
}
|
||||
|
||||
export function mockDeepLinkConfig(links?: any[]): DeepLinkConfig {
|
||||
|
@ -170,7 +170,7 @@ function _runInDev(fn: Function) {
|
||||
|
||||
|
||||
/** @private */
|
||||
function _assert(actual: any, reason?: string) {
|
||||
function _assert(actual: any, reason: string) {
|
||||
if (!actual && ASSERT_ENABLED === true) {
|
||||
let message = 'IONIC ASSERT: ' + reason;
|
||||
console.error(message);
|
||||
|
Reference in New Issue
Block a user