"use strict";
var __extends = (this && this.__extends) || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var core_1 = require('@angular/core');
var bootstrap_1 = require('../../config/bootstrap');
var ion_1 = require('../ion');
var util_1 = require('../../util/util');
var nav_params_1 = require('./nav-params');
var swipe_back_1 = require('./swipe-back');
var transition_1 = require('../../transitions/transition');
var view_controller_1 = require('./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 (_super) {
    __extends(NavController, _super);
    function NavController(parent, _app, config, _keyboard, elementRef, _zone, _renderer, _compiler, _gestureCtrl) {
        _super.call(this, elementRef);
        this._app = _app;
        this._keyboard = _keyboard;
        this._zone = _zone;
        this._renderer = _renderer;
        this._compiler = _compiler;
        this._gestureCtrl = _gestureCtrl;
        this._transIds = 0;
        this._init = false;
        this._children = [];
        this._ids = -1;
        this._views = [];
        /**
         * @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 core_1.EventEmitter();
        this.viewWillEnter = new core_1.EventEmitter();
        this.viewDidEnter = new core_1.EventEmitter();
        this.viewWillLeave = new core_1.EventEmitter();
        this.viewDidLeave = new core_1.EventEmitter();
        this.viewWillUnload = new core_1.EventEmitter();
        this.viewDidUnload = new core_1.EventEmitter();
    }
    /**
     * @private
     */
    NavController.prototype.setViewport = function (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.
     */
    NavController.prototype.setRoot = function (page, params, opts) {
        return this.setPages([{ page: page, params: params }], opts);
    };
    /**
     * Set the views of the current navigation stack and navigate to the
     * last view. By default animations are disabled, but they can be enabled
     * by passing options to the navigation controller.You can also pass any
     * navigation params to the individual pages in the array.
     *
     * @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.
     */
    NavController.prototype.setPages = function (pages, opts) {
        if (!pages || !pages.length) {
            return Promise.resolve(false);
        }
        if (util_1.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 view_controller_1.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 || exports.DIRECTION_BACK;
        var resolve;
        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 a new component onto the current navication stack. Pass any aditional information
     * along as an object. This additional information is acessible through NavParams
     *
     * @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.
     */
    NavController.prototype.push = function (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.
     */
    NavController.prototype.present = function (enteringView, opts) {
        // deprecated warning: added beta.11 2016-06-27
        void 0;
        return Promise.resolve();
    };
    /**
     * Inserts a component into the nav stack at the specified index. This is useful if
     * you need to add a component at any point in your 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.
     */
    NavController.prototype.insert = function (insertIndex, page, params, opts) {
        return this.insertPages(insertIndex, [{ page: page, params: params }], opts);
    };
    /**
     * Inserts an array of components into the nav stack at the specified index.
     * The last component in the array will animate in and become the active component
     *
     * @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.
     */
    NavController.prototype.insertPages = function (insertIndex, insertPages, opts) {
        var views = insertPages.map(function (p) { return new view_controller_1.ViewController(p.page, p.params); });
        return this.insertViews(insertIndex, views, opts);
    };
    /**
     * @private
     */
    NavController.prototype.insertViews = function (insertIndex, insertViews, opts) {
        if (!insertViews || !insertViews.length) {
            return Promise.reject('invalid pages');
        }
        if (util_1.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;
        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
     */
    NavController.prototype._insert = function (insertIndex, insertViews) {
        // 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 _this = this;
        // 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(_this);
            view.state = STATE_INACTIVE;
            // give this inserted view an ID
            view.id = _this.id + '-' + (++_this._ids);
            // insert the entering view into the correct index in the stack
            _this._views.splice(insertIndex + i, 0, view);
        });
        if (insertView) {
            insertView.state = STATE_INIT_ENTER;
        }
        return insertView;
    };
    /**
     * Call to navigate back from a current component. Similar to `push()`, you
     * can also pass navigation options.
     *
     * @param {object} [opts={}] Nav options to go with this transition.
     * @returns {Promise} Returns a promise which is resolved when the transition has completed.
     */
    NavController.prototype.pop = function (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 (util_1.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 (util_1.isBlank(opts.climbNav)) {
            opts.climbNav = true;
        }
        return this.remove(this.indexOf(activeView), 1, opts);
    };
    /**
     * Navigate back to the root of the stack, no matter how far 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.
     */
    NavController.prototype.popToRoot = function (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.
     */
    NavController.prototype.popTo = function (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.
     *
     * @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.
     */
    NavController.prototype.remove = function (startIndex, removeCount, opts) {
        var _this = this;
        if (startIndex === void 0) { startIndex = -1; }
        if (removeCount === void 0) { removeCount = 1; }
        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 (util_1.isBlank(opts)) {
            opts = {};
        }
        // default the direction to "back"
        opts.direction = opts.direction || exports.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) {
            // 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 = this.getByState(STATE_INIT_ENTER);
            if (!enteringView && !this.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 = this.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();
                            this.viewWillLeave.emit(leavingView);
                            this._app.viewWillLeave.emit(leavingView);
                            return parentNav.pop(opts).then(function (rtnVal) {
                                leavingView.fireDidLeave();
                                _this.viewDidLeave.emit(leavingView);
                                _this._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 Promise.resolve(false);
            }
            var resolve_1;
            var promise = new Promise(function (res) { resolve_1 = res; });
            if (!opts.animation) {
                opts.animation = leavingView.getTransitionName(opts.direction);
            }
            // start the transition, fire resolve when done...
            this._transition(enteringView, leavingView, opts, function (hasCompleted) {
                // transition has completed!!
                resolve_1(hasCompleted);
            });
            return promise;
        }
        // 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
     */
    NavController.prototype._remove = function (startIndex, removeCount) {
        var _this = 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();
            _this._views.splice(_this.indexOf(view), 1);
            view.destroy();
        });
        return this.getByState(STATE_INIT_LEAVE);
    };
    /**
     * @private
     */
    NavController.prototype._transition = function (enteringView, leavingView, opts, done) {
        var _this = 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 (util_1.isBlank(opts)) {
            opts = {};
        }
        this._setAnimate(opts);
        if (!leavingView) {
            // if no leaving view then create a bogus one
            leavingView = new view_controller_1.ViewController();
        }
        if (!enteringView) {
            // if no entering view then create a bogus one
            enteringView = new view_controller_1.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) {
            _this._transFinish(transId, enteringView, leavingView, opts.direction, hasCompleted);
            done(hasCompleted);
        });
    };
    /**
     * @private
     */
    NavController.prototype._setAnimate = function (opts) {
        if ((this._views.length === 1 && !this._init && !this.isPortal) || this.config.get('animate') === false) {
            opts.animate = false;
        }
    };
    /**
     * @private
     */
    NavController.prototype._render = function (transId, enteringView, leavingView, opts, done) {
        // compile/load the view into the DOM
        var _this = this;
        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();
                _this.viewDidLoad.emit(enteringView);
                _this._app.viewDidLoad.emit(enteringView);
                _this._postRender(transId, enteringView, leavingView, isAlreadyTransitioning, opts, done);
            });
        }
    };
    /**
     * @private
     */
    NavController.prototype._postRender = function (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
     */
    NavController.prototype._beforeTrans = function (enteringView, leavingView, opts, done) {
        // called after one raf from postRender()
        // create the transitions animation, play the animation
        // when the transition ends call wait for it to end
        var _this = this;
        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 || _this._trnsDelay,
                isRTL: _this.config.platform.isRTL(),
                ev: opts.ev,
            };
            var transAnimation = _this._createTrans(enteringView, leavingView, transitionOpts);
            _this._trans && _this._trans.destroy();
            _this._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 = _this._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();
                _this.setTransitioning(true, duration);
            }
            else {
                // this is the only active transition (for now), so disable the app
                var keyboardDurationPadding = 0;
                if (_this._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);
                _this._app.setEnabled(enableApp, duration);
                _this.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();
                _this._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
     */
    NavController.prototype._afterTrans = function (enteringView, leavingView, opts, hasCompleted, done) {
        // transition has completed, update each view's state
        // place back into the zone, run didEnter/didLeave
        // call the final callback when done
        var _this = this;
        // 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();
                    _this.viewDidEnter.emit(enteringView);
                    _this._app.viewDidEnter.emit(enteringView);
                }
                if (enteringView.fireOtherLifecycles && _this._init) {
                    // only fire leaving lifecycle if the entering
                    // view hasn't explicitly set not to
                    // and after the nav has initialized
                    leavingView.fireDidLeave();
                    _this.viewDidLeave.emit(leavingView);
                    _this._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 && _this._keyboard.isOpen()) {
                // the keyboard is still open!
                // no problem, let's just close for them
                _this._keyboard.close();
                _this._keyboard.onClose(function () {
                    // keyboard has finished closing, transition complete
                    done(hasCompleted);
                }, 32);
            }
            else {
                // all good, transition complete
                done(hasCompleted);
            }
        });
    };
    /**
     * @private
     */
    NavController.prototype._transFinish = function (transId, enteringView, leavingView, direction, hasCompleted) {
        // 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
        var _this = this;
        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, _this._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);
            }
            // update that this nav is not longer actively transitioning
            this.setTransitioning(false);
            // 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
     */
    NavController.prototype._createTrans = function (enteringView, leavingView, transitionOpts) {
        return transition_1.Transition.createTransition(enteringView, leavingView, transitionOpts);
    };
    NavController.prototype._cleanup = function () {
        var _this = 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) {
            _this._views.splice(_this.indexOf(view), 1);
            view.destroy();
            _this.viewDidUnload.emit(view);
            _this._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, _this._renderer);
            });
        }
    };
    /**
     * @private
     */
    NavController.prototype.getActiveChildNav = function () {
        return this._children[this._children.length - 1];
    };
    /**
     * @private
     */
    NavController.prototype.registerChildNav = function (nav) {
        this._children.push(nav);
    };
    /**
     * @private
     */
    NavController.prototype.unregisterChildNav = function (nav) {
        var index = this._children.indexOf(nav);
        if (index > -1) {
            this._children.splice(index, 1);
        }
    };
    /**
     * @private
     */
    NavController.prototype.ngOnDestroy = function () {
        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
     */
    NavController.prototype.loadPage = function (view, viewport, opts, done) {
        var _this = this;
        if (!viewport || !view.componentType) {
            return;
        }
        // TEMPORARY: automatically set selector w/ dah reflector
        // TODO: use componentFactory.create once fixed
        bootstrap_1.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();
                _this._views.splice(view.index, 1);
                return;
            }
            // add more providers to just this page
            var componentProviders = core_1.ReflectiveInjector.resolve([
                core_1.provide(NavController, { useValue: _this }),
                core_1.provide(view_controller_1.ViewController, { useValue: view }),
                core_1.provide(nav_params_1.NavParams, { useValue: view.getNavParams() })
            ]);
            var childInjector = core_1.ReflectiveInjector.fromResolvedProviders(componentProviders, _this._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 = util_1.pascalCaseToDashCase(view.componentType.name);
                _this._renderer.setElementClass(pageElementRef.nativeElement, cssClassName, true);
                view.onDestroy(function () {
                    // ensure the element is cleaned up for when the view pool reuses this element
                    _this._renderer.setElementAttribute(pageElementRef.nativeElement, 'class', null);
                    _this._renderer.setElementAttribute(pageElementRef.nativeElement, 'style', null);
                    componentRef.destroy();
                });
                // our job is done here
                done(view);
            });
        });
    };
    /**
     * @private
     */
    NavController.prototype.swipeBackStart = function () {
        // default the direction to "back"
        var opts = {
            direction: exports.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!!
                void 0;
            });
        }
    };
    /**
     * @private
     */
    NavController.prototype.swipeBackProgress = function (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
     */
    NavController.prototype.swipeBackEnd = function (shouldComplete, currentStepValue) {
        if (this._trans && this._sbGesture) {
            // the swipe back gesture has ended
            this._trans.progressEnd(shouldComplete, currentStepValue);
        }
    };
    /**
     * @private
     */
    NavController.prototype._sbCheck = function () {
        var _this = 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
                };
                this._sbGesture = new swipe_back_1.SwipeBackGesture(this.getNativeElement(), opts, this, this._gestureCtrl);
            }
            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
                        void 0;
                        _this._sbGesture.listen();
                    });
                }
            }
            else if (this._sbGesture.isListening) {
                // it should not be possible to swipe back
                // but the gesture is still listening
                void 0;
                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}
     */
    NavController.prototype.canSwipeBack = function () {
        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}
     */
    NavController.prototype.canGoBack = function () {
        var activeView = this.getActive();
        if (activeView) {
            return activeView.enableBack();
        }
        return false;
    };
    /**
     * Returns if the nav controller is actively transitioning or not.
     * @return {boolean}
     */
    NavController.prototype.isTransitioning = function (includeAncestors) {
        var now = Date.now();
        if (includeAncestors && this._getLongestTrans(now) > 0) {
            return true;
        }
        return (this.trnsTime > now);
    };
    /**
     * @private
     */
    NavController.prototype.setTransitioning = function (isTransitioning, fallback) {
        if (fallback === void 0) { fallback = 700; }
        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
     */
    NavController.prototype._getLongestTrans = function (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
     */
    NavController.prototype.hasOverlay = function () {
        for (var i = this._views.length - 1; i >= 0; i--) {
            if (this._views[i].isOverlay) {
                return true;
            }
        }
        return false;
    };
    /**
     * @private
     */
    NavController.prototype.getByState = function (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.
     */
    NavController.prototype.getByIndex = function (index) {
        return (index < this._views.length && index > -1 ? this._views[index] : null);
    };
    /**
     * @returns {ViewController} Returns the active page's view controller.
     */
    NavController.prototype.getActive = function () {
        return this.getByState(STATE_ACTIVE);
    };
    /**
     * @param {ViewController} view
     * @returns {boolean}
     */
    NavController.prototype.isActive = function (view) {
        return !!(view && view.state === STATE_ACTIVE);
    };
    /**
     * Returns the view controller which is before the given view controller.
     * @param {ViewController} view
     * @returns {viewController}
     */
    NavController.prototype.getPrevious = function (view) {
        return this.getByIndex(this.indexOf(view) - 1);
    };
    /**
     * Returns the first view controller in this nav controller's stack.
     * @returns {ViewController}
     */
    NavController.prototype.first = function () {
        return (this._views.length ? this._views[0] : null);
    };
    /**
     * Returns the last page in this nav controller's stack.
     * @returns {ViewController}
     */
    NavController.prototype.last = function () {
        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}
     */
    NavController.prototype.indexOf = function (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.
     */
    NavController.prototype.length = function () {
        return this._views.length;
    };
    /**
     * @private
     */
    NavController.prototype.isSwipeBackEnabled = function () {
        return this._sbEnabled;
    };
    Object.defineProperty(NavController.prototype, "rootNav", {
        /**
         * Returns the root `NavController`.
         * @returns {NavController}
         */
        get: function () {
            var nav = this;
            while (nav.parent) {
                nav = nav.parent;
            }
            return nav;
        },
        enumerable: true,
        configurable: true
    });
    /**
     * @private
     * Dismiss all pages which have set the `dismissOnPageChange` property.
     */
    NavController.prototype.dismissPageChangeViews = function () {
        this._views.forEach(function (view) {
            if (view.data && view.data.dismissOnPageChange) {
                view.dismiss();
            }
        });
    };
    /**
     * @private
     */
    NavController.prototype._setZIndex = function (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 === exports.DIRECTION_BACK) {
                // moving back
                enteringView.setZIndex(leavingView.zIndex - 1, this._renderer);
            }
            else {
                // moving forward
                enteringView.setZIndex(leavingView.zIndex + 1, this._renderer);
            }
        }
    };
    return NavController;
}(ion_1.Ion));
exports.NavController = 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;
exports.DIRECTION_BACK = 'back';
exports.DIRECTION_FORWARD = 'forward';
var INIT_ZINDEX = 100;
var PORTAL_ZINDEX = 9999;
var ctrlIds = -1;
