UNPKG

30.2 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', { value: true });
4
5const index = require('./index-a0a08b2a.js');
6const ionicGlobal = require('./ionic-global-06f21c1a.js');
7const cubicBezier = require('./cubic-bezier-0b2ccc35.js');
8const helpers = require('./helpers-d381ec4d.js');
9const index$1 = require('./index-222357e4.js');
10const frameworkDelegate = require('./framework-delegate-45524d8c.js');
11
12const VIEW_STATE_NEW = 1;
13const VIEW_STATE_ATTACHED = 2;
14const VIEW_STATE_DESTROYED = 3;
15class ViewController {
16 constructor(component, params) {
17 this.component = component;
18 this.params = params;
19 this.state = VIEW_STATE_NEW;
20 }
21 async init(container) {
22 this.state = VIEW_STATE_ATTACHED;
23 if (!this.element) {
24 const component = this.component;
25 this.element = await frameworkDelegate.attachComponent(this.delegate, container, component, ['ion-page', 'ion-page-invisible'], this.params);
26 }
27 }
28 /**
29 * DOM WRITE
30 */
31 _destroy() {
32 helpers.assert(this.state !== VIEW_STATE_DESTROYED, 'view state must be ATTACHED');
33 const element = this.element;
34 if (element) {
35 if (this.delegate) {
36 this.delegate.removeViewFromDom(element.parentElement, element);
37 }
38 else {
39 element.remove();
40 }
41 }
42 this.nav = undefined;
43 this.state = VIEW_STATE_DESTROYED;
44 }
45}
46const matches = (view, id, params) => {
47 if (!view) {
48 return false;
49 }
50 if (view.component !== id) {
51 return false;
52 }
53 const currentParams = view.params;
54 if (currentParams === params) {
55 return true;
56 }
57 if (!currentParams && !params) {
58 return true;
59 }
60 if (!currentParams || !params) {
61 return false;
62 }
63 const keysA = Object.keys(currentParams);
64 const keysB = Object.keys(params);
65 if (keysA.length !== keysB.length) {
66 return false;
67 }
68 // Test for A's keys different from B.
69 for (const key of keysA) {
70 if (currentParams[key] !== params[key]) {
71 return false;
72 }
73 }
74 return true;
75};
76const convertToView = (page, params) => {
77 if (!page) {
78 return null;
79 }
80 if (page instanceof ViewController) {
81 return page;
82 }
83 return new ViewController(page, params);
84};
85const convertToViews = (pages) => {
86 return pages.map(page => {
87 if (page instanceof ViewController) {
88 return page;
89 }
90 if ('component' in page) {
91 /**
92 * TODO Ionic 6:
93 * Consider switching to just using `undefined` here
94 * as well as on the public interfaces and on
95 * `NavComponentWithProps`. Previously `pages` was
96 * of type `any[]` so TypeScript did not catch this.
97 */
98 return convertToView(page.component, (page.componentProps === null) ? undefined : page.componentProps);
99 }
100 return convertToView(page, undefined);
101 }).filter(v => v !== null);
102};
103
104const navCss = ":host{left:0;right:0;top:0;bottom:0;position:absolute;contain:layout size style;overflow:hidden;z-index:0}";
105
106const Nav = class {
107 constructor(hostRef) {
108 index.registerInstance(this, hostRef);
109 this.ionNavWillLoad = index.createEvent(this, "ionNavWillLoad", 7);
110 this.ionNavWillChange = index.createEvent(this, "ionNavWillChange", 3);
111 this.ionNavDidChange = index.createEvent(this, "ionNavDidChange", 3);
112 this.transInstr = [];
113 this.animationEnabled = true;
114 this.useRouter = false;
115 this.isTransitioning = false;
116 this.destroyed = false;
117 this.views = [];
118 /**
119 * If `true`, the nav should animate the transition of components.
120 */
121 this.animated = true;
122 }
123 swipeGestureChanged() {
124 if (this.gesture) {
125 this.gesture.enable(this.swipeGesture === true);
126 }
127 }
128 rootChanged() {
129 if (this.root !== undefined) {
130 if (!this.useRouter) {
131 this.setRoot(this.root, this.rootParams);
132 }
133 }
134 }
135 componentWillLoad() {
136 this.useRouter =
137 !!document.querySelector('ion-router') &&
138 !this.el.closest('[no-router]');
139 if (this.swipeGesture === undefined) {
140 const mode = ionicGlobal.getIonMode(this);
141 this.swipeGesture = ionicGlobal.config.getBoolean('swipeBackEnabled', mode === 'ios');
142 }
143 this.ionNavWillLoad.emit();
144 }
145 async componentDidLoad() {
146 this.rootChanged();
147 this.gesture = (await Promise.resolve().then(function () { return require('./swipe-back-e654d7fd.js'); })).createSwipeBackGesture(this.el, this.canStart.bind(this), this.onStart.bind(this), this.onMove.bind(this), this.onEnd.bind(this));
148 this.swipeGestureChanged();
149 }
150 disconnectedCallback() {
151 for (const view of this.views) {
152 index$1.lifecycle(view.element, index$1.LIFECYCLE_WILL_UNLOAD);
153 view._destroy();
154 }
155 if (this.gesture) {
156 this.gesture.destroy();
157 this.gesture = undefined;
158 }
159 // release swipe back gesture and transition
160 this.transInstr.length = this.views.length = 0;
161 this.destroyed = true;
162 }
163 /**
164 * Push a new component onto the current navigation stack. Pass any additional
165 * information along as an object. This additional information is accessible
166 * through NavParams.
167 *
168 * @param component The component to push onto the navigation stack.
169 * @param componentProps Any properties of the component.
170 * @param opts The navigation options.
171 * @param done The transition complete function.
172 */
173 push(component, componentProps, opts, done) {
174 return this.queueTrns({
175 insertStart: -1,
176 insertViews: [{ component, componentProps }],
177 opts
178 }, done);
179 }
180 /**
181 * Inserts a component into the navigation stack at the specified index.
182 * This is useful to add a component at any point in the navigation stack.
183 *
184 * @param insertIndex The index to insert the component at in the stack.
185 * @param component The component to insert into the navigation stack.
186 * @param componentProps Any properties of the component.
187 * @param opts The navigation options.
188 * @param done The transition complete function.
189 */
190 insert(insertIndex, component, componentProps, opts, done) {
191 return this.queueTrns({
192 insertStart: insertIndex,
193 insertViews: [{ component, componentProps }],
194 opts
195 }, done);
196 }
197 /**
198 * Inserts an array of components into the navigation stack at the specified index.
199 * The last component in the array will become instantiated as a view, and animate
200 * in to become the active view.
201 *
202 * @param insertIndex The index to insert the components at in the stack.
203 * @param insertComponents The components to insert into the navigation stack.
204 * @param opts The navigation options.
205 * @param done The transition complete function.
206 */
207 insertPages(insertIndex, insertComponents, opts, done) {
208 return this.queueTrns({
209 insertStart: insertIndex,
210 insertViews: insertComponents,
211 opts
212 }, done);
213 }
214 /**
215 * Pop a component off of the navigation stack. Navigates back from the current
216 * component.
217 *
218 * @param opts The navigation options.
219 * @param done The transition complete function.
220 */
221 pop(opts, done) {
222 return this.queueTrns({
223 removeStart: -1,
224 removeCount: 1,
225 opts
226 }, done);
227 }
228 /**
229 * Pop to a specific index in the navigation stack.
230 *
231 * @param indexOrViewCtrl The index or view controller to pop to.
232 * @param opts The navigation options.
233 * @param done The transition complete function.
234 */
235 popTo(indexOrViewCtrl, opts, done) {
236 const tiConfig = {
237 removeStart: -1,
238 removeCount: -1,
239 opts
240 };
241 if (typeof indexOrViewCtrl === 'object' && indexOrViewCtrl.component) {
242 tiConfig.removeView = indexOrViewCtrl;
243 tiConfig.removeStart = 1;
244 }
245 else if (typeof indexOrViewCtrl === 'number') {
246 tiConfig.removeStart = indexOrViewCtrl + 1;
247 }
248 return this.queueTrns(tiConfig, done);
249 }
250 /**
251 * Navigate back to the root of the stack, no matter how far back that is.
252 *
253 * @param opts The navigation options.
254 * @param done The transition complete function.
255 */
256 popToRoot(opts, done) {
257 return this.queueTrns({
258 removeStart: 1,
259 removeCount: -1,
260 opts
261 }, done);
262 }
263 /**
264 * Removes a component from the navigation stack at the specified index.
265 *
266 * @param startIndex The number to begin removal at.
267 * @param removeCount The number of components to remove.
268 * @param opts The navigation options.
269 * @param done The transition complete function.
270 */
271 removeIndex(startIndex, removeCount = 1, opts, done) {
272 return this.queueTrns({
273 removeStart: startIndex,
274 removeCount,
275 opts
276 }, done);
277 }
278 /**
279 * Set the root for the current navigation stack to a component.
280 *
281 * @param component The component to set as the root of the navigation stack.
282 * @param componentProps Any properties of the component.
283 * @param opts The navigation options.
284 * @param done The transition complete function.
285 */
286 setRoot(component, componentProps, opts, done) {
287 return this.setPages([{ component, componentProps }], opts, done);
288 }
289 /**
290 * Set the views of the current navigation stack and navigate to the last view.
291 * By default animations are disabled, but they can be enabled by passing options
292 * to the navigation controller. Navigation parameters can also be passed to the
293 * individual pages in the array.
294 *
295 * @param views The list of views to set as the navigation stack.
296 * @param opts The navigation options.
297 * @param done The transition complete function.
298 */
299 setPages(views, opts, done) {
300 if (opts == null) {
301 opts = {};
302 }
303 // if animation wasn't set to true then default it to NOT animate
304 if (opts.animated !== true) {
305 opts.animated = false;
306 }
307 return this.queueTrns({
308 insertStart: 0,
309 insertViews: views,
310 removeStart: 0,
311 removeCount: -1,
312 opts
313 }, done);
314 }
315 /** @internal */
316 setRouteId(id, params, direction, animation) {
317 const active = this.getActiveSync();
318 if (matches(active, id, params)) {
319 return Promise.resolve({
320 changed: false,
321 element: active.element
322 });
323 }
324 let resolve;
325 const promise = new Promise(r => (resolve = r));
326 let finish;
327 const commonOpts = {
328 updateURL: false,
329 viewIsReady: enteringEl => {
330 let mark;
331 const p = new Promise(r => (mark = r));
332 resolve({
333 changed: true,
334 element: enteringEl,
335 markVisible: async () => {
336 mark();
337 await finish;
338 }
339 });
340 return p;
341 }
342 };
343 if (direction === 'root') {
344 finish = this.setRoot(id, params, commonOpts);
345 }
346 else {
347 const viewController = this.views.find(v => matches(v, id, params));
348 if (viewController) {
349 finish = this.popTo(viewController, Object.assign(Object.assign({}, commonOpts), { direction: 'back', animationBuilder: animation }));
350 }
351 else if (direction === 'forward') {
352 finish = this.push(id, params, Object.assign(Object.assign({}, commonOpts), { animationBuilder: animation }));
353 }
354 else if (direction === 'back') {
355 finish = this.setRoot(id, params, Object.assign(Object.assign({}, commonOpts), { direction: 'back', animated: true, animationBuilder: animation }));
356 }
357 }
358 return promise;
359 }
360 /** @internal */
361 async getRouteId() {
362 const active = this.getActiveSync();
363 return active
364 ? {
365 id: active.element.tagName,
366 params: active.params,
367 element: active.element
368 }
369 : undefined;
370 }
371 /**
372 * Get the active view.
373 */
374 getActive() {
375 return Promise.resolve(this.getActiveSync());
376 }
377 /**
378 * Get the view at the specified index.
379 *
380 * @param index The index of the view.
381 */
382 getByIndex(index) {
383 return Promise.resolve(this.views[index]);
384 }
385 /**
386 * Returns `true` if the current view can go back.
387 *
388 * @param view The view to check.
389 */
390 canGoBack(view) {
391 return Promise.resolve(this.canGoBackSync(view));
392 }
393 /**
394 * Get the previous view.
395 *
396 * @param view The view to get.
397 */
398 getPrevious(view) {
399 return Promise.resolve(this.getPreviousSync(view));
400 }
401 getLength() {
402 return this.views.length;
403 }
404 getActiveSync() {
405 return this.views[this.views.length - 1];
406 }
407 canGoBackSync(view = this.getActiveSync()) {
408 return !!(view && this.getPreviousSync(view));
409 }
410 getPreviousSync(view = this.getActiveSync()) {
411 if (!view) {
412 return undefined;
413 }
414 const views = this.views;
415 const index = views.indexOf(view);
416 return index > 0 ? views[index - 1] : undefined;
417 }
418 // _queueTrns() adds a navigation stack change to the queue and schedules it to run:
419 // 1. _nextTrns(): consumes the next transition in the queue
420 // 2. _viewInit(): initializes enteringView if required
421 // 3. _viewTest(): ensures canLeave/canEnter Returns `true`, so the operation can continue
422 // 4. _postViewInit(): add/remove the views from the navigation stack
423 // 5. _transitionInit(): initializes the visual transition if required and schedules it to run
424 // 6. _viewAttachToDOM(): attaches the enteringView to the DOM
425 // 7. _transitionStart(): called once the transition actually starts, it initializes the Animation underneath.
426 // 8. _transitionFinish(): called once the transition finishes
427 // 9. _cleanup(): syncs the navigation internal state with the DOM. For example it removes the pages from the DOM or hides/show them.
428 async queueTrns(ti, done) {
429 if (this.isTransitioning && ti.opts != null && ti.opts.skipIfBusy) {
430 return Promise.resolve(false);
431 }
432 const promise = new Promise((resolve, reject) => {
433 ti.resolve = resolve;
434 ti.reject = reject;
435 });
436 ti.done = done;
437 /**
438 * If using router, check to see if navigation hooks
439 * will allow us to perform this transition. This
440 * is required in order for hooks to work with
441 * the ion-back-button or swipe to go back.
442 */
443 if (ti.opts && ti.opts.updateURL !== false && this.useRouter) {
444 const router = document.querySelector('ion-router');
445 if (router) {
446 const canTransition = await router.canTransition();
447 if (canTransition === false) {
448 return Promise.resolve(false);
449 }
450 else if (typeof canTransition === 'string') {
451 router.push(canTransition, ti.opts.direction || 'back');
452 return Promise.resolve(false);
453 }
454 }
455 }
456 // Normalize empty
457 if (ti.insertViews && ti.insertViews.length === 0) {
458 ti.insertViews = undefined;
459 }
460 // Enqueue transition instruction
461 this.transInstr.push(ti);
462 // if there isn't a transition already happening
463 // then this will kick off this transition
464 this.nextTrns();
465 return promise;
466 }
467 success(result, ti) {
468 if (this.destroyed) {
469 this.fireError('nav controller was destroyed', ti);
470 return;
471 }
472 if (ti.done) {
473 ti.done(result.hasCompleted, result.requiresTransition, result.enteringView, result.leavingView, result.direction);
474 }
475 ti.resolve(result.hasCompleted);
476 if (ti.opts.updateURL !== false && this.useRouter) {
477 const router = document.querySelector('ion-router');
478 if (router) {
479 const direction = result.direction === 'back' ? 'back' : 'forward';
480 router.navChanged(direction);
481 }
482 }
483 }
484 failed(rejectReason, ti) {
485 if (this.destroyed) {
486 this.fireError('nav controller was destroyed', ti);
487 return;
488 }
489 this.transInstr.length = 0;
490 this.fireError(rejectReason, ti);
491 }
492 fireError(rejectReason, ti) {
493 if (ti.done) {
494 ti.done(false, false, rejectReason);
495 }
496 if (ti.reject && !this.destroyed) {
497 ti.reject(rejectReason);
498 }
499 else {
500 ti.resolve(false);
501 }
502 }
503 nextTrns() {
504 // this is the framework's bread 'n butta function
505 // only one transition is allowed at any given time
506 if (this.isTransitioning) {
507 return false;
508 }
509 // there is no transition happening right now
510 // get the next instruction
511 const ti = this.transInstr.shift();
512 if (!ti) {
513 return false;
514 }
515 this.runTransition(ti);
516 return true;
517 }
518 async runTransition(ti) {
519 try {
520 // set that this nav is actively transitioning
521 this.ionNavWillChange.emit();
522 this.isTransitioning = true;
523 this.prepareTI(ti);
524 const leavingView = this.getActiveSync();
525 const enteringView = this.getEnteringView(ti, leavingView);
526 if (!leavingView && !enteringView) {
527 throw new Error('no views in the stack to be removed');
528 }
529 if (enteringView && enteringView.state === VIEW_STATE_NEW) {
530 await enteringView.init(this.el);
531 }
532 this.postViewInit(enteringView, leavingView, ti);
533 // Needs transition?
534 const requiresTransition = (ti.enteringRequiresTransition || ti.leavingRequiresTransition) &&
535 enteringView !== leavingView;
536 if (requiresTransition && ti.opts && leavingView) {
537 const isBackDirection = ti.opts.direction === 'back';
538 /**
539 * If heading back, use the entering page's animation
540 * unless otherwise specified by the developer.
541 */
542 if (isBackDirection) {
543 ti.opts.animationBuilder = ti.opts.animationBuilder || (enteringView && enteringView.animationBuilder);
544 }
545 leavingView.animationBuilder = ti.opts.animationBuilder;
546 }
547 const result = requiresTransition
548 ? await this.transition(enteringView, leavingView, ti)
549 : {
550 // transition is not required, so we are already done!
551 // they're inserting/removing the views somewhere in the middle or
552 // beginning, so visually nothing needs to animate/transition
553 // resolve immediately because there's no animation that's happening
554 hasCompleted: true,
555 requiresTransition: false
556 };
557 this.success(result, ti);
558 this.ionNavDidChange.emit();
559 }
560 catch (rejectReason) {
561 this.failed(rejectReason, ti);
562 }
563 this.isTransitioning = false;
564 this.nextTrns();
565 }
566 prepareTI(ti) {
567 const viewsLength = this.views.length;
568 ti.opts = ti.opts || {};
569 if (ti.opts.delegate === undefined) {
570 ti.opts.delegate = this.delegate;
571 }
572 if (ti.removeView !== undefined) {
573 helpers.assert(ti.removeStart !== undefined, 'removeView needs removeStart');
574 helpers.assert(ti.removeCount !== undefined, 'removeView needs removeCount');
575 const index = this.views.indexOf(ti.removeView);
576 if (index < 0) {
577 throw new Error('removeView was not found');
578 }
579 ti.removeStart += index;
580 }
581 if (ti.removeStart !== undefined) {
582 if (ti.removeStart < 0) {
583 ti.removeStart = viewsLength - 1;
584 }
585 if (ti.removeCount < 0) {
586 ti.removeCount = viewsLength - ti.removeStart;
587 }
588 ti.leavingRequiresTransition =
589 ti.removeCount > 0 && ti.removeStart + ti.removeCount === viewsLength;
590 }
591 if (ti.insertViews) {
592 // allow -1 to be passed in to auto push it on the end
593 // and clean up the index if it's larger then the size of the stack
594 if (ti.insertStart < 0 || ti.insertStart > viewsLength) {
595 ti.insertStart = viewsLength;
596 }
597 ti.enteringRequiresTransition = ti.insertStart === viewsLength;
598 }
599 const insertViews = ti.insertViews;
600 if (!insertViews) {
601 return;
602 }
603 helpers.assert(insertViews.length > 0, 'length can not be zero');
604 const viewControllers = convertToViews(insertViews);
605 if (viewControllers.length === 0) {
606 throw new Error('invalid views to insert');
607 }
608 // Check all the inserted view are correct
609 for (const view of viewControllers) {
610 view.delegate = ti.opts.delegate;
611 const nav = view.nav;
612 if (nav && nav !== this) {
613 throw new Error('inserted view was already inserted');
614 }
615 if (view.state === VIEW_STATE_DESTROYED) {
616 throw new Error('inserted view was already destroyed');
617 }
618 }
619 ti.insertViews = viewControllers;
620 }
621 getEnteringView(ti, leavingView) {
622 const insertViews = ti.insertViews;
623 if (insertViews !== undefined) {
624 // grab the very last view of the views to be inserted
625 // and initialize it as the new entering view
626 return insertViews[insertViews.length - 1];
627 }
628 const removeStart = ti.removeStart;
629 if (removeStart !== undefined) {
630 const views = this.views;
631 const removeEnd = removeStart + ti.removeCount;
632 for (let i = views.length - 1; i >= 0; i--) {
633 const view = views[i];
634 if ((i < removeStart || i >= removeEnd) && view !== leavingView) {
635 return view;
636 }
637 }
638 }
639 return undefined;
640 }
641 postViewInit(enteringView, leavingView, ti) {
642 helpers.assert(leavingView || enteringView, 'Both leavingView and enteringView are null');
643 helpers.assert(ti.resolve, 'resolve must be valid');
644 helpers.assert(ti.reject, 'reject must be valid');
645 const opts = ti.opts;
646 const insertViews = ti.insertViews;
647 const removeStart = ti.removeStart;
648 const removeCount = ti.removeCount;
649 let destroyQueue;
650 // there are views to remove
651 if (removeStart !== undefined && removeCount !== undefined) {
652 helpers.assert(removeStart >= 0, 'removeStart can not be negative');
653 helpers.assert(removeCount >= 0, 'removeCount can not be negative');
654 destroyQueue = [];
655 for (let i = 0; i < removeCount; i++) {
656 const view = this.views[i + removeStart];
657 if (view && view !== enteringView && view !== leavingView) {
658 destroyQueue.push(view);
659 }
660 }
661 // default the direction to "back"
662 opts.direction = opts.direction || 'back';
663 }
664 const finalBalance = this.views.length +
665 (insertViews !== undefined ? insertViews.length : 0) -
666 (removeCount !== undefined ? removeCount : 0);
667 helpers.assert(finalBalance >= 0, 'final balance can not be negative');
668 if (finalBalance === 0) {
669 console.warn(`You can't remove all the pages in the navigation stack. nav.pop() is probably called too many times.`, this, this.el);
670 throw new Error('navigation stack needs at least one root page');
671 }
672 // At this point the transition can not be rejected, any throw should be an error
673 // there are views to insert
674 if (insertViews) {
675 // add the views to the
676 let insertIndex = ti.insertStart;
677 for (const view of insertViews) {
678 this.insertViewAt(view, insertIndex);
679 insertIndex++;
680 }
681 if (ti.enteringRequiresTransition) {
682 // default to forward if not already set
683 opts.direction = opts.direction || 'forward';
684 }
685 }
686 // if the views to be removed are in the beginning or middle
687 // and there is not a view that needs to visually transition out
688 // then just destroy them and don't transition anything
689 // batch all of lifecycles together
690 // let's make sure, callbacks are zoned
691 if (destroyQueue && destroyQueue.length > 0) {
692 for (const view of destroyQueue) {
693 index$1.lifecycle(view.element, index$1.LIFECYCLE_WILL_LEAVE);
694 index$1.lifecycle(view.element, index$1.LIFECYCLE_DID_LEAVE);
695 index$1.lifecycle(view.element, index$1.LIFECYCLE_WILL_UNLOAD);
696 }
697 // once all lifecycle events has been delivered, we can safely detroy the views
698 for (const view of destroyQueue) {
699 this.destroyView(view);
700 }
701 }
702 }
703 async transition(enteringView, leavingView, ti) {
704 // we should animate (duration > 0) if the pushed page is not the first one (startup)
705 // or if it is a portal (modal, actionsheet, etc.)
706 const opts = ti.opts;
707 const progressCallback = opts.progressAnimation
708 ? (ani) => this.sbAni = ani
709 : undefined;
710 const mode = ionicGlobal.getIonMode(this);
711 const enteringEl = enteringView.element;
712 const leavingEl = leavingView && leavingView.element;
713 const animationOpts = Object.assign(Object.assign({ mode, showGoBack: this.canGoBackSync(enteringView), baseEl: this.el, progressCallback, animated: this.animated && ionicGlobal.config.getBoolean('animated', true), enteringEl,
714 leavingEl }, opts), { animationBuilder: opts.animationBuilder || this.animation || ionicGlobal.config.get('navAnimation') });
715 const { hasCompleted } = await index$1.transition(animationOpts);
716 return this.transitionFinish(hasCompleted, enteringView, leavingView, opts);
717 }
718 transitionFinish(hasCompleted, enteringView, leavingView, opts) {
719 const cleanupView = hasCompleted ? enteringView : leavingView;
720 if (cleanupView) {
721 this.cleanup(cleanupView);
722 }
723 return {
724 hasCompleted,
725 requiresTransition: true,
726 enteringView,
727 leavingView,
728 direction: opts.direction
729 };
730 }
731 insertViewAt(view, index) {
732 const views = this.views;
733 const existingIndex = views.indexOf(view);
734 if (existingIndex > -1) {
735 // this view is already in the stack!!
736 // move it to its new location
737 helpers.assert(view.nav === this, 'view is not part of the nav');
738 views.splice(index, 0, views.splice(existingIndex, 1)[0]);
739 }
740 else {
741 helpers.assert(!view.nav, 'nav is used');
742 // this is a new view to add to the stack
743 // create the new entering view
744 view.nav = this;
745 // insert the entering view into the correct index in the stack
746 views.splice(index, 0, view);
747 }
748 }
749 removeView(view) {
750 helpers.assert(view.state === VIEW_STATE_ATTACHED || view.state === VIEW_STATE_DESTROYED, 'view state should be loaded or destroyed');
751 const views = this.views;
752 const index = views.indexOf(view);
753 helpers.assert(index > -1, 'view must be part of the stack');
754 if (index >= 0) {
755 views.splice(index, 1);
756 }
757 }
758 destroyView(view) {
759 view._destroy();
760 this.removeView(view);
761 }
762 /**
763 * DOM WRITE
764 */
765 cleanup(activeView) {
766 // ok, cleanup time!! Destroy all of the views that are
767 // INACTIVE and come after the active view
768 // only do this if the views exist, though
769 if (this.destroyed) {
770 return;
771 }
772 const views = this.views;
773 const activeViewIndex = views.indexOf(activeView);
774 for (let i = views.length - 1; i >= 0; i--) {
775 const view = views[i];
776 /**
777 * When inserting multiple views via insertPages
778 * the last page will be transitioned to, but the
779 * others will not be. As a result, a DOM element
780 * will only be created for the last page inserted.
781 * As a result, it is possible to have views in the
782 * stack that do not have `view.element` yet.
783 */
784 const element = view.element;
785 if (element) {
786 if (i > activeViewIndex) {
787 // this view comes after the active view
788 // let's unload it
789 index$1.lifecycle(element, index$1.LIFECYCLE_WILL_UNLOAD);
790 this.destroyView(view);
791 }
792 else if (i < activeViewIndex) {
793 // this view comes before the active view
794 // and it is not a portal then ensure it is hidden
795 index$1.setPageHidden(element, true);
796 }
797 }
798 }
799 }
800 canStart() {
801 return (!!this.swipeGesture &&
802 !this.isTransitioning &&
803 this.transInstr.length === 0 &&
804 this.animationEnabled &&
805 this.canGoBackSync());
806 }
807 onStart() {
808 this.queueTrns({
809 removeStart: -1,
810 removeCount: 1,
811 opts: {
812 direction: 'back',
813 progressAnimation: true
814 }
815 }, undefined);
816 }
817 onMove(stepValue) {
818 if (this.sbAni) {
819 this.sbAni.progressStep(stepValue);
820 }
821 }
822 onEnd(shouldComplete, stepValue, dur) {
823 if (this.sbAni) {
824 this.animationEnabled = false;
825 this.sbAni.onFinish(() => {
826 this.animationEnabled = true;
827 }, { oneTimeCallback: true });
828 // Account for rounding errors in JS
829 let newStepValue = (shouldComplete) ? -0.001 : 0.001;
830 /**
831 * Animation will be reversed here, so need to
832 * reverse the easing curve as well
833 *
834 * Additionally, we need to account for the time relative
835 * to the new easing curve, as `stepValue` is going to be given
836 * in terms of a linear curve.
837 */
838 if (!shouldComplete) {
839 this.sbAni.easing('cubic-bezier(1, 0, 0.68, 0.28)');
840 newStepValue += cubicBezier.getTimeGivenProgression([0, 0], [1, 0], [0.68, 0.28], [1, 1], stepValue)[0];
841 }
842 else {
843 newStepValue += cubicBezier.getTimeGivenProgression([0, 0], [0.32, 0.72], [0, 1], [1, 1], stepValue)[0];
844 }
845 this.sbAni.progressEnd(shouldComplete ? 1 : 0, newStepValue, dur);
846 }
847 }
848 render() {
849 return (index.h("slot", null));
850 }
851 get el() { return index.getElement(this); }
852 static get watchers() { return {
853 "swipeGesture": ["swipeGestureChanged"],
854 "root": ["rootChanged"]
855 }; }
856};
857Nav.style = navCss;
858
859const navLink = (el, routerDirection, component, componentProps, routerAnimation) => {
860 const nav = el.closest('ion-nav');
861 if (nav) {
862 if (routerDirection === 'forward') {
863 if (component !== undefined) {
864 return nav.push(component, componentProps, { skipIfBusy: true, animationBuilder: routerAnimation });
865 }
866 }
867 else if (routerDirection === 'root') {
868 if (component !== undefined) {
869 return nav.setRoot(component, componentProps, { skipIfBusy: true, animationBuilder: routerAnimation });
870 }
871 }
872 else if (routerDirection === 'back') {
873 return nav.pop({ skipIfBusy: true, animationBuilder: routerAnimation });
874 }
875 }
876 return Promise.resolve(false);
877};
878
879const NavLink = class {
880 constructor(hostRef) {
881 index.registerInstance(this, hostRef);
882 /**
883 * The transition direction when navigating to another page.
884 */
885 this.routerDirection = 'forward';
886 this.onClick = () => {
887 return navLink(this.el, this.routerDirection, this.component, this.componentProps, this.routerAnimation);
888 };
889 }
890 render() {
891 return (index.h(index.Host, { onClick: this.onClick }));
892 }
893 get el() { return index.getElement(this); }
894};
895
896exports.ion_nav = Nav;
897exports.ion_nav_link = NavLink;