var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

import { EventEmitter, provide, ReflectiveInjector } from '@angular/core';
import { addSelector } from '../../config/bootstrap';
import { Ion } from '../ion';
import { isBlank, pascalCaseToDashCase } from '../../util/util';
import { MenuController } from '../menu/menu-controller';
import { NavParams } from './nav-params';
import { SwipeBackGesture } from './swipe-back';
import { Transition } from '../../transitions/transition';
import { ViewController } from './view-controller';
/**
 * @name NavController
 * @description
 * _For examples on the basic usage of NavController, check out the
 * [Navigation section](../../../../components/#navigation) of the Component
 * docs._
 *
 * NavController is the base class for navigation controller components like
 * [`Nav`](../Nav/) and [`Tab`](../../Tabs/Tab/). You use navigation controllers
 * to navigate to [pages](#creating_pages) in your app. At a basic level, a
 * navigation controller is an array of pages representing a particular history
 * (of a Tab for example). This array can be manipulated to navigate throughout
 * an app by pushing and popping pages or inserting and removing them at
 * arbitrary locations in history.
 *
 * The current page is the last one in the array, or the top of the stack if we
 * think of it that way.  [Pushing](#push) a new page onto the top of the
 * navigation stack causes the new page to be animated in, while [popping](#pop)
 * the current page will navigate to the previous page in the stack.
 *
 * Unless you are using a directive like [NavPush](../NavPush/), or need a
 * specific NavController, most times you will inject and use a reference to the
 * nearest NavController to manipulate the navigation stack.
 *
 * ### Injecting NavController
 * Injecting NavController will always get you an instance of the nearest
 * NavController, regardless of whether it is a Tab or a Nav.
 *
 * Behind the scenes, when Ionic instantiates a new NavController, it creates an
 * injector with NavController bound to that instance (usually either a Nav or
 * Tab) and adds the injector to its own providers.  For more information on
 * providers and dependency injection, see [Providers and DI]().
 *
 * Instead, you can inject NavController and know that it is the correct
 * navigation controller for most situations (for more advanced situations, see
 * [Menu](../../Menu/Menu/) and [Tab](../../Tab/Tab/)).
 *
 * ```ts
 *  class MyComponent {
 *    constructor(private nav: NavController) {
 *
 *    }
 *  }
 * ```
 *
 *
 * ## Page creation
 * Pages are created when they are added to the navigation stack.  For methods
 * like [push()](#push), the NavController takes any component class that is
 * decorated with `@Component` as its first argument.  The NavController then
 * compiles that component, adds it to the app and animates it into view.
 *
 * By default, pages are cached and left in the DOM if they are navigated away
 * from but still in the navigation stack (the exiting page on a `push()` for
 * example).  They are destroyed when removed from the navigation stack (on
 * [pop()](#pop) or [setRoot()](#setRoot)).
 *
 *
 * ## Lifecycle events
 * Lifecycle events are fired during various stages of navigation.  They can be
 * defined in any component type which is pushed/popped from a `NavController`.
 *
 * ```ts
 * import {Component } from '@angular/core';
 *
 * @Component({
 *   template: 'Hello World'
 * })
 * class HelloWorld {
 *   ionViewLoaded() {
 *     console.log("I'm alive!");
 *   }
 *   ionViewWillLeave() {
 *     console.log("Looks like I'm about to leave :(");
 *   }
 * }
 * ```
 *
 *  | Page Event         | Description                                                                                                                                                                                                                                                                       |
 *  |--------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
 *  | `ionViewLoaded`     | Runs when the page has loaded. This event only happens once per page being created and added to the DOM. If a page leaves but is cached, then this event will not fire again on a subsequent viewing. The `ionViewLoaded` event is good place to put your setup code for the page. |
 *  | `ionViewWillEnter`  | Runs when the page is about to enter and become the active page.                                                                                                                                                                                                                  |
 *  | `ionViewDidEnter`   | Runs when the page has fully entered and is now the active page. This event will fire, whether it was the first load or a cached page.                                                                                                                                            |
 *  | `ionViewWillLeave`  | Runs when the page is about to leave and no longer be the active page.                                                                                                                                                                                                            |
 *  | `ionViewDidLeave`   | Runs when the page has finished leaving and is no longer the active page.                                                                                                                                                                                                         |
 *  | `ionViewWillUnload` | Runs when the page is about to be destroyed and have its elements removed.                                                                                                                                                                                                        |
 *  | `ionViewDidUnload`  | Runs after the page has been destroyed and its elements have been removed.
 *
 *
 * ## Nav Transition Promises
 *
 * Navigation transitions are asynchronous, meaning they take a few moments to finish, and
 * the duration of a transition could be any number. In most cases the async nature of a
 * transition doesn't cause any problems and the nav controller is pretty good about handling
 * which transition was the most recent when multiple transitions have been kicked off.
 * However, when an app begins firing off many transitions, on the same stack at
 * *roughly* the same time, the nav controller can start to get lost as to which transition
 * should be finishing, and which transitions should not be animated.
 *
 * In cases where an app's navigation can be altered by other async tasks, which may or
 * may not take a long time, it's best to rely on each nav transition's returned
 * promise. So instead of firing and forgetting multiple `push` or `pop` nav transitions,
 * it's better to fire the next nav transition when the previous one has finished.
 *
 * In the example below, after the async operation has completed, we then want to transition
 * to another page. Where the potential problem comes in, is that if the async operation
 * completed 100ms after the first transition started, then kicking off another transition
 * halfway through the first transition ends up with a janky animation. Instead, it's best
 * to always ensure the first transition has already finished before starting the next.
 *
 * ```ts
 * // begin the first transition
 * let navTransition = this.nav.push(SomePage);
 *
 * // start an async call, we're not sure how long it'll take
 * someAsyncOperation().then(() => {
 *   // incase the async operation completed faster than the time
 *   // it took to finish the first transition, this logic should
 *   // always ensure that the previous transition has resolved
 *   // first before kicking off the next transition
 *   navTransition.then(() => {
 *     this.nav.push(AnotherPage);
 *   });
 * });
 * ```
 *
 * ## NavOptions
 *
 * Some methods on `NavController` allow for customizing the current transition.
 * To do this, we can pass an object with the modified properites.
 *
 * | Property  | Value     | Description                                                                                                |
 * |-----------|-----------|------------------------------------------------------------------------------------------------------------|
 * | animate   | `boolean` | Whether or not the transition should animate.                                                              |
 * | animation | `string`  | What kind of animation should be used.                                                                     |
 * | direction | `string`  | The conceptual direction the user is navigating. For example, is the user navigating `forward`, or `back`? |
 * | duration  | `number`  | The length in milliseconds the animation should take.                                                      |
 * | easing    | `string`  | The easing for the animation.                                                                              |
 *
 * The property 'animation' understands the following values: `md-transition`, `ios-transition` and `wp-transition`.
 *
 * @see {@link /docs/v2/components#navigation Navigation Component Docs}
 */

var NavController = function (_Ion) {
    _inherits(NavController, _Ion);

    function NavController(parent, _app, config, _keyboard, elementRef, _zone, _renderer, _compiler) {
        _classCallCheck(this, NavController);

        var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(NavController).call(this, elementRef));

        _this._app = _app;
        _this._keyboard = _keyboard;
        _this._zone = _zone;
        _this._renderer = _renderer;
        _this._compiler = _compiler;
        _this._transIds = 0;
        _this._init = false;
        _this._children = [];
        _this._ids = -1;
        _this._views = [];
        /**
         * @private
         */
        _this.routers = [];
        /**
         * @private
         */
        _this.isPortal = false;
        /**
         * @private
         */
        _this._trnsTime = 0;
        _this.parent = parent;
        _this.config = config;
        _this._trnsDelay = config.get('pageTransitionDelay');
        _this._sbEnabled = config.getBoolean('swipeBackEnabled');
        _this._sbThreshold = config.getNumber('swipeBackThreshold', 40);
        _this.id = (++ctrlIds).toString();
        _this.viewDidLoad = new EventEmitter();
        _this.viewWillEnter = new EventEmitter();
        _this.viewDidEnter = new EventEmitter();
        _this.viewWillLeave = new EventEmitter();
        _this.viewDidLeave = new EventEmitter();
        _this.viewWillUnload = new EventEmitter();
        _this.viewDidUnload = new EventEmitter();
        return _this;
    }
    /**
     * @private
     */


    _createClass(NavController, [{
        key: 'setViewport',
        value: function setViewport(val) {
            this._viewport = val;
        }
        /**
         * Set the root for the current navigation stack.
         * @param {Page} page  The name of the component you want to push on the navigation stack.
         * @param {object} [params={}] Any nav-params you want to pass along to the next view.
         * @param {object} [opts={}] Any options you want to use pass to transtion.
         * @returns {Promise} Returns a promise which is resolved when the transition has completed.
         */

    }, {
        key: 'setRoot',
        value: function setRoot(page, params, opts) {
            return this.setPages([{ page: page, params: params }], opts);
        }
        /**
         * You can set the views of the current navigation stack and navigate to the
         * last view.
         *
         *
         *```ts
         * import {NavController } from 'ionic-angular'
         * import {Detail } from '../detail/detail'
         * import {Info } from '../info/info'
         *
         *  export class Home {
         *    constructor(private nav: NavController) {
         *
         *    }
         *    setPages() {
         *      this.nav.setPages([ {page: List}, {page: Detail}, {page:Info} ]);
         *    }
         *  }
         *```
         *
         *
         * In this example, we're giving the current nav stack an array of pages.
         * Then the navigation stack will navigate to the last page in the array
         * and remove the previously active page.
         *
         * By default animations are disabled, but they can be enabled by passing
         * options to the navigation controller.
         *
         *
         * ```ts
         * import {NavController } from 'ionic-angular'
         * import {Detail } from '../detail/detail'
         *
         *  export class Home {
         *    constructor(private nav: NavController) {
         *
         *    }
         *    setPages() {
         *      this.nav.setPages([ {page: List}, {page: Detail} ], {
         *        animate: true
         *      });
         *    }
         *  }
         * ```
         *
         * You can also pass any navigation params to the individual pages in
         * the array.
         *
         *
         * ```ts
         * import {NavController } from 'ionic-angular';
         * import {Info } from '../info/info';
         * import {List } from '../list/list';
         * import {Detail } from '../detail/detail';
         *
         *  export class Home {
         *    constructor(private nav: NavController) {
         *
         *    }
         *    setPages() {
         *      this.nav.setPages([{
         *        page: Info
         *      }, {
         *        page: List,
         *        params: {tags: 'css'}
         *      }, {
         *        page: Detail,
         *        params: {id: 325}
         *      }]);
         *    }
         *  }
         *```
         *
         * @param {array<Page>} pages  An arry of page components and their params to load in the stack.
         * @param {object} [opts={}] Nav options to go with this transition.
         * @returns {Promise} Returns a promise which is resolved when the transition has completed.
         */

    }, {
        key: 'setPages',
        value: function setPages(pages, opts) {
            if (!pages || !pages.length) {
                return Promise.resolve(false);
            }
            if (isBlank(opts)) {
                opts = {};
            }
            // remove existing views
            var leavingView = this._remove(0, this._views.length);
            // create view controllers out of the pages and insert the new views
            var views = pages.map(function (p) {
                return new ViewController(p.page, p.params);
            });
            var enteringView = this._insert(0, views);
            // if animation wasn't set to true then default it to NOT animate
            if (opts.animate !== true) {
                opts.animate = false;
            }
            // set the nav direction to "back" if it wasn't set
            opts.direction = opts.direction || 'back';
            var resolve = void 0;
            var promise = new Promise(function (res) {
                resolve = res;
            });
            // start the transition, fire resolve when done...
            this._transition(enteringView, leavingView, opts, function (hasCompleted) {
                // transition has completed!!
                resolve(hasCompleted);
            });
            return promise;
        }
        /**
         * Push is how we can pass components and navigate to them. We push the component
         * we want to navigate to on to the navigation stack.
         *
         * ```ts
         * class MyClass{
         *    constructor(nav:NavController){
         *      this.nav = nav;
         *    }
         *
         *    pushPage(){
         *      this.nav.push(SecondView);
         *    }
         * }
         * ```
         *
         * We can also pass along parameters to the next view, such as data that we have
         * on the current view. This is a similar concept to to V1 apps with `$stateParams`.
         *
         * ```ts
         * class MyClass{
         *    constructor(nav:NavController){
         *      this.nav = nav;
         *    }
         *
         *    pushPage(user){
         *       // user is an object we have in our view
         *       // typically this comes from an ngFor or some array
         *       // here we can create an object with a property of
         *       // paramUser, and set its value to the user object we passed in
         *      this.nav.push(SecondView, { paramUser: user });
         *    }
         * }
         * ```
         *
         * We'll look at how we can access that data in the `SecondView` in the
         * navParam docs.
         *
         * We can also pass any options to the transtion from that same method.
         *
         * ```ts
         * class MyClass{
         *    constructor(private nav: NavController){
         *
         *    }
         *
         *    pushPage(user){
         *      this.nav.push(SecondView,{
         *       // user is an object we have in our view
         *       // typically this comes from an ngFor or some array
         *       // here we can create an object with a property of
         *       // paramUser, and set it's value to the user object we passed in
         *       paramUser: user
         *      },{
         *       // here we can configure things like the animations direction or
         *       // or if the view should animate at all.
         *       direction: 'back'
         *      });
         *    }
         * }
         * ```
         * @param {Page} page  The page component class you want to push on to the navigation stack
         * @param {object} [params={}] Any nav-params you want to pass along to the next view
         * @param {object} [opts={}] Nav options to go with this transition.
         * @returns {Promise} Returns a promise which is resolved when the transition has completed.
         */

    }, {
        key: 'push',
        value: function push(page, params, opts) {
            return this.insertPages(-1, [{ page: page, params: params }], opts);
        }
        /**
         * @private
         * DEPRECATED: Please use inject the overlays controller and use the present method on the instance instead.
         */

    }, {
        key: 'present',
        value: function present(enteringView, opts) {
            // deprecated warning: added beta.11 2016-06-27
            console.warn('nav.present() has been deprecated.\n' + 'Please inject the overlay\'s controller and use the present method on the instance instead.');
            return Promise.resolve();
        }
        /**
         * Inserts a view into the nav stack at the specified index. This is useful if
         * you need to add a view at any point in your navigation stack.
         *
         * ```ts
         * export class Detail {
         *    constructor(private nav: NavController) {
         *
         *    }
         *    insertPage(){
         *      this.nav.insert(1, Info);
         *    }
         *  }
         * ```
         *
         * This will insert the `Info` page into the second slot of our navigation stack.
         *
         * @param {number} insertIndex  The index where to insert the page.
         * @param {Page} page  The component you want to insert into the nav stack.
         * @param {object} [params={}] Any nav-params you want to pass along to the next page.
         * @param {object} [opts={}] Nav options to go with this transition.
         * @returns {Promise} Returns a promise which is resolved when the transition has completed.
         */

    }, {
        key: 'insert',
        value: function insert(insertIndex, page, params, opts) {
            return this.insertPages(insertIndex, [{ page: page, params: params }], opts);
        }
        /**
         * Inserts multiple pages into the nav stack at the specified index.
         *
         * ```ts
         * export class Detail {
         *    constructor(private nav: NavController) {
         *
         *    }
         *    insertPages(){
         *      let pages = [
         *        { page: Info },
         *        { page: ProfileList },
         *        { page: ProfileDetail, params: {userId:5} }
         *      ];
         *      this.nav.insertPages(2, pages);
         *    }
         *  }
         * ```
         *
         * This will insert each of the pages in the array, starting at the third slot
         * (second index) of the nav stack. The last page in the array will animate
         * in and become the active page.
         *
         * @param {number} insertIndex  The index where you want to insert the page.
         * @param {array<{page: Page, params=: any}>} insertPages  An array of objects, each with a `page` and optionally `params` property.
         * @param {object} [opts={}] Nav options to go with this transition.
         * @returns {Promise} Returns a promise which is resolved when the transition has completed.
         */

    }, {
        key: 'insertPages',
        value: function insertPages(insertIndex, _insertPages, opts) {
            var views = _insertPages.map(function (p) {
                return new ViewController(p.page, p.params);
            });
            return this.insertViews(insertIndex, views, opts);
        }
        /**
         * @private
         */

    }, {
        key: 'insertViews',
        value: function insertViews(insertIndex, _insertViews, opts) {
            if (!_insertViews || !_insertViews.length) {
                return Promise.reject('invalid pages');
            }
            if (isBlank(opts)) {
                opts = {};
            }
            // insert the new page into the stack
            // returns the newly created entering view
            var enteringView = this._insert(insertIndex, _insertViews);
            // set the nav direction to "forward" if it wasn't set
            opts.direction = opts.direction || 'forward';
            // set which animation it should use if it wasn't set yet
            if (!opts.animation) {
                opts.animation = enteringView.getTransitionName(opts.direction);
            }
            var resolve = void 0;
            var promise = new Promise(function (res) {
                resolve = res;
            });
            // it's possible that the newly added view doesn't need to
            // transition in, but was simply inserted somewhere in the stack
            // go backwards through the stack and find the first active view
            // which could be active or one ready to enter
            for (var i = this._views.length - 1; i >= 0; i--) {
                if (this._views[i].state === STATE_ACTIVE || this._views[i].state === STATE_INIT_ENTER) {
                    // found the view at the end of the stack that's either
                    // already active or it is about to enter
                    if (this._views[i] === enteringView) {
                        // cool, so the last valid view is also our entering view!!
                        // this means we should animate that bad boy in so it's the active view
                        // return a promise and resolve when the transition has completed
                        // get the leaving view which the _insert() already set
                        var leavingView = this.getByState(STATE_INIT_LEAVE);
                        // start the transition, fire resolve when done...
                        this._transition(enteringView, leavingView, opts, function (hasCompleted) {
                            // transition has completed!!
                            resolve(hasCompleted);
                        });
                        return promise;
                    }
                    break;
                }
            }
            // the page was not pushed onto the end of the stack
            // but rather inserted somewhere in the middle or beginning
            // Since there are views after this new one, don't transition in
            // auto resolve cuz there was is no need for an animation
            return Promise.resolve(enteringView);
        }
        /**
         * @private
         */

    }, {
        key: '_insert',
        value: function _insert(insertIndex, insertViews) {
            var _this2 = this;

            // when this is done, there should only be at most
            // 1 STATE_INIT_ENTER and 1 STATE_INIT_LEAVE
            // there should not be any that are STATE_ACTIVE after this is done
            // allow -1 to be passed in to auto push it on the end
            // and clean up the index if it's larger then the size of the stack
            if (insertIndex < 0 || insertIndex > this._views.length) {
                insertIndex = this._views.length;
            }
            // first see if there's an active view
            var view = this.getActive();
            if (view) {
                // there's an active view, set that it's initialized to leave
                view.state = STATE_INIT_LEAVE;
            } else if (view = this.getByState(STATE_INIT_ENTER)) {
                // oh no, there's already a transition initalized ready to enter!
                // but it actually hasn't entered yet at all so lets
                // just keep it in the array, but not render or animate it in
                view.state = STATE_INACTIVE;
            }
            // insert each of the views in the pages array
            var insertView = null;
            insertViews.forEach(function (view, i) {
                insertView = view;
                // create the new entering view
                view.setNav(_this2);
                view.state = STATE_INACTIVE;
                // give this inserted view an ID
                _this2._incId(view);
                // insert the entering view into the correct index in the stack
                _this2._views.splice(insertIndex + i, 0, view);
            });
            if (insertView) {
                insertView.state = STATE_INIT_ENTER;
            }
            return insertView;
        }
        /**
         * If you wanted to navigate back from a current view, you can use the
         * back-button or programatically call `pop()`. Similar to `push()`, you
         * can also pass navigation options.
         *
         * ```ts
         * class SecondView{
         *    constructor(nav:NavController){
         *      this.nav = nav;
         *    }
         *    goBack(){
         *      this.nav.pop();
         *    }
         * }
         * ```
         *
         * @param {object} [opts={}] Nav options to go with this transition.
         * @returns {Promise} Returns a promise which is resolved when the transition has completed.
         */

    }, {
        key: 'pop',
        value: function pop(opts) {
            // get the index of the active view
            // which will become the view to be leaving
            var activeView = this.getByState(STATE_TRANS_ENTER) || this.getByState(STATE_INIT_ENTER) || this.getActive();
            if (isBlank(opts)) {
                opts = {};
            }
            // if not set, by default climb up the nav controllers if
            // there isn't a previous view in this nav controller
            if (isBlank(opts.climbNav)) {
                opts.climbNav = true;
            }
            return this.remove(this.indexOf(activeView), 1, opts);
        }
        /**
         * Similar to `pop()`, this method let's you navigate back to the root of
         * the stack, no matter how many pages back that is.
         * @param {object} [opts={}] Nav options to go with this transition.
         * @returns {Promise} Returns a promise which is resolved when the transition has completed.
         */

    }, {
        key: 'popToRoot',
        value: function popToRoot(opts) {
            return this.popTo(this.first(), opts);
        }
        /**
         * Pop to a specific view in the history stack.
         * @param {ViewController} view  to pop to
         * @param {object} [opts={}] Nav options to go with this transition.
         * @returns {Promise} Returns a promise which is resolved when the transition has completed.
         */

    }, {
        key: 'popTo',
        value: function popTo(view, opts) {
            var startIndex = this.indexOf(view);
            if (startIndex < 0) {
                return Promise.reject('View not found to pop to');
            }
            var activeView = this.getByState(STATE_TRANS_ENTER) || this.getByState(STATE_INIT_ENTER) || this.getActive();
            var removeCount = this.indexOf(activeView) - startIndex;
            return this.remove(startIndex + 1, removeCount, opts);
        }
        /**
         * Removes a page from the nav stack at the specified index.
         *
         * ```ts
         * export class Detail {
         *    constructor(private nav: NavController) {
         *
         *    }
         *    removePage(){
         *      this.nav.remove(1);
         *    }
         *  }
         * ```
         *
         * @param {number} [startIndex]  The starting index to remove pages from the stack. Default is the index of the last page.
         * @param {number} [removeCount]  The number of pages to remove, defaults to remove `1`.
         * @param {object} [opts={}] Any options you want to use pass to transtion.
         * @returns {Promise} Returns a promise which is resolved when the transition has completed.
         */

    }, {
        key: 'remove',
        value: function remove() {
            var startIndex = arguments.length <= 0 || arguments[0] === undefined ? -1 : arguments[0];

            var _this3 = this;

            var removeCount = arguments.length <= 1 || arguments[1] === undefined ? 1 : arguments[1];
            var opts = arguments[2];

            if (startIndex === -1) {
                startIndex = this._views.length - 1;
            } else if (startIndex < 0 || startIndex >= this._views.length) {
                return Promise.reject('remove index out of range');
            }
            if (isBlank(opts)) {
                opts = {};
            }
            // default the direction to "back"
            opts.direction = opts.direction || 'back';
            // figure out the states of each view in the stack
            var leavingView = this._remove(startIndex, removeCount);
            if (!leavingView) {
                var forcedActive = this.getByState(STATE_FORCE_ACTIVE);
                if (forcedActive) {
                    // this scenario happens when a remove is going on
                    // during a transition
                    if (this._trans) {
                        this._trans.stop();
                        this._trans.destroy();
                        this._trans = null;
                        this._cleanup();
                    }
                    return Promise.resolve(false);
                }
            }
            if (leavingView) {
                var _ret = function () {
                    // there is a view ready to leave, meaning that a transition needs
                    // to happen and the previously active view is going to animate out
                    // get the view thats ready to enter
                    var enteringView = _this3.getByState(STATE_INIT_ENTER);
                    if (!enteringView && !_this3.isPortal) {
                        // oh nos! no entering view to go to!
                        // if there is no previous view that would enter in this nav stack
                        // and the option is set to climb up the nav parent looking
                        // for the next nav we could transition to instead
                        if (opts.climbNav) {
                            var parentNav = _this3.parent;
                            while (parentNav) {
                                if (!parentNav['_tabs']) {
                                    // Tabs can be a parent, but it is not a collection of views
                                    // only we're looking for an actual NavController w/ stack of views
                                    leavingView.fireWillLeave();
                                    _this3.viewWillLeave.emit(leavingView);
                                    _this3._app.viewWillLeave.emit(leavingView);
                                    return {
                                        v: parentNav.pop(opts).then(function (rtnVal) {
                                            leavingView.fireDidLeave();
                                            _this3.viewDidLeave.emit(leavingView);
                                            _this3._app.viewDidLeave.emit(leavingView);
                                            return rtnVal;
                                        })
                                    };
                                }
                                parentNav = parentNav.parent;
                            }
                        }
                        // there's no previous view and there's no valid parent nav
                        // to climb to so this shouldn't actually remove the leaving
                        // view because there's nothing that would enter, eww
                        leavingView.state = STATE_ACTIVE;
                        return {
                            v: Promise.resolve(false)
                        };
                    }
                    var resolve = void 0;
                    var promise = new Promise(function (res) {
                        resolve = res;
                    });
                    if (!opts.animation) {
                        opts.animation = leavingView.getTransitionName(opts.direction);
                    }
                    // start the transition, fire resolve when done...
                    _this3._transition(enteringView, leavingView, opts, function (hasCompleted) {
                        // transition has completed!!
                        resolve(hasCompleted);
                    });
                    return {
                        v: promise
                    };
                }();

                if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v;
            }
            // no need to transition when the active view isn't being removed
            // there's still an active view after _remove() figured out states
            // so this means views that were only removed before the active
            // view, so auto-resolve since no transition needs to happen
            return Promise.resolve(false);
        }
        /**
         * @private
         */

    }, {
        key: '_remove',
        value: function _remove(startIndex, removeCount) {
            var _this4 = this;

            // when this is done, there should only be at most
            // 1 STATE_INIT_ENTER and 1 STATE_INIT_LEAVE
            // there should not be any that are STATE_ACTIVE after this is done
            var view = null;
            // loop through each view that is set to be removed
            for (var i = startIndex, ii = removeCount + startIndex; i < ii; i++) {
                view = this.getByIndex(i);
                if (!view) break;
                if (view.state === STATE_TRANS_ENTER || view.state === STATE_TRANS_LEAVE) {
                    // oh no!!! this view should be removed, but it's
                    // actively transitioning in at the moment!!
                    // since it's viewable right now, let's just set that
                    // it should be removed after the transition
                    view.state = STATE_REMOVE_AFTER_TRANS;
                } else if (view.state === STATE_INIT_ENTER) {
                    // asked to be removed before it even entered!
                    view.state = STATE_CANCEL_ENTER;
                } else {
                    // if this view is already leaving then no need to immediately
                    // remove it, otherwise set the remove state
                    // this is useful if the view being removed isn't going to
                    // animate out, but just removed from the stack, no transition
                    view.state = STATE_REMOVE;
                }
            }
            if (view = this.getByState(STATE_INIT_LEAVE)) {
                // looks like there's already an active leaving view
                // reassign previous entering view to just be inactive
                var enteringView = this.getByState(STATE_INIT_ENTER);
                if (enteringView) {
                    enteringView.state = STATE_INACTIVE;
                }
                // from the index of the leaving view, go backwards and
                // find the first view that is inactive
                for (var i = this.indexOf(view) - 1; i >= 0; i--) {
                    if (this._views[i].state === STATE_INACTIVE) {
                        this._views[i].state = STATE_INIT_ENTER;
                        break;
                    }
                }
            } else if (view = this.getByState(STATE_TRANS_LEAVE)) {
                // an active transition is happening, but a new transition
                // still needs to happen force this view to be the active one
                view.state = STATE_FORCE_ACTIVE;
            } else if (view = this.getByState(STATE_REMOVE)) {
                // there is no active transition about to happen
                // find the first view that is supposed to be removed and
                // set that it is the init leaving view
                // the first view to be removed, it should init leave
                view.state = STATE_INIT_LEAVE;
                view.fireWillUnload();
                this.viewWillUnload.emit(view);
                this._app.viewWillUnload.emit(view);
                // from the index of the leaving view, go backwards and
                // find the first view that is inactive so it can be the entering
                for (var i = this.indexOf(view) - 1; i >= 0; i--) {
                    if (this._views[i].state === STATE_INACTIVE) {
                        this._views[i].state = STATE_INIT_ENTER;
                        break;
                    }
                }
            }
            // if there is still an active view, then it wasn't one that was
            // set to be removed, so there actually won't be a transition at all
            view = this.getActive();
            if (view) {
                // the active view remains untouched, so all the removes
                // must have happened before it, so really no need for transition
                view = this.getByState(STATE_INIT_ENTER);
                if (view) {
                    // if it was going to enter, then just make inactive
                    view.state = STATE_INACTIVE;
                }
                view = this.getByState(STATE_INIT_LEAVE);
                if (view) {
                    // this was going to leave, so just remove it completely
                    view.state = STATE_REMOVE;
                }
            }
            // remove views that have been set to be removed, but not
            // apart of any transitions that will eventually happen
            this._views.filter(function (v) {
                return v.state === STATE_REMOVE;
            }).forEach(function (view) {
                view.fireWillLeave();
                view.fireDidLeave();
                _this4._views.splice(_this4.indexOf(view), 1);
                view.destroy();
            });
            return this.getByState(STATE_INIT_LEAVE);
        }
        /**
         * @private
         */

    }, {
        key: '_transition',
        value: function _transition(enteringView, leavingView, opts, done) {
            var _this5 = this;

            var transId = ++this._transIds;
            if (enteringView === leavingView) {
                // if the entering view and leaving view are the same thing don't continue
                this._transFinish(transId, enteringView, leavingView, null, false);
                return done(false);
            }
            if (isBlank(opts)) {
                opts = {};
            }
            this._setAnimate(opts);
            if (!leavingView) {
                // if no leaving view then create a bogus one
                leavingView = new ViewController();
            }
            if (!enteringView) {
                // if no entering view then create a bogus one
                enteringView = new ViewController();
                enteringView.fireLoaded();
            }
            /* Async steps to complete a transition
              1. _render: compile the view and render it in the DOM. Load page if it hasn't loaded already. When done call postRender
              2. _postRender: Run willEnter/willLeave, then wait a frame (change detection happens), then call beginTransition
              3. _beforeTrans: Create the transition's animation, play the animation, wait for it to end
              4. _afterTrans: Run didEnter/didLeave, call _transComplete()
              5. _transComplete: Cleanup, remove cache views, then call the final callback
            */
            // begin the multiple async process of transitioning to the entering view
            this._render(transId, enteringView, leavingView, opts, function (hasCompleted) {
                _this5._transFinish(transId, enteringView, leavingView, opts.direction, hasCompleted);
                done(hasCompleted);
            });
        }
        /**
         * @private
         */

    }, {
        key: '_setAnimate',
        value: function _setAnimate(opts) {
            if (this._views.length === 1 && !this._init && !this.isPortal || this.config.get('animate') === false) {
                opts.animate = false;
            }
        }
        /**
         * @private
         */

    }, {
        key: '_render',
        value: function _render(transId, enteringView, leavingView, opts, done) {
            var _this6 = this;

            // compile/load the view into the DOM
            if (enteringView.state === STATE_INACTIVE) {
                // this entering view is already set to inactive, so this
                // transition must be canceled, so don't continue
                return done();
            }
            enteringView.state = STATE_INIT_ENTER;
            leavingView.state = STATE_INIT_LEAVE;
            // remember if this nav is already transitioning or not
            var isAlreadyTransitioning = this.isTransitioning();
            if (enteringView.isLoaded()) {
                // already compiled this view, do not load again and continue
                this._postRender(transId, enteringView, leavingView, isAlreadyTransitioning, opts, done);
            } else {
                // view has not been compiled/loaded yet
                // continue once the view has finished compiling
                // DOM WRITE
                this.setTransitioning(true, 500);
                this.loadPage(enteringView, this._viewport, opts, function () {
                    enteringView.fireLoaded();
                    _this6.viewDidLoad.emit(enteringView);
                    _this6._app.viewDidLoad.emit(enteringView);
                    _this6._postRender(transId, enteringView, leavingView, isAlreadyTransitioning, opts, done);
                });
            }
        }
        /**
         * @private
         */

    }, {
        key: '_postRender',
        value: function _postRender(transId, enteringView, leavingView, isAlreadyTransitioning, opts, done) {
            // called after _render has completed and the view is compiled/loaded
            if (enteringView.state === STATE_INACTIVE) {
                // this entering view is already set to inactive, so this
                // transition must be canceled, so don't continue
                return done();
            }
            if (!opts.preload) {
                // the enteringView will become the active view, and is not being preloaded
                // set the correct zIndex for the entering and leaving views
                // if there's already another trans_enter happening then
                // the zIndex for the entering view should go off of that one
                // DOM WRITE
                var lastestLeavingView = this.getByState(STATE_TRANS_ENTER) || leavingView;
                this._setZIndex(enteringView, lastestLeavingView, opts.direction);
                // make sure the entering and leaving views are showing
                // DOM WRITE
                if (isAlreadyTransitioning) {
                    // the previous transition was still going when this one started
                    // so to be safe, only update showing the entering/leaving
                    // don't hide the others when they could still be transitioning
                    enteringView.domShow(true, this._renderer);
                    leavingView.domShow(true, this._renderer);
                } else {
                    // there are no other transitions happening but this one
                    // only entering/leaving should show, all others hidden
                    // also if a view is an overlay or the previous view is an
                    // overlay then always show the overlay and the view before it
                    var view;
                    var shouldShow;
                    for (var i = 0, ii = this._views.length; i < ii; i++) {
                        view = this._views[i];
                        shouldShow = view === enteringView || view === leavingView || view.isOverlay || (i < ii - 1 ? this._views[i + 1].isOverlay : false);
                        view.domShow(shouldShow, this._renderer);
                    }
                }
                // call each view's lifecycle events
                if (leavingView.fireOtherLifecycles) {
                    // only fire entering lifecycle if the leaving
                    // view hasn't explicitly set not to
                    enteringView.fireWillEnter();
                    this.viewWillEnter.emit(enteringView);
                    this._app.viewWillEnter.emit(enteringView);
                }
                if (enteringView.fireOtherLifecycles) {
                    // only fire leaving lifecycle if the entering
                    // view hasn't explicitly set not to
                    leavingView.fireWillLeave();
                    this.viewWillLeave.emit(leavingView);
                    this._app.viewWillLeave.emit(leavingView);
                }
            } else {
                // this view is being preloaded, don't call lifecycle events
                // transition does not need to animate
                opts.animate = false;
            }
            this._beforeTrans(enteringView, leavingView, opts, done);
        }
        /**
         * @private
         */

    }, {
        key: '_beforeTrans',
        value: function _beforeTrans(enteringView, leavingView, opts, done) {
            var _this7 = this;

            // called after one raf from postRender()
            // create the transitions animation, play the animation
            // when the transition ends call wait for it to end
            if (enteringView.state === STATE_INACTIVE || enteringView.state === STATE_CANCEL_ENTER) {
                // this entering view is already set to inactive or has been canceled
                // so this transition must not begin, so don't continue
                return done();
            }
            enteringView.state = STATE_TRANS_ENTER;
            leavingView.state = STATE_TRANS_LEAVE;
            // everything during the transition should runOutsideAngular
            this._zone.runOutsideAngular(function () {
                // init the transition animation
                var transitionOpts = {
                    animation: opts.animation,
                    direction: opts.direction,
                    duration: opts.duration,
                    easing: opts.easing,
                    renderDelay: opts.transitionDelay || _this7._trnsDelay,
                    isRTL: _this7.config.platform.isRTL(),
                    ev: opts.ev
                };
                var transAnimation = _this7._createTrans(enteringView, leavingView, transitionOpts);
                _this7._trans && _this7._trans.destroy();
                _this7._trans = transAnimation;
                if (opts.animate === false) {
                    // force it to not animate the elements, just apply the "to" styles
                    transAnimation.duration(0);
                }
                // check if a parent is transitioning and get the time that it ends
                var parentTransitionEndTime = _this7._getLongestTrans(Date.now());
                if (parentTransitionEndTime > 0) {
                    // the parent is already transitioning and has disabled the app
                    // so just update the local transitioning information
                    var duration = parentTransitionEndTime - Date.now();
                    _this7.setTransitioning(true, duration);
                } else {
                    // this is the only active transition (for now), so disable the app
                    var keyboardDurationPadding = 0;
                    if (_this7._keyboard.isOpen()) {
                        // add XXms to the duration the app is disabled when the keyboard is open
                        keyboardDurationPadding = 600;
                    }
                    var _duration = transAnimation.getDuration() + keyboardDurationPadding;
                    var enableApp = _duration < 64;
                    _this7._app.setEnabled(enableApp, _duration);
                    _this7.setTransitioning(!enableApp, _duration);
                }
                // create a callback for when the animation is done
                transAnimation.onFinish(function (trans) {
                    // transition animation has ended
                    // destroy the animation and it's element references
                    trans.destroy();
                    _this7._afterTrans(enteringView, leavingView, opts, trans.hasCompleted, done);
                });
                // cool, let's do this, start the transition
                if (opts.progressAnimation) {
                    // this is a swipe to go back, just get the transition progress ready
                    // kick off the swipe animation start
                    transAnimation.progressStart();
                } else {
                    // this is a normal animation
                    // kick it off and let it play through
                    transAnimation.play();
                }
            });
        }
        /**
         * @private
         */

    }, {
        key: '_afterTrans',
        value: function _afterTrans(enteringView, leavingView, opts, hasCompleted, done) {
            var _this8 = this;

            // transition has completed, update each view's state
            // place back into the zone, run didEnter/didLeave
            // call the final callback when done
            // run inside of the zone again
            this._zone.run(function () {
                if (!opts.preload && hasCompleted) {
                    if (leavingView.fireOtherLifecycles) {
                        // only fire entering lifecycle if the leaving
                        // view hasn't explicitly set not to
                        enteringView.fireDidEnter();
                        _this8.viewDidEnter.emit(enteringView);
                        _this8._app.viewDidEnter.emit(enteringView);
                    }
                    if (enteringView.fireOtherLifecycles && _this8._init) {
                        // only fire leaving lifecycle if the entering
                        // view hasn't explicitly set not to
                        // and after the nav has initialized
                        leavingView.fireDidLeave();
                        _this8.viewDidLeave.emit(leavingView);
                        _this8._app.viewDidLeave.emit(leavingView);
                    }
                }
                if (enteringView.state === STATE_INACTIVE) {
                    // this entering view is already set to inactive, so this
                    // transition must be canceled, so don't continue
                    return done(hasCompleted);
                }
                if (opts.keyboardClose !== false && _this8._keyboard.isOpen()) {
                    // the keyboard is still open!
                    // no problem, let's just close for them
                    _this8._keyboard.close();
                    _this8._keyboard.onClose(function () {
                        // keyboard has finished closing, transition complete
                        done(hasCompleted);
                    }, 32);
                } else {
                    // all good, transition complete
                    done(hasCompleted);
                }
            });
        }
        /**
         * @private
         */

    }, {
        key: '_transFinish',
        value: function _transFinish(transId, enteringView, leavingView, direction, hasCompleted) {
            var _this9 = this;

            // a transition has completed, but not sure if it's the last one or not
            // check if this transition is the most recent one or not
            if (enteringView.state === STATE_CANCEL_ENTER) {
                // this view was told to leave before it finished entering
                this.remove(enteringView.index, 1);
            }
            if (transId === this._transIds) {
                // ok, good news, there were no other transitions that kicked
                // off during the time this transition started and ended
                if (hasCompleted) {
                    // this transition has completed as normal
                    // so the entering one is now the active view
                    // and the leaving view is now just inactive
                    if (enteringView.state !== STATE_REMOVE_AFTER_TRANS) {
                        enteringView.state = STATE_ACTIVE;
                    }
                    if (leavingView.state !== STATE_REMOVE_AFTER_TRANS) {
                        leavingView.state = STATE_INACTIVE;
                    }
                    // only need to do all this clean up if the transition
                    // completed, otherwise nothing actually changed
                    // destroy all of the views that come after the active view
                    this._cleanup();
                    // make sure only this entering view and PREVIOUS view are the
                    // only two views that are not display:none
                    // do not make any changes to the stack's current visibility
                    // if there is an overlay somewhere in the stack
                    leavingView = this.getPrevious(enteringView);
                    if (this.hasOverlay()) {
                        // ensure the entering view is showing
                        enteringView.domShow(true, this._renderer);
                    } else {
                        // only possibly hide a view if there are no overlays in the stack
                        this._views.forEach(function (view) {
                            var shouldShow = view === enteringView || view === leavingView;
                            view.domShow(shouldShow, _this9._renderer);
                        });
                    }
                    // this check only needs to happen once, which will add the css
                    // class to the nav when it's finished its first transition
                    this._init = true;
                } else {
                    // this transition has not completed, meaning the
                    // entering view did not end up as the active view
                    // this would happen when swipe to go back started
                    // but the user did not complete the swipe and the
                    // what was the active view stayed as the active view
                    leavingView.state = STATE_ACTIVE;
                    enteringView.state = STATE_INACTIVE;
                }
                // check if there is a parent actively transitioning
                var transitionEndTime = this._getLongestTrans(Date.now());
                // if transitionEndTime is greater than 0, there is a parent transition occurring
                // so delegate enabling the app to the parent.  If it <= 0, go ahead and enable the app
                if (transitionEndTime <= 0) {
                    this._app && this._app.setEnabled(true);
                }
                this.setTransitioning(false);
                if (direction !== null && hasCompleted && !this.isPortal) {
                    // notify router of the state change if a direction was provided
                    // multiple routers can exist and each should be notified
                    this.routers.forEach(function (router) {
                        router.stateChange(direction, enteringView);
                    });
                }
                // see if we should add the swipe back gesture listeners or not
                this._sbCheck();
            } else {
                // darn, so this wasn't the most recent transition
                // so while this one did end, there's another more recent one
                // still going on. Because a new transition is happening,
                // then this entering view isn't actually going to be the active
                // one, so only update the state to active/inactive if the state
                // wasn't already updated somewhere else during its transition
                if (enteringView.state === STATE_TRANS_ENTER) {
                    enteringView.state = STATE_INACTIVE;
                }
                if (leavingView.state === STATE_TRANS_LEAVE) {
                    leavingView.state = STATE_INACTIVE;
                }
            }
        }
        /**
         *@private
         * This method is just a wrapper to the Transition function of same name
         * to make it easy/possible to mock the method call by overriding the function.
         * In testing we don't want to actually do the animation, we want to return a stub instead
         */

    }, {
        key: '_createTrans',
        value: function _createTrans(enteringView, leavingView, transitionOpts) {
            return Transition.createTransition(enteringView, leavingView, transitionOpts);
        }
    }, {
        key: '_cleanup',
        value: function _cleanup() {
            var _this10 = this;

            // ok, cleanup time!! Destroy all of the views that are
            // INACTIVE and come after the active view
            var activeViewIndex = this.indexOf(this.getActive());
            var destroys = this._views.filter(function (v) {
                return v.state === STATE_REMOVE_AFTER_TRANS;
            });
            for (var i = activeViewIndex + 1; i < this._views.length; i++) {
                if (this._views[i].state === STATE_INACTIVE) {
                    destroys.push(this._views[i]);
                }
            }
            // all pages being destroyed should be removed from the list of
            // pages and completely removed from the dom
            destroys.forEach(function (view) {
                _this10._views.splice(_this10.indexOf(view), 1);
                view.destroy();
                _this10.viewDidUnload.emit(view);
                _this10._app.viewDidUnload.emit(view);
            });
            // if any z-index goes under 0, then reset them all
            var shouldResetZIndex = this._views.some(function (v) {
                return v.zIndex < 0;
            });
            if (shouldResetZIndex) {
                this._views.forEach(function (view) {
                    view.setZIndex(view.zIndex + INIT_ZINDEX + 1, _this10._renderer);
                });
            }
        }
        /**
         * @private
         */

    }, {
        key: 'getActiveChildNav',
        value: function getActiveChildNav() {
            return this._children[this._children.length - 1];
        }
        /**
         * @private
         */

    }, {
        key: 'registerChildNav',
        value: function registerChildNav(nav) {
            this._children.push(nav);
        }
        /**
         * @private
         */

    }, {
        key: 'unregisterChildNav',
        value: function unregisterChildNav(nav) {
            var index = this._children.indexOf(nav);
            if (index > -1) {
                this._children.splice(index, 1);
            }
        }
        /**
         * @private
         */

    }, {
        key: 'ngOnDestroy',
        value: function ngOnDestroy() {
            for (var i = this._views.length - 1; i >= 0; i--) {
                this._views[i].destroy();
            }
            this._views.length = 0;
            if (this.parent && this.parent.unregisterChildNav) {
                this.parent.unregisterChildNav(this);
            }
        }
        /**
         * @private
         */

    }, {
        key: 'loadPage',
        value: function loadPage(view, viewport, opts, done) {
            var _this11 = this;

            if (!viewport || !view.componentType) {
                return;
            }
            // TEMPORARY: automatically set selector w/ dah reflector
            // TODO: use componentFactory.create once fixed
            addSelector(view.componentType, 'ion-page');
            this._compiler.resolveComponent(view.componentType).then(function (componentFactory) {
                if (view.state === STATE_CANCEL_ENTER) {
                    // view may have already been removed from the stack
                    // if so, don't even bother adding it
                    view.destroy();
                    _this11._views.splice(view.index, 1);
                    return;
                }
                // add more providers to just this page
                var componentProviders = ReflectiveInjector.resolve([provide(NavController, { useValue: _this11 }), provide(ViewController, { useValue: view }), provide(NavParams, { useValue: view.getNavParams() })]);
                var childInjector = ReflectiveInjector.fromResolvedProviders(componentProviders, _this11._viewport.parentInjector);
                var componentRef = componentFactory.create(childInjector, null, null);
                viewport.insert(componentRef.hostView, viewport.length);
                // a new ComponentRef has been created
                // set the ComponentRef's instance to its ViewController
                view.setInstance(componentRef.instance);
                // the component has been loaded, so call the view controller's loaded method to load any dependencies into the dom
                view.loaded(function () {
                    // the ElementRef of the actual ion-page created
                    var pageElementRef = componentRef.location;
                    // remember the ChangeDetectorRef for this ViewController
                    view.setChangeDetector(componentRef.changeDetectorRef);
                    // remember the ElementRef to the ion-page elementRef that was just created
                    view.setPageRef(pageElementRef);
                    // auto-add page css className created from component JS class name
                    var cssClassName = pascalCaseToDashCase(view.componentType.name);
                    _this11._renderer.setElementClass(pageElementRef.nativeElement, cssClassName, true);
                    view.onDestroy(function () {
                        // ensure the element is cleaned up for when the view pool reuses this element
                        _this11._renderer.setElementAttribute(pageElementRef.nativeElement, 'class', null);
                        _this11._renderer.setElementAttribute(pageElementRef.nativeElement, 'style', null);
                        componentRef.destroy();
                    });
                    // our job is done here
                    done(view);
                });
            });
        }
        /**
         * @private
         */

    }, {
        key: 'swipeBackStart',
        value: function swipeBackStart() {
            // default the direction to "back"
            var opts = {
                direction: 'back',
                progressAnimation: true
            };
            // figure out the states of each view in the stack
            var leavingView = this._remove(this._views.length - 1, 1);
            if (leavingView) {
                opts.animation = leavingView.getTransitionName(opts.direction);
                // get the view thats ready to enter
                var enteringView = this.getByState(STATE_INIT_ENTER);
                // start the transition, fire callback when done...
                this._transition(enteringView, leavingView, opts, function (hasCompleted) {
                    // swipe back has finished!!
                    console.debug('swipeBack, hasCompleted', hasCompleted);
                });
            }
        }
        /**
         * @private
         */

    }, {
        key: 'swipeBackProgress',
        value: function swipeBackProgress(stepValue) {
            if (this._trans && this._sbGesture) {
                // continue to disable the app while actively dragging
                this._app.setEnabled(false, 4000);
                this.setTransitioning(true, 4000);
                // set the transition animation's progress
                this._trans.progressStep(stepValue);
            }
        }
        /**
         * @private
         */

    }, {
        key: 'swipeBackEnd',
        value: function swipeBackEnd(shouldComplete, currentStepValue) {
            if (this._trans && this._sbGesture) {
                // the swipe back gesture has ended
                this._trans.progressEnd(shouldComplete, currentStepValue);
            }
        }
        /**
         * @private
         */

    }, {
        key: '_sbCheck',
        value: function _sbCheck() {
            var _this12 = this;

            if (this._sbEnabled) {
                // this nav controller can have swipe to go back
                if (!this._sbGesture) {
                    // create the swipe back gesture if we haven't already
                    var opts = {
                        edge: 'left',
                        threshold: this._sbThreshold
                    };
                    var menuCtrl = this._app.getAppInjector().get(MenuController);
                    this._sbGesture = new SwipeBackGesture(this.getNativeElement(), opts, this, menuCtrl);
                }
                if (this.canSwipeBack()) {
                    // it is be possible to swipe back
                    if (!this._sbGesture.isListening) {
                        this._zone.runOutsideAngular(function () {
                            // start listening if it's not already
                            console.debug('swipeBack gesture, listen');
                            _this12._sbGesture.listen();
                        });
                    }
                } else if (this._sbGesture.isListening) {
                    // it should not be possible to swipe back
                    // but the gesture is still listening
                    console.debug('swipeBack gesture, unlisten');
                    this._sbGesture.unlisten();
                }
            }
        }
        /**
         * If it's possible to use swipe back or not. If it's not possible
         * to go back, or swipe back is not enabled, then this will return `false`.
         * If it is possible to go back, and swipe back is enabled, then this
         * will return `true`.
         * @returns {boolean}
         */

    }, {
        key: 'canSwipeBack',
        value: function canSwipeBack() {
            return this._sbEnabled && !this.isTransitioning() && this._app.isEnabled() && this.canGoBack();
        }
        /**
         * Returns `true` if there's a valid previous page that we can pop
         * back to. Otherwise returns `false`.
         * @returns {boolean}
         */

    }, {
        key: 'canGoBack',
        value: function canGoBack() {
            var activeView = this.getActive();
            if (activeView) {
                return activeView.enableBack();
            }
            return false;
        }
        /**
         * Returns if the nav controller is actively transitioning or not.
         * @return {boolean}
         */

    }, {
        key: 'isTransitioning',
        value: function isTransitioning() {
            return this._trnsTime > Date.now();
        }
        /**
         * @private
         */

    }, {
        key: 'setTransitioning',
        value: function setTransitioning(isTransitioning) {
            var fallback = arguments.length <= 1 || arguments[1] === undefined ? 700 : arguments[1];

            this._trnsTime = isTransitioning ? Date.now() + fallback : 0;
        }
        /**
         * @private
         * This method traverses the tree of parents upwards
         * and looks at the time the transition ends (if it's transitioning)
         * and returns the value that is the furthest into the future
         * thus giving us the longest transition duration
         */

    }, {
        key: '_getLongestTrans',
        value: function _getLongestTrans(now) {
            var parentNav = this.parent;
            var transitionEndTime = -1;
            while (parentNav) {
                if (parentNav._trnsTime > transitionEndTime) {
                    transitionEndTime = parentNav._trnsTime;
                }
                parentNav = parentNav.parent;
            }
            // only check if the transitionTime is greater than the current time once
            return transitionEndTime > 0 && transitionEndTime > now ? transitionEndTime : 0;
        }
        /**
         * @private
         */

    }, {
        key: 'hasOverlay',
        value: function hasOverlay() {
            for (var i = this._views.length - 1; i >= 0; i--) {
                if (this._views[i].isOverlay) {
                    return true;
                }
            }
            return false;
        }
        /**
         * @private
         */

    }, {
        key: 'getByState',
        value: function getByState(state) {
            for (var i = this._views.length - 1; i >= 0; i--) {
                if (this._views[i].state === state) {
                    return this._views[i];
                }
            }
            return null;
        }
        /**
         * @param {number} index  The index of the page to get.
         * @returns {ViewController} Returns the view controller that matches the given index.
         */

    }, {
        key: 'getByIndex',
        value: function getByIndex(index) {
            return index < this._views.length && index > -1 ? this._views[index] : null;
        }
        /**
         * @returns {ViewController} Returns the active page's view controller.
         */

    }, {
        key: 'getActive',
        value: function getActive() {
            return this.getByState(STATE_ACTIVE);
        }
        /**
         * @param {ViewController} view
         * @returns {boolean}
         */

    }, {
        key: 'isActive',
        value: function isActive(view) {
            return !!(view && view.state === STATE_ACTIVE);
        }
        /**
         * Returns the view controller which is before the given view controller.
         * @param {ViewController} view
         * @returns {viewController}
         */

    }, {
        key: 'getPrevious',
        value: function getPrevious(view) {
            return this.getByIndex(this.indexOf(view) - 1);
        }
        /**
         * Returns the first view controller in this nav controller's stack.
         * @returns {ViewController}
         */

    }, {
        key: 'first',
        value: function first() {
            return this._views.length ? this._views[0] : null;
        }
        /**
         * Returns the last page in this nav controller's stack.
         * @returns {ViewController}
         */

    }, {
        key: 'last',
        value: function last() {
            return this._views.length ? this._views[this._views.length - 1] : null;
        }
        /**
         * Returns the index number of the given view controller.
         * @param {ViewController} view
         * @returns {number}
         */

    }, {
        key: 'indexOf',
        value: function indexOf(view) {
            return this._views.indexOf(view);
        }
        /**
         * Returns the number of views in this nav controller.
         * @returns {number} The number of views in this stack, including the current view.
         */

    }, {
        key: 'length',
        value: function length() {
            return this._views.length;
        }
        /**
         * @private
         */

    }, {
        key: 'isSwipeBackEnabled',
        value: function isSwipeBackEnabled() {
            return this._sbEnabled;
        }
        /**
         * Returns the root `NavController`.
         * @returns {NavController}
         */

    }, {
        key: 'dismissPageChangeViews',

        /**
         * @private
         * Dismiss all pages which have set the `dismissOnPageChange` property.
         */
        value: function dismissPageChangeViews() {
            this._views.forEach(function (view) {
                if (view.data && view.data.dismissOnPageChange) {
                    view.dismiss();
                }
            });
        }
        /**
         * @private
         */

    }, {
        key: 'registerRouter',
        value: function registerRouter(router) {
            this.routers.push(router);
        }
        /**
         * @private
         */

    }, {
        key: '_incId',
        value: function _incId(view) {
            view.id = this.id + '-' + ++this._ids;
        }
        /**
         * @private
         */

    }, {
        key: '_setZIndex',
        value: function _setZIndex(enteringView, leavingView, direction) {
            if (enteringView) {
                // get the leaving view, which could be in various states
                if (!leavingView || !leavingView.isLoaded()) {
                    // the leavingView is a mocked view, either we're
                    // actively transitioning or it's the initial load
                    var previousView = this.getPrevious(enteringView);
                    if (previousView && previousView.isLoaded()) {
                        // we found a better previous view to reference
                        // use this one instead
                        enteringView.setZIndex(previousView.zIndex + 1, this._renderer);
                    } else {
                        // this is the initial view
                        enteringView.setZIndex(this.isPortal ? PORTAL_ZINDEX : INIT_ZINDEX, this._renderer);
                    }
                } else if (direction === 'back') {
                    // moving back
                    enteringView.setZIndex(leavingView.zIndex - 1, this._renderer);
                } else {
                    // moving forward
                    enteringView.setZIndex(leavingView.zIndex + 1, this._renderer);
                }
            }
        }
    }, {
        key: 'rootNav',
        get: function get() {
            var nav = this;
            while (nav.parent) {
                nav = nav.parent;
            }
            return nav;
        }
    }]);

    return NavController;
}(Ion);

export { NavController };

var STATE_ACTIVE = 1;
var STATE_INACTIVE = 2;
var STATE_INIT_ENTER = 3;
var STATE_INIT_LEAVE = 4;
var STATE_TRANS_ENTER = 5;
var STATE_TRANS_LEAVE = 6;
var STATE_REMOVE = 7;
var STATE_REMOVE_AFTER_TRANS = 8;
var STATE_CANCEL_ENTER = 9;
var STATE_FORCE_ACTIVE = 10;
var INIT_ZINDEX = 100;
var PORTAL_ZINDEX = 9999;
var ctrlIds = -1;