UNPKG

102 kBJavaScriptView Raw
1import * as i0 from '@angular/core';
2import { Injectable, Inject, Optional, InjectionToken, inject, NgZone, ApplicationRef, Injector, createComponent, TemplateRef, Directive, ContentChild, EventEmitter, ViewContainerRef, EnvironmentInjector, Attribute, SkipSelf, Input, Output, reflectComponentType, HostListener, ElementRef, ViewChild } from '@angular/core';
3import { __awaiter, __decorate } from 'tslib';
4import * as i3 from '@angular/router';
5import { NavigationStart, PRIMARY_OUTLET, ChildrenOutletContexts, ActivatedRoute, Router } from '@angular/router';
6import * as i1 from '@angular/common';
7import { DOCUMENT } from '@angular/common';
8import { isPlatform, getPlatforms, LIFECYCLE_WILL_ENTER, LIFECYCLE_DID_ENTER, LIFECYCLE_WILL_LEAVE, LIFECYCLE_DID_LEAVE, LIFECYCLE_WILL_UNLOAD, componentOnReady } from '@ionic/core/components';
9import { Subject, fromEvent, BehaviorSubject, combineLatest, of } from 'rxjs';
10import { filter, switchMap, distinctUntilChanged } from 'rxjs/operators';
11import { NgControl } from '@angular/forms';
12
13class MenuController {
14 constructor(menuController) {
15 this.menuController = menuController;
16 }
17 /**
18 * Programmatically open the Menu.
19 * @param [menuId] Optionally get the menu by its id, or side.
20 * @return returns a promise when the menu is fully opened
21 */
22 open(menuId) {
23 return this.menuController.open(menuId);
24 }
25 /**
26 * Programmatically close the Menu. If no `menuId` is given as the first
27 * argument then it'll close any menu which is open. If a `menuId`
28 * is given then it'll close that exact menu.
29 * @param [menuId] Optionally get the menu by its id, or side.
30 * @return returns a promise when the menu is fully closed
31 */
32 close(menuId) {
33 return this.menuController.close(menuId);
34 }
35 /**
36 * Toggle the menu. If it's closed, it will open, and if opened, it
37 * will close.
38 * @param [menuId] Optionally get the menu by its id, or side.
39 * @return returns a promise when the menu has been toggled
40 */
41 toggle(menuId) {
42 return this.menuController.toggle(menuId);
43 }
44 /**
45 * Used to enable or disable a menu. For example, there could be multiple
46 * left menus, but only one of them should be able to be opened at the same
47 * time. If there are multiple menus on the same side, then enabling one menu
48 * will also automatically disable all the others that are on the same side.
49 * @param [menuId] Optionally get the menu by its id, or side.
50 * @return Returns the instance of the menu, which is useful for chaining.
51 */
52 enable(shouldEnable, menuId) {
53 return this.menuController.enable(shouldEnable, menuId);
54 }
55 /**
56 * Used to enable or disable the ability to swipe open the menu.
57 * @param shouldEnable True if it should be swipe-able, false if not.
58 * @param [menuId] Optionally get the menu by its id, or side.
59 * @return Returns the instance of the menu, which is useful for chaining.
60 */
61 swipeGesture(shouldEnable, menuId) {
62 return this.menuController.swipeGesture(shouldEnable, menuId);
63 }
64 /**
65 * @param [menuId] Optionally get the menu by its id, or side.
66 * @return Returns true if the specified menu is currently open, otherwise false.
67 * If the menuId is not specified, it returns true if ANY menu is currenly open.
68 */
69 isOpen(menuId) {
70 return this.menuController.isOpen(menuId);
71 }
72 /**
73 * @param [menuId] Optionally get the menu by its id, or side.
74 * @return Returns true if the menu is currently enabled, otherwise false.
75 */
76 isEnabled(menuId) {
77 return this.menuController.isEnabled(menuId);
78 }
79 /**
80 * Used to get a menu instance. If a `menuId` is not provided then it'll
81 * return the first menu found. If a `menuId` is `left` or `right`, then
82 * it'll return the enabled menu on that side. Otherwise, if a `menuId` is
83 * provided, then it'll try to find the menu using the menu's `id`
84 * property. If a menu is not found then it'll return `null`.
85 * @param [menuId] Optionally get the menu by its id, or side.
86 * @return Returns the instance of the menu if found, otherwise `null`.
87 */
88 get(menuId) {
89 return this.menuController.get(menuId);
90 }
91 /**
92 * @return Returns the instance of the menu already opened, otherwise `null`.
93 */
94 getOpen() {
95 return this.menuController.getOpen();
96 }
97 /**
98 * @return Returns an array of all menu instances.
99 */
100 getMenus() {
101 return this.menuController.getMenus();
102 }
103 registerAnimation(name, animation) {
104 return this.menuController.registerAnimation(name, animation);
105 }
106 isAnimating() {
107 return this.menuController.isAnimating();
108 }
109 _getOpenSync() {
110 return this.menuController._getOpenSync();
111 }
112 _createAnimation(type, menuCmp) {
113 return this.menuController._createAnimation(type, menuCmp);
114 }
115 _register(menu) {
116 return this.menuController._register(menu);
117 }
118 _unregister(menu) {
119 return this.menuController._unregister(menu);
120 }
121 _setOpen(menu, shouldOpen, animated) {
122 return this.menuController._setOpen(menu, shouldOpen, animated);
123 }
124}
125
126class DomController {
127 /**
128 * Schedules a task to run during the READ phase of the next frame.
129 * This task should only read the DOM, but never modify it.
130 */
131 read(cb) {
132 getQueue().read(cb);
133 }
134 /**
135 * Schedules a task to run during the WRITE phase of the next frame.
136 * This task should write the DOM, but never READ it.
137 */
138 write(cb) {
139 getQueue().write(cb);
140 }
141}
142/** @nocollapse */ DomController.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: DomController, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
143/** @nocollapse */ DomController.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: DomController, providedIn: 'root' });
144i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: DomController, decorators: [{
145 type: Injectable,
146 args: [{
147 providedIn: 'root',
148 }]
149 }] });
150const getQueue = () => {
151 const win = typeof window !== 'undefined' ? window : null;
152 if (win != null) {
153 const Ionic = win.Ionic;
154 if (Ionic === null || Ionic === void 0 ? void 0 : Ionic.queue) {
155 return Ionic.queue;
156 }
157 return {
158 read: (cb) => win.requestAnimationFrame(cb),
159 write: (cb) => win.requestAnimationFrame(cb),
160 };
161 }
162 return {
163 read: (cb) => cb(),
164 write: (cb) => cb(),
165 };
166};
167
168class Platform {
169 constructor(doc, zone) {
170 this.doc = doc;
171 /**
172 * @hidden
173 */
174 this.backButton = new Subject();
175 /**
176 * The keyboardDidShow event emits when the
177 * on-screen keyboard is presented.
178 */
179 this.keyboardDidShow = new Subject();
180 /**
181 * The keyboardDidHide event emits when the
182 * on-screen keyboard is hidden.
183 */
184 this.keyboardDidHide = new Subject();
185 /**
186 * The pause event emits when the native platform puts the application
187 * into the background, typically when the user switches to a different
188 * application. This event would emit when a Cordova app is put into
189 * the background, however, it would not fire on a standard web browser.
190 */
191 this.pause = new Subject();
192 /**
193 * The resume event emits when the native platform pulls the application
194 * out from the background. This event would emit when a Cordova app comes
195 * out from the background, however, it would not fire on a standard web browser.
196 */
197 this.resume = new Subject();
198 /**
199 * The resize event emits when the browser window has changed dimensions. This
200 * could be from a browser window being physically resized, or from a device
201 * changing orientation.
202 */
203 this.resize = new Subject();
204 zone.run(() => {
205 var _a;
206 this.win = doc.defaultView;
207 this.backButton.subscribeWithPriority = function (priority, callback) {
208 return this.subscribe((ev) => {
209 return ev.register(priority, (processNextHandler) => zone.run(() => callback(processNextHandler)));
210 });
211 };
212 proxyEvent(this.pause, doc, 'pause', zone);
213 proxyEvent(this.resume, doc, 'resume', zone);
214 proxyEvent(this.backButton, doc, 'ionBackButton', zone);
215 proxyEvent(this.resize, this.win, 'resize', zone);
216 proxyEvent(this.keyboardDidShow, this.win, 'ionKeyboardDidShow', zone);
217 proxyEvent(this.keyboardDidHide, this.win, 'ionKeyboardDidHide', zone);
218 let readyResolve;
219 this._readyPromise = new Promise((res) => {
220 readyResolve = res;
221 });
222 if ((_a = this.win) === null || _a === void 0 ? void 0 : _a['cordova']) {
223 doc.addEventListener('deviceready', () => {
224 readyResolve('cordova');
225 }, { once: true });
226 }
227 else {
228 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
229 readyResolve('dom');
230 }
231 });
232 }
233 /**
234 * @returns returns true/false based on platform.
235 * @description
236 * Depending on the platform the user is on, `is(platformName)` will
237 * return `true` or `false`. Note that the same app can return `true`
238 * for more than one platform name. For example, an app running from
239 * an iPad would return `true` for the platform names: `mobile`,
240 * `ios`, `ipad`, and `tablet`. Additionally, if the app was running
241 * from Cordova then `cordova` would be true, and if it was running
242 * from a web browser on the iPad then `mobileweb` would be `true`.
243 *
244 * ```
245 * import { Platform } from 'ionic-angular';
246 *
247 * @Component({...})
248 * export MyPage {
249 * constructor(public platform: Platform) {
250 * if (this.platform.is('ios')) {
251 * // This will only print when on iOS
252 * console.log('I am an iOS device!');
253 * }
254 * }
255 * }
256 * ```
257 *
258 * | Platform Name | Description |
259 * |-----------------|------------------------------------|
260 * | android | on a device running Android. |
261 * | capacitor | on a device running Capacitor. |
262 * | cordova | on a device running Cordova. |
263 * | ios | on a device running iOS. |
264 * | ipad | on an iPad device. |
265 * | iphone | on an iPhone device. |
266 * | phablet | on a phablet device. |
267 * | tablet | on a tablet device. |
268 * | electron | in Electron on a desktop device. |
269 * | pwa | as a PWA app. |
270 * | mobile | on a mobile device. |
271 * | mobileweb | on a mobile device in a browser. |
272 * | desktop | on a desktop device. |
273 * | hybrid | is a cordova or capacitor app. |
274 *
275 */
276 is(platformName) {
277 return isPlatform(this.win, platformName);
278 }
279 /**
280 * @returns the array of platforms
281 * @description
282 * Depending on what device you are on, `platforms` can return multiple values.
283 * Each possible value is a hierarchy of platforms. For example, on an iPhone,
284 * it would return `mobile`, `ios`, and `iphone`.
285 *
286 * ```
287 * import { Platform } from 'ionic-angular';
288 *
289 * @Component({...})
290 * export MyPage {
291 * constructor(public platform: Platform) {
292 * // This will print an array of the current platforms
293 * console.log(this.platform.platforms());
294 * }
295 * }
296 * ```
297 */
298 platforms() {
299 return getPlatforms(this.win);
300 }
301 /**
302 * Returns a promise when the platform is ready and native functionality
303 * can be called. If the app is running from within a web browser, then
304 * the promise will resolve when the DOM is ready. When the app is running
305 * from an application engine such as Cordova, then the promise will
306 * resolve when Cordova triggers the `deviceready` event.
307 *
308 * The resolved value is the `readySource`, which states which platform
309 * ready was used. For example, when Cordova is ready, the resolved ready
310 * source is `cordova`. The default ready source value will be `dom`. The
311 * `readySource` is useful if different logic should run depending on the
312 * platform the app is running from. For example, only Cordova can execute
313 * the status bar plugin, so the web should not run status bar plugin logic.
314 *
315 * ```
316 * import { Component } from '@angular/core';
317 * import { Platform } from 'ionic-angular';
318 *
319 * @Component({...})
320 * export MyApp {
321 * constructor(public platform: Platform) {
322 * this.platform.ready().then((readySource) => {
323 * console.log('Platform ready from', readySource);
324 * // Platform now ready, execute any required native code
325 * });
326 * }
327 * }
328 * ```
329 */
330 ready() {
331 return this._readyPromise;
332 }
333 /**
334 * Returns if this app is using right-to-left language direction or not.
335 * We recommend the app's `index.html` file already has the correct `dir`
336 * attribute value set, such as `<html dir="ltr">` or `<html dir="rtl">`.
337 * [W3C: Structural markup and right-to-left text in HTML](http://www.w3.org/International/questions/qa-html-dir)
338 */
339 get isRTL() {
340 return this.doc.dir === 'rtl';
341 }
342 /**
343 * Get the query string parameter
344 */
345 getQueryParam(key) {
346 return readQueryParam(this.win.location.href, key);
347 }
348 /**
349 * Returns `true` if the app is in landscape mode.
350 */
351 isLandscape() {
352 return !this.isPortrait();
353 }
354 /**
355 * Returns `true` if the app is in portrait mode.
356 */
357 isPortrait() {
358 var _a, _b;
359 return (_b = (_a = this.win).matchMedia) === null || _b === void 0 ? void 0 : _b.call(_a, '(orientation: portrait)').matches;
360 }
361 testUserAgent(expression) {
362 const nav = this.win.navigator;
363 return !!((nav === null || nav === void 0 ? void 0 : nav.userAgent) && nav.userAgent.indexOf(expression) >= 0);
364 }
365 /**
366 * Get the current url.
367 */
368 url() {
369 return this.win.location.href;
370 }
371 /**
372 * Gets the width of the platform's viewport using `window.innerWidth`.
373 */
374 width() {
375 return this.win.innerWidth;
376 }
377 /**
378 * Gets the height of the platform's viewport using `window.innerHeight`.
379 */
380 height() {
381 return this.win.innerHeight;
382 }
383}
384/** @nocollapse */ Platform.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: Platform, deps: [{ token: DOCUMENT }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
385/** @nocollapse */ Platform.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: Platform, providedIn: 'root' });
386i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: Platform, decorators: [{
387 type: Injectable,
388 args: [{
389 providedIn: 'root',
390 }]
391 }], ctorParameters: function () {
392 return [{ type: undefined, decorators: [{
393 type: Inject,
394 args: [DOCUMENT]
395 }] }, { type: i0.NgZone }];
396 } });
397const readQueryParam = (url, key) => {
398 key = key.replace(/[[\]\\]/g, '\\$&');
399 const regex = new RegExp('[\\?&]' + key + '=([^&#]*)');
400 const results = regex.exec(url);
401 return results ? decodeURIComponent(results[1].replace(/\+/g, ' ')) : null;
402};
403const proxyEvent = (emitter, el, eventName, zone) => {
404 if (el) {
405 el.addEventListener(eventName, (ev) => {
406 /**
407 * `zone.run` is required to make sure that we are running inside the Angular zone
408 * at all times. This is necessary since an app that has Capacitor will
409 * override the `document.addEventListener` with its own implementation.
410 * The override causes the event to no longer be in the Angular zone.
411 */
412 zone.run(() => {
413 // ?? cordova might emit "null" events
414 const value = ev != null ? ev.detail : undefined;
415 emitter.next(value);
416 });
417 });
418 }
419};
420
421class NavController {
422 constructor(platform, location, serializer, router) {
423 this.location = location;
424 this.serializer = serializer;
425 this.router = router;
426 this.direction = DEFAULT_DIRECTION;
427 this.animated = DEFAULT_ANIMATED;
428 this.guessDirection = 'forward';
429 this.lastNavId = -1;
430 // Subscribe to router events to detect direction
431 if (router) {
432 router.events.subscribe((ev) => {
433 if (ev instanceof NavigationStart) {
434 const id = ev.restoredState ? ev.restoredState.navigationId : ev.id;
435 this.guessDirection = id < this.lastNavId ? 'back' : 'forward';
436 this.guessAnimation = !ev.restoredState ? this.guessDirection : undefined;
437 this.lastNavId = this.guessDirection === 'forward' ? ev.id : id;
438 }
439 });
440 }
441 // Subscribe to backButton events
442 platform.backButton.subscribeWithPriority(0, (processNextHandler) => {
443 this.pop();
444 processNextHandler();
445 });
446 }
447 /**
448 * This method uses Angular's [Router](https://angular.io/api/router/Router) under the hood,
449 * it's equivalent to calling `this.router.navigateByUrl()`, but it's explicit about the **direction** of the transition.
450 *
451 * Going **forward** means that a new page is going to be pushed to the stack of the outlet (ion-router-outlet),
452 * and that it will show a "forward" animation by default.
453 *
454 * Navigating forward can also be triggered in a declarative manner by using the `[routerDirection]` directive:
455 *
456 * ```html
457 * <a routerLink="/path/to/page" routerDirection="forward">Link</a>
458 * ```
459 */
460 navigateForward(url, options = {}) {
461 this.setDirection('forward', options.animated, options.animationDirection, options.animation);
462 return this.navigate(url, options);
463 }
464 /**
465 * This method uses Angular's [Router](https://angular.io/api/router/Router) under the hood,
466 * it's equivalent to calling:
467 *
468 * ```ts
469 * this.navController.setDirection('back');
470 * this.router.navigateByUrl(path);
471 * ```
472 *
473 * Going **back** means that all the pages in the stack until the navigated page is found will be popped,
474 * and that it will show a "back" animation by default.
475 *
476 * Navigating back can also be triggered in a declarative manner by using the `[routerDirection]` directive:
477 *
478 * ```html
479 * <a routerLink="/path/to/page" routerDirection="back">Link</a>
480 * ```
481 */
482 navigateBack(url, options = {}) {
483 this.setDirection('back', options.animated, options.animationDirection, options.animation);
484 return this.navigate(url, options);
485 }
486 /**
487 * This method uses Angular's [Router](https://angular.io/api/router/Router) under the hood,
488 * it's equivalent to calling:
489 *
490 * ```ts
491 * this.navController.setDirection('root');
492 * this.router.navigateByUrl(path);
493 * ```
494 *
495 * Going **root** means that all existing pages in the stack will be removed,
496 * and the navigated page will become the single page in the stack.
497 *
498 * Navigating root can also be triggered in a declarative manner by using the `[routerDirection]` directive:
499 *
500 * ```html
501 * <a routerLink="/path/to/page" routerDirection="root">Link</a>
502 * ```
503 */
504 navigateRoot(url, options = {}) {
505 this.setDirection('root', options.animated, options.animationDirection, options.animation);
506 return this.navigate(url, options);
507 }
508 /**
509 * Same as [Location](https://angular.io/api/common/Location)'s back() method.
510 * It will use the standard `window.history.back()` under the hood, but featuring a `back` animation
511 * by default.
512 */
513 back(options = { animated: true, animationDirection: 'back' }) {
514 this.setDirection('back', options.animated, options.animationDirection, options.animation);
515 return this.location.back();
516 }
517 /**
518 * This methods goes back in the context of Ionic's stack navigation.
519 *
520 * It recursively finds the top active `ion-router-outlet` and calls `pop()`.
521 * This is the recommended way to go back when you are using `ion-router-outlet`.
522 *
523 * Resolves to `true` if it was able to pop.
524 */
525 pop() {
526 return __awaiter(this, void 0, void 0, function* () {
527 let outlet = this.topOutlet;
528 while (outlet) {
529 if (yield outlet.pop()) {
530 return true;
531 }
532 else {
533 outlet = outlet.parentOutlet;
534 }
535 }
536 return false;
537 });
538 }
539 /**
540 * This methods specifies the direction of the next navigation performed by the Angular router.
541 *
542 * `setDirection()` does not trigger any transition, it just sets some flags to be consumed by `ion-router-outlet`.
543 *
544 * It's recommended to use `navigateForward()`, `navigateBack()` and `navigateRoot()` instead of `setDirection()`.
545 */
546 setDirection(direction, animated, animationDirection, animationBuilder) {
547 this.direction = direction;
548 this.animated = getAnimation(direction, animated, animationDirection);
549 this.animationBuilder = animationBuilder;
550 }
551 /**
552 * @internal
553 */
554 setTopOutlet(outlet) {
555 this.topOutlet = outlet;
556 }
557 /**
558 * @internal
559 */
560 consumeTransition() {
561 let direction = 'root';
562 let animation;
563 const animationBuilder = this.animationBuilder;
564 if (this.direction === 'auto') {
565 direction = this.guessDirection;
566 animation = this.guessAnimation;
567 }
568 else {
569 animation = this.animated;
570 direction = this.direction;
571 }
572 this.direction = DEFAULT_DIRECTION;
573 this.animated = DEFAULT_ANIMATED;
574 this.animationBuilder = undefined;
575 return {
576 direction,
577 animation,
578 animationBuilder,
579 };
580 }
581 navigate(url, options) {
582 if (Array.isArray(url)) {
583 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
584 return this.router.navigate(url, options);
585 }
586 else {
587 /**
588 * navigateByUrl ignores any properties that
589 * would change the url, so things like queryParams
590 * would be ignored unless we create a url tree
591 * More Info: https://github.com/angular/angular/issues/18798
592 */
593 const urlTree = this.serializer.parse(url.toString());
594 if (options.queryParams !== undefined) {
595 urlTree.queryParams = Object.assign({}, options.queryParams);
596 }
597 if (options.fragment !== undefined) {
598 urlTree.fragment = options.fragment;
599 }
600 /**
601 * `navigateByUrl` will still apply `NavigationExtras` properties
602 * that do not modify the url, such as `replaceUrl` which is why
603 * `options` is passed in here.
604 */
605 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
606 return this.router.navigateByUrl(urlTree, options);
607 }
608 }
609}
610/** @nocollapse */ NavController.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: NavController, deps: [{ token: Platform }, { token: i1.Location }, { token: i3.UrlSerializer }, { token: i3.Router, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
611/** @nocollapse */ NavController.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: NavController, providedIn: 'root' });
612i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: NavController, decorators: [{
613 type: Injectable,
614 args: [{
615 providedIn: 'root',
616 }]
617 }], ctorParameters: function () {
618 return [{ type: Platform }, { type: i1.Location }, { type: i3.UrlSerializer }, { type: i3.Router, decorators: [{
619 type: Optional
620 }] }];
621 } });
622const getAnimation = (direction, animated, animationDirection) => {
623 if (animated === false) {
624 return undefined;
625 }
626 if (animationDirection !== undefined) {
627 return animationDirection;
628 }
629 if (direction === 'forward' || direction === 'back') {
630 return direction;
631 }
632 else if (direction === 'root' && animated === true) {
633 return 'forward';
634 }
635 return undefined;
636};
637const DEFAULT_DIRECTION = 'auto';
638const DEFAULT_ANIMATED = undefined;
639
640class Config {
641 get(key, fallback) {
642 const c = getConfig();
643 if (c) {
644 return c.get(key, fallback);
645 }
646 return null;
647 }
648 getBoolean(key, fallback) {
649 const c = getConfig();
650 if (c) {
651 return c.getBoolean(key, fallback);
652 }
653 return false;
654 }
655 getNumber(key, fallback) {
656 const c = getConfig();
657 if (c) {
658 return c.getNumber(key, fallback);
659 }
660 return 0;
661 }
662}
663/** @nocollapse */ Config.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: Config, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
664/** @nocollapse */ Config.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: Config, providedIn: 'root' });
665i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: Config, decorators: [{
666 type: Injectable,
667 args: [{
668 providedIn: 'root',
669 }]
670 }] });
671const ConfigToken = new InjectionToken('USERCONFIG');
672const getConfig = () => {
673 if (typeof window !== 'undefined') {
674 const Ionic = window.Ionic;
675 if (Ionic === null || Ionic === void 0 ? void 0 : Ionic.config) {
676 return Ionic.config;
677 }
678 }
679 return null;
680};
681
682/**
683 * @description
684 * NavParams are an object that exists on a page and can contain data for that particular view.
685 * Similar to how data was pass to a view in V1 with `$stateParams`, NavParams offer a much more flexible
686 * option with a simple `get` method.
687 *
688 * @usage
689 * ```ts
690 * import { NavParams } from '@ionic/angular';
691 *
692 * export class MyClass{
693 *
694 * constructor(navParams: NavParams){
695 * // userParams is an object we have in our nav-parameters
696 * navParams.get('userParams');
697 * }
698 *
699 * }
700 * ```
701 */
702class NavParams {
703 constructor(data = {}) {
704 this.data = data;
705 }
706 /**
707 * Get the value of a nav-parameter for the current view
708 *
709 * ```ts
710 * import { NavParams } from 'ionic-angular';
711 *
712 * export class MyClass{
713 * constructor(public navParams: NavParams){
714 * // userParams is an object we have in our nav-parameters
715 * this.navParams.get('userParams');
716 * }
717 * }
718 * ```
719 *
720 * @param param Which param you want to look up
721 */
722 get(param) {
723 return this.data[param];
724 }
725}
726
727// TODO(FW-2827): types
728class AngularDelegate {
729 constructor() {
730 this.zone = inject(NgZone);
731 this.applicationRef = inject(ApplicationRef);
732 }
733 create(environmentInjector, injector, elementReferenceKey) {
734 return new AngularFrameworkDelegate(environmentInjector, injector, this.applicationRef, this.zone, elementReferenceKey);
735 }
736}
737/** @nocollapse */ AngularDelegate.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: AngularDelegate, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
738/** @nocollapse */ AngularDelegate.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: AngularDelegate });
739i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: AngularDelegate, decorators: [{
740 type: Injectable
741 }] });
742class AngularFrameworkDelegate {
743 constructor(environmentInjector, injector, applicationRef, zone, elementReferenceKey) {
744 this.environmentInjector = environmentInjector;
745 this.injector = injector;
746 this.applicationRef = applicationRef;
747 this.zone = zone;
748 this.elementReferenceKey = elementReferenceKey;
749 this.elRefMap = new WeakMap();
750 this.elEventsMap = new WeakMap();
751 }
752 attachViewToDom(container, component, params, cssClasses) {
753 return this.zone.run(() => {
754 return new Promise((resolve) => {
755 const componentProps = Object.assign({}, params);
756 /**
757 * Ionic Angular passes a reference to a modal
758 * or popover that can be accessed using a
759 * variable in the overlay component. If
760 * elementReferenceKey is defined, then we should
761 * pass a reference to the component using
762 * elementReferenceKey as the key.
763 */
764 if (this.elementReferenceKey !== undefined) {
765 componentProps[this.elementReferenceKey] = container;
766 }
767 const el = attachView(this.zone, this.environmentInjector, this.injector, this.applicationRef, this.elRefMap, this.elEventsMap, container, component, componentProps, cssClasses, this.elementReferenceKey);
768 resolve(el);
769 });
770 });
771 }
772 removeViewFromDom(_container, component) {
773 return this.zone.run(() => {
774 return new Promise((resolve) => {
775 const componentRef = this.elRefMap.get(component);
776 if (componentRef) {
777 componentRef.destroy();
778 this.elRefMap.delete(component);
779 const unbindEvents = this.elEventsMap.get(component);
780 if (unbindEvents) {
781 unbindEvents();
782 this.elEventsMap.delete(component);
783 }
784 }
785 resolve();
786 });
787 });
788 }
789}
790const attachView = (zone, environmentInjector, injector, applicationRef, elRefMap, elEventsMap, container, component, params, cssClasses, elementReferenceKey) => {
791 /**
792 * Wraps the injector with a custom injector that
793 * provides NavParams to the component.
794 *
795 * NavParams is a legacy feature from Ionic v3 that allows
796 * Angular developers to provide data to a component
797 * and access it by providing NavParams as a dependency
798 * in the constructor.
799 *
800 * The modern approach is to access the data directly
801 * from the component's class instance.
802 */
803 const childInjector = Injector.create({
804 providers: getProviders(params),
805 parent: injector,
806 });
807 const componentRef = createComponent(component, {
808 environmentInjector,
809 elementInjector: childInjector,
810 });
811 const instance = componentRef.instance;
812 const hostElement = componentRef.location.nativeElement;
813 if (params) {
814 /**
815 * For modals and popovers, a reference to the component is
816 * added to `params` during the call to attachViewToDom. If
817 * a reference using this name is already set, this means
818 * the app is trying to use the name as a component prop,
819 * which will cause collisions.
820 */
821 if (elementReferenceKey && instance[elementReferenceKey] !== undefined) {
822 console.error(`[Ionic Error]: ${elementReferenceKey} is a reserved property when using ${container.tagName.toLowerCase()}. Rename or remove the "${elementReferenceKey}" property from ${component.name}.`);
823 }
824 Object.assign(instance, params);
825 }
826 if (cssClasses) {
827 for (const cssClass of cssClasses) {
828 hostElement.classList.add(cssClass);
829 }
830 }
831 const unbindEvents = bindLifecycleEvents(zone, instance, hostElement);
832 container.appendChild(hostElement);
833 applicationRef.attachView(componentRef.hostView);
834 elRefMap.set(hostElement, componentRef);
835 elEventsMap.set(hostElement, unbindEvents);
836 return hostElement;
837};
838const LIFECYCLES = [
839 LIFECYCLE_WILL_ENTER,
840 LIFECYCLE_DID_ENTER,
841 LIFECYCLE_WILL_LEAVE,
842 LIFECYCLE_DID_LEAVE,
843 LIFECYCLE_WILL_UNLOAD,
844];
845const bindLifecycleEvents = (zone, instance, element) => {
846 return zone.run(() => {
847 const unregisters = LIFECYCLES.filter((eventName) => typeof instance[eventName] === 'function').map((eventName) => {
848 const handler = (ev) => instance[eventName](ev.detail);
849 element.addEventListener(eventName, handler);
850 return () => element.removeEventListener(eventName, handler);
851 });
852 return () => unregisters.forEach((fn) => fn());
853 });
854};
855const NavParamsToken = new InjectionToken('NavParamsToken');
856const getProviders = (params) => {
857 return [
858 {
859 provide: NavParamsToken,
860 useValue: params,
861 },
862 {
863 provide: NavParams,
864 useFactory: provideNavParamsInjectable,
865 deps: [NavParamsToken],
866 },
867 ];
868};
869const provideNavParamsInjectable = (params) => {
870 return new NavParams(params);
871};
872
873// TODO: Is there a way we can grab this from angular-component-lib instead?
874const proxyInputs = (Cmp, inputs) => {
875 const Prototype = Cmp.prototype;
876 inputs.forEach((item) => {
877 Object.defineProperty(Prototype, item, {
878 get() {
879 return this.el[item];
880 },
881 set(val) {
882 this.z.runOutsideAngular(() => (this.el[item] = val));
883 },
884 });
885 });
886};
887const proxyMethods = (Cmp, methods) => {
888 const Prototype = Cmp.prototype;
889 methods.forEach((methodName) => {
890 Prototype[methodName] = function () {
891 const args = arguments;
892 return this.z.runOutsideAngular(() => this.el[methodName].apply(this.el, args));
893 };
894 });
895};
896const proxyOutputs = (instance, el, events) => {
897 events.forEach((eventName) => (instance[eventName] = fromEvent(el, eventName)));
898};
899// tslint:disable-next-line: only-arrow-functions
900function ProxyCmp(opts) {
901 const decorator = function (cls) {
902 const { defineCustomElementFn, inputs, methods } = opts;
903 if (defineCustomElementFn !== undefined) {
904 defineCustomElementFn();
905 }
906 if (inputs) {
907 proxyInputs(cls, inputs);
908 }
909 if (methods) {
910 proxyMethods(cls, methods);
911 }
912 return cls;
913 };
914 return decorator;
915}
916
917const POPOVER_INPUTS = [
918 'alignment',
919 'animated',
920 'arrow',
921 'keepContentsMounted',
922 'backdropDismiss',
923 'cssClass',
924 'dismissOnSelect',
925 'enterAnimation',
926 'event',
927 'isOpen',
928 'keyboardClose',
929 'leaveAnimation',
930 'mode',
931 'showBackdrop',
932 'translucent',
933 'trigger',
934 'triggerAction',
935 'reference',
936 'size',
937 'side',
938];
939const POPOVER_METHODS = ['present', 'dismiss', 'onDidDismiss', 'onWillDismiss'];
940let IonPopover = class IonPopover {
941 constructor(c, r, z) {
942 this.z = z;
943 this.isCmpOpen = false;
944 this.el = r.nativeElement;
945 this.el.addEventListener('ionMount', () => {
946 this.isCmpOpen = true;
947 c.detectChanges();
948 });
949 this.el.addEventListener('didDismiss', () => {
950 this.isCmpOpen = false;
951 c.detectChanges();
952 });
953 proxyOutputs(this, this.el, [
954 'ionPopoverDidPresent',
955 'ionPopoverWillPresent',
956 'ionPopoverWillDismiss',
957 'ionPopoverDidDismiss',
958 'didPresent',
959 'willPresent',
960 'willDismiss',
961 'didDismiss',
962 ]);
963 }
964};
965/** @nocollapse */ IonPopover.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: IonPopover, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ElementRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Directive });
966/** @nocollapse */ IonPopover.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: IonPopover, selector: "ion-popover", inputs: { alignment: "alignment", animated: "animated", arrow: "arrow", keepContentsMounted: "keepContentsMounted", backdropDismiss: "backdropDismiss", cssClass: "cssClass", dismissOnSelect: "dismissOnSelect", enterAnimation: "enterAnimation", event: "event", isOpen: "isOpen", keyboardClose: "keyboardClose", leaveAnimation: "leaveAnimation", mode: "mode", showBackdrop: "showBackdrop", translucent: "translucent", trigger: "trigger", triggerAction: "triggerAction", reference: "reference", size: "size", side: "side" }, queries: [{ propertyName: "template", first: true, predicate: TemplateRef, descendants: true }], ngImport: i0 });
967IonPopover = __decorate([
968 ProxyCmp({
969 inputs: POPOVER_INPUTS,
970 methods: POPOVER_METHODS,
971 })
972 /**
973 * @Component extends from @Directive
974 * so by defining the inputs here we
975 * do not need to re-define them for the
976 * lazy loaded popover.
977 */
978], IonPopover);
979i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: IonPopover, decorators: [{
980 type: Directive,
981 args: [{
982 selector: 'ion-popover',
983 // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
984 inputs: POPOVER_INPUTS,
985 }]
986 }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: i0.NgZone }]; }, propDecorators: { template: [{
987 type: ContentChild,
988 args: [TemplateRef, { static: false }]
989 }] } });
990
991const MODAL_INPUTS = [
992 'animated',
993 'keepContentsMounted',
994 'backdropBreakpoint',
995 'backdropDismiss',
996 'breakpoints',
997 'canDismiss',
998 'cssClass',
999 'enterAnimation',
1000 'event',
1001 'handle',
1002 'handleBehavior',
1003 'initialBreakpoint',
1004 'isOpen',
1005 'keyboardClose',
1006 'leaveAnimation',
1007 'mode',
1008 'presentingElement',
1009 'showBackdrop',
1010 'translucent',
1011 'trigger',
1012];
1013const MODAL_METHODS = [
1014 'present',
1015 'dismiss',
1016 'onDidDismiss',
1017 'onWillDismiss',
1018 'setCurrentBreakpoint',
1019 'getCurrentBreakpoint',
1020];
1021let IonModal = class IonModal {
1022 constructor(c, r, z) {
1023 this.z = z;
1024 this.isCmpOpen = false;
1025 this.el = r.nativeElement;
1026 this.el.addEventListener('ionMount', () => {
1027 this.isCmpOpen = true;
1028 c.detectChanges();
1029 });
1030 this.el.addEventListener('didDismiss', () => {
1031 this.isCmpOpen = false;
1032 c.detectChanges();
1033 });
1034 proxyOutputs(this, this.el, [
1035 'ionModalDidPresent',
1036 'ionModalWillPresent',
1037 'ionModalWillDismiss',
1038 'ionModalDidDismiss',
1039 'ionBreakpointDidChange',
1040 'didPresent',
1041 'willPresent',
1042 'willDismiss',
1043 'didDismiss',
1044 ]);
1045 }
1046};
1047/** @nocollapse */ IonModal.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: IonModal, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ElementRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Directive });
1048/** @nocollapse */ IonModal.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: IonModal, selector: "ion-modal", inputs: { animated: "animated", keepContentsMounted: "keepContentsMounted", backdropBreakpoint: "backdropBreakpoint", backdropDismiss: "backdropDismiss", breakpoints: "breakpoints", canDismiss: "canDismiss", cssClass: "cssClass", enterAnimation: "enterAnimation", event: "event", handle: "handle", handleBehavior: "handleBehavior", initialBreakpoint: "initialBreakpoint", isOpen: "isOpen", keyboardClose: "keyboardClose", leaveAnimation: "leaveAnimation", mode: "mode", presentingElement: "presentingElement", showBackdrop: "showBackdrop", translucent: "translucent", trigger: "trigger" }, queries: [{ propertyName: "template", first: true, predicate: TemplateRef, descendants: true }], ngImport: i0 });
1049IonModal = __decorate([
1050 ProxyCmp({
1051 inputs: MODAL_INPUTS,
1052 methods: MODAL_METHODS,
1053 })
1054 /**
1055 * @Component extends from @Directive
1056 * so by defining the inputs here we
1057 * do not need to re-define them for the
1058 * lazy loaded popover.
1059 */
1060], IonModal);
1061i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: IonModal, decorators: [{
1062 type: Directive,
1063 args: [{
1064 selector: 'ion-modal',
1065 // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
1066 inputs: MODAL_INPUTS,
1067 }]
1068 }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: i0.NgZone }]; }, propDecorators: { template: [{
1069 type: ContentChild,
1070 args: [TemplateRef, { static: false }]
1071 }] } });
1072
1073const insertView = (views, view, direction) => {
1074 if (direction === 'root') {
1075 return setRoot(views, view);
1076 }
1077 else if (direction === 'forward') {
1078 return setForward(views, view);
1079 }
1080 else {
1081 return setBack(views, view);
1082 }
1083};
1084const setRoot = (views, view) => {
1085 views = views.filter((v) => v.stackId !== view.stackId);
1086 views.push(view);
1087 return views;
1088};
1089const setForward = (views, view) => {
1090 const index = views.indexOf(view);
1091 if (index >= 0) {
1092 views = views.filter((v) => v.stackId !== view.stackId || v.id <= view.id);
1093 }
1094 else {
1095 views.push(view);
1096 }
1097 return views;
1098};
1099const setBack = (views, view) => {
1100 const index = views.indexOf(view);
1101 if (index >= 0) {
1102 return views.filter((v) => v.stackId !== view.stackId || v.id <= view.id);
1103 }
1104 else {
1105 return setRoot(views, view);
1106 }
1107};
1108const getUrl = (router, activatedRoute) => {
1109 const urlTree = router.createUrlTree(['.'], { relativeTo: activatedRoute });
1110 return router.serializeUrl(urlTree);
1111};
1112const isTabSwitch = (enteringView, leavingView) => {
1113 if (!leavingView) {
1114 return true;
1115 }
1116 return enteringView.stackId !== leavingView.stackId;
1117};
1118const computeStackId = (prefixUrl, url) => {
1119 if (!prefixUrl) {
1120 return undefined;
1121 }
1122 const segments = toSegments(url);
1123 for (let i = 0; i < segments.length; i++) {
1124 if (i >= prefixUrl.length) {
1125 return segments[i];
1126 }
1127 if (segments[i] !== prefixUrl[i]) {
1128 return undefined;
1129 }
1130 }
1131 return undefined;
1132};
1133const toSegments = (path) => {
1134 return path
1135 .split('/')
1136 .map((s) => s.trim())
1137 .filter((s) => s !== '');
1138};
1139const destroyView = (view) => {
1140 if (view) {
1141 view.ref.destroy();
1142 view.unlistenEvents();
1143 }
1144};
1145
1146// TODO(FW-2827): types
1147class StackController {
1148 constructor(tabsPrefix, containerEl, router, navCtrl, zone, location) {
1149 this.containerEl = containerEl;
1150 this.router = router;
1151 this.navCtrl = navCtrl;
1152 this.zone = zone;
1153 this.location = location;
1154 this.views = [];
1155 this.skipTransition = false;
1156 this.nextId = 0;
1157 this.tabsPrefix = tabsPrefix !== undefined ? toSegments(tabsPrefix) : undefined;
1158 }
1159 createView(ref, activatedRoute) {
1160 var _a;
1161 const url = getUrl(this.router, activatedRoute);
1162 const element = (_a = ref === null || ref === void 0 ? void 0 : ref.location) === null || _a === void 0 ? void 0 : _a.nativeElement;
1163 const unlistenEvents = bindLifecycleEvents(this.zone, ref.instance, element);
1164 return {
1165 id: this.nextId++,
1166 stackId: computeStackId(this.tabsPrefix, url),
1167 unlistenEvents,
1168 element,
1169 ref,
1170 url,
1171 };
1172 }
1173 getExistingView(activatedRoute) {
1174 const activatedUrlKey = getUrl(this.router, activatedRoute);
1175 const view = this.views.find((vw) => vw.url === activatedUrlKey);
1176 if (view) {
1177 view.ref.changeDetectorRef.reattach();
1178 }
1179 return view;
1180 }
1181 setActive(enteringView) {
1182 var _a, _b;
1183 const consumeResult = this.navCtrl.consumeTransition();
1184 let { direction, animation, animationBuilder } = consumeResult;
1185 const leavingView = this.activeView;
1186 const tabSwitch = isTabSwitch(enteringView, leavingView);
1187 if (tabSwitch) {
1188 direction = 'back';
1189 animation = undefined;
1190 }
1191 const viewsSnapshot = this.views.slice();
1192 let currentNavigation;
1193 const router = this.router;
1194 // Angular >= 7.2.0
1195 if (router.getCurrentNavigation) {
1196 currentNavigation = router.getCurrentNavigation();
1197 // Angular < 7.2.0
1198 }
1199 else if ((_a = router.navigations) === null || _a === void 0 ? void 0 : _a.value) {
1200 currentNavigation = router.navigations.value;
1201 }
1202 /**
1203 * If the navigation action
1204 * sets `replaceUrl: true`
1205 * then we need to make sure
1206 * we remove the last item
1207 * from our views stack
1208 */
1209 if ((_b = currentNavigation === null || currentNavigation === void 0 ? void 0 : currentNavigation.extras) === null || _b === void 0 ? void 0 : _b.replaceUrl) {
1210 if (this.views.length > 0) {
1211 this.views.splice(-1, 1);
1212 }
1213 }
1214 const reused = this.views.includes(enteringView);
1215 const views = this.insertView(enteringView, direction);
1216 // Trigger change detection before transition starts
1217 // This will call ngOnInit() the first time too, just after the view
1218 // was attached to the dom, but BEFORE the transition starts
1219 if (!reused) {
1220 enteringView.ref.changeDetectorRef.detectChanges();
1221 }
1222 /**
1223 * If we are going back from a page that
1224 * was presented using a custom animation
1225 * we should default to using that
1226 * unless the developer explicitly
1227 * provided another animation.
1228 */
1229 const customAnimation = enteringView.animationBuilder;
1230 if (animationBuilder === undefined && direction === 'back' && !tabSwitch && customAnimation !== undefined) {
1231 animationBuilder = customAnimation;
1232 }
1233 /**
1234 * Save any custom animation so that navigating
1235 * back will use this custom animation by default.
1236 */
1237 if (leavingView) {
1238 leavingView.animationBuilder = animationBuilder;
1239 }
1240 // Wait until previous transitions finish
1241 return this.zone.runOutsideAngular(() => {
1242 return this.wait(() => {
1243 // disconnect leaving page from change detection to
1244 // reduce jank during the page transition
1245 if (leavingView) {
1246 leavingView.ref.changeDetectorRef.detach();
1247 }
1248 // In case the enteringView is the same as the leavingPage we need to reattach()
1249 enteringView.ref.changeDetectorRef.reattach();
1250 return this.transition(enteringView, leavingView, animation, this.canGoBack(1), false, animationBuilder)
1251 .then(() => cleanupAsync(enteringView, views, viewsSnapshot, this.location, this.zone))
1252 .then(() => ({
1253 enteringView,
1254 direction,
1255 animation,
1256 tabSwitch,
1257 }));
1258 });
1259 });
1260 }
1261 canGoBack(deep, stackId = this.getActiveStackId()) {
1262 return this.getStack(stackId).length > deep;
1263 }
1264 pop(deep, stackId = this.getActiveStackId()) {
1265 return this.zone.run(() => {
1266 var _a, _b;
1267 const views = this.getStack(stackId);
1268 if (views.length <= deep) {
1269 return Promise.resolve(false);
1270 }
1271 const view = views[views.length - deep - 1];
1272 let url = view.url;
1273 const viewSavedData = view.savedData;
1274 if (viewSavedData) {
1275 const primaryOutlet = viewSavedData.get('primary');
1276 if ((_b = (_a = primaryOutlet === null || primaryOutlet === void 0 ? void 0 : primaryOutlet.route) === null || _a === void 0 ? void 0 : _a._routerState) === null || _b === void 0 ? void 0 : _b.snapshot.url) {
1277 url = primaryOutlet.route._routerState.snapshot.url;
1278 }
1279 }
1280 const { animationBuilder } = this.navCtrl.consumeTransition();
1281 return this.navCtrl.navigateBack(url, Object.assign(Object.assign({}, view.savedExtras), { animation: animationBuilder })).then(() => true);
1282 });
1283 }
1284 startBackTransition() {
1285 const leavingView = this.activeView;
1286 if (leavingView) {
1287 const views = this.getStack(leavingView.stackId);
1288 const enteringView = views[views.length - 2];
1289 const customAnimation = enteringView.animationBuilder;
1290 return this.wait(() => {
1291 return this.transition(enteringView, // entering view
1292 leavingView, // leaving view
1293 'back', this.canGoBack(2), true, customAnimation);
1294 });
1295 }
1296 return Promise.resolve();
1297 }
1298 endBackTransition(shouldComplete) {
1299 if (shouldComplete) {
1300 this.skipTransition = true;
1301 this.pop(1);
1302 }
1303 else if (this.activeView) {
1304 cleanup(this.activeView, this.views, this.views, this.location, this.zone);
1305 }
1306 }
1307 getLastUrl(stackId) {
1308 const views = this.getStack(stackId);
1309 return views.length > 0 ? views[views.length - 1] : undefined;
1310 }
1311 /**
1312 * @internal
1313 */
1314 getRootUrl(stackId) {
1315 const views = this.getStack(stackId);
1316 return views.length > 0 ? views[0] : undefined;
1317 }
1318 getActiveStackId() {
1319 return this.activeView ? this.activeView.stackId : undefined;
1320 }
1321 /**
1322 * @internal
1323 */
1324 getActiveView() {
1325 return this.activeView;
1326 }
1327 hasRunningTask() {
1328 return this.runningTask !== undefined;
1329 }
1330 destroy() {
1331 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1332 this.containerEl = undefined;
1333 this.views.forEach(destroyView);
1334 this.activeView = undefined;
1335 this.views = [];
1336 }
1337 getStack(stackId) {
1338 return this.views.filter((v) => v.stackId === stackId);
1339 }
1340 insertView(enteringView, direction) {
1341 this.activeView = enteringView;
1342 this.views = insertView(this.views, enteringView, direction);
1343 return this.views.slice();
1344 }
1345 transition(enteringView, leavingView, direction, showGoBack, progressAnimation, animationBuilder) {
1346 if (this.skipTransition) {
1347 this.skipTransition = false;
1348 return Promise.resolve(false);
1349 }
1350 if (leavingView === enteringView) {
1351 return Promise.resolve(false);
1352 }
1353 const enteringEl = enteringView ? enteringView.element : undefined;
1354 const leavingEl = leavingView ? leavingView.element : undefined;
1355 const containerEl = this.containerEl;
1356 if (enteringEl && enteringEl !== leavingEl) {
1357 enteringEl.classList.add('ion-page');
1358 enteringEl.classList.add('ion-page-invisible');
1359 if (enteringEl.parentElement !== containerEl) {
1360 containerEl.appendChild(enteringEl);
1361 }
1362 if (containerEl.commit) {
1363 return containerEl.commit(enteringEl, leavingEl, {
1364 duration: direction === undefined ? 0 : undefined,
1365 direction,
1366 showGoBack,
1367 progressAnimation,
1368 animationBuilder,
1369 });
1370 }
1371 }
1372 return Promise.resolve(false);
1373 }
1374 wait(task) {
1375 return __awaiter(this, void 0, void 0, function* () {
1376 if (this.runningTask !== undefined) {
1377 yield this.runningTask;
1378 this.runningTask = undefined;
1379 }
1380 const promise = (this.runningTask = task());
1381 promise.finally(() => (this.runningTask = undefined));
1382 return promise;
1383 });
1384 }
1385}
1386const cleanupAsync = (activeRoute, views, viewsSnapshot, location, zone) => {
1387 if (typeof requestAnimationFrame === 'function') {
1388 return new Promise((resolve) => {
1389 requestAnimationFrame(() => {
1390 cleanup(activeRoute, views, viewsSnapshot, location, zone);
1391 resolve();
1392 });
1393 });
1394 }
1395 return Promise.resolve();
1396};
1397const cleanup = (activeRoute, views, viewsSnapshot, location, zone) => {
1398 /**
1399 * Re-enter the Angular zone when destroying page components. This will allow
1400 * lifecycle events (`ngOnDestroy`) to be run inside the Angular zone.
1401 */
1402 zone.run(() => viewsSnapshot.filter((view) => !views.includes(view)).forEach(destroyView));
1403 views.forEach((view) => {
1404 /**
1405 * In the event that a user navigated multiple
1406 * times in rapid succession, we want to make sure
1407 * we don't pre-emptively detach a view while
1408 * it is in mid-transition.
1409 *
1410 * In this instance we also do not care about query
1411 * params or fragments as it will be the same view regardless
1412 */
1413 const locationWithoutParams = location.path().split('?')[0];
1414 const locationWithoutFragment = locationWithoutParams.split('#')[0];
1415 if (view !== activeRoute && view.url !== locationWithoutFragment) {
1416 const element = view.element;
1417 element.setAttribute('aria-hidden', 'true');
1418 element.classList.add('ion-page-hidden');
1419 view.ref.changeDetectorRef.detach();
1420 }
1421 });
1422};
1423
1424// TODO(FW-2827): types
1425// eslint-disable-next-line @angular-eslint/directive-class-suffix
1426class IonRouterOutlet {
1427 constructor(name, tabs, commonLocation, elementRef, router, zone, activatedRoute, parentOutlet) {
1428 this.parentOutlet = parentOutlet;
1429 this.activatedView = null;
1430 // Maintain map of activated route proxies for each component instance
1431 this.proxyMap = new WeakMap();
1432 // Keep the latest activated route in a subject for the proxy routes to switch map to
1433 this.currentActivatedRoute$ = new BehaviorSubject(null);
1434 this.activated = null;
1435 this._activatedRoute = null;
1436 /**
1437 * The name of the outlet
1438 */
1439 this.name = PRIMARY_OUTLET;
1440 /** @internal */
1441 this.stackWillChange = new EventEmitter();
1442 /** @internal */
1443 this.stackDidChange = new EventEmitter();
1444 // eslint-disable-next-line @angular-eslint/no-output-rename
1445 this.activateEvents = new EventEmitter();
1446 // eslint-disable-next-line @angular-eslint/no-output-rename
1447 this.deactivateEvents = new EventEmitter();
1448 this.parentContexts = inject(ChildrenOutletContexts);
1449 this.location = inject(ViewContainerRef);
1450 this.environmentInjector = inject(EnvironmentInjector);
1451 this.inputBinder = inject(INPUT_BINDER, { optional: true });
1452 /** @nodoc */
1453 this.supportsBindingToComponentInputs = true;
1454 // Ionic providers
1455 this.config = inject(Config);
1456 this.navCtrl = inject(NavController);
1457 this.nativeEl = elementRef.nativeElement;
1458 this.name = name || PRIMARY_OUTLET;
1459 this.tabsPrefix = tabs === 'true' ? getUrl(router, activatedRoute) : undefined;
1460 this.stackCtrl = new StackController(this.tabsPrefix, this.nativeEl, router, this.navCtrl, zone, commonLocation);
1461 this.parentContexts.onChildOutletCreated(this.name, this);
1462 }
1463 /** @internal */
1464 get activatedComponentRef() {
1465 return this.activated;
1466 }
1467 set animation(animation) {
1468 this.nativeEl.animation = animation;
1469 }
1470 set animated(animated) {
1471 this.nativeEl.animated = animated;
1472 }
1473 set swipeGesture(swipe) {
1474 this._swipeGesture = swipe;
1475 this.nativeEl.swipeHandler = swipe
1476 ? {
1477 canStart: () => this.stackCtrl.canGoBack(1) && !this.stackCtrl.hasRunningTask(),
1478 onStart: () => this.stackCtrl.startBackTransition(),
1479 onEnd: (shouldContinue) => this.stackCtrl.endBackTransition(shouldContinue),
1480 }
1481 : undefined;
1482 }
1483 ngOnDestroy() {
1484 var _a;
1485 this.stackCtrl.destroy();
1486 (_a = this.inputBinder) === null || _a === void 0 ? void 0 : _a.unsubscribeFromRouteData(this);
1487 }
1488 getContext() {
1489 return this.parentContexts.getContext(this.name);
1490 }
1491 ngOnInit() {
1492 this.initializeOutletWithName();
1493 }
1494 // Note: Ionic deviates from the Angular Router implementation here
1495 initializeOutletWithName() {
1496 if (!this.activated) {
1497 // If the outlet was not instantiated at the time the route got activated we need to populate
1498 // the outlet when it is initialized (ie inside a NgIf)
1499 const context = this.getContext();
1500 if (context === null || context === void 0 ? void 0 : context.route) {
1501 this.activateWith(context.route, context.injector);
1502 }
1503 }
1504 new Promise((resolve) => componentOnReady(this.nativeEl, resolve)).then(() => {
1505 if (this._swipeGesture === undefined) {
1506 this.swipeGesture = this.config.getBoolean('swipeBackEnabled', this.nativeEl.mode === 'ios');
1507 }
1508 });
1509 }
1510 get isActivated() {
1511 return !!this.activated;
1512 }
1513 get component() {
1514 if (!this.activated) {
1515 throw new Error('Outlet is not activated');
1516 }
1517 return this.activated.instance;
1518 }
1519 get activatedRoute() {
1520 if (!this.activated) {
1521 throw new Error('Outlet is not activated');
1522 }
1523 return this._activatedRoute;
1524 }
1525 get activatedRouteData() {
1526 if (this._activatedRoute) {
1527 return this._activatedRoute.snapshot.data;
1528 }
1529 return {};
1530 }
1531 /**
1532 * Called when the `RouteReuseStrategy` instructs to detach the subtree
1533 */
1534 detach() {
1535 throw new Error('incompatible reuse strategy');
1536 }
1537 /**
1538 * Called when the `RouteReuseStrategy` instructs to re-attach a previously detached subtree
1539 */
1540 // eslint-disable-next-line @typescript-eslint/no-unused-vars
1541 attach(_ref, _activatedRoute) {
1542 throw new Error('incompatible reuse strategy');
1543 }
1544 deactivate() {
1545 if (this.activated) {
1546 if (this.activatedView) {
1547 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1548 const context = this.getContext();
1549 this.activatedView.savedData = new Map(context.children['contexts']);
1550 /**
1551 * Angular v11.2.10 introduced a change
1552 * where this route context is cleared out when
1553 * a router-outlet is deactivated, However,
1554 * we need this route information in order to
1555 * return a user back to the correct tab when
1556 * leaving and then going back to the tab context.
1557 */
1558 const primaryOutlet = this.activatedView.savedData.get('primary');
1559 if (primaryOutlet && context.route) {
1560 primaryOutlet.route = Object.assign({}, context.route);
1561 }
1562 /**
1563 * Ensure we are saving the NavigationExtras
1564 * data otherwise it will be lost
1565 */
1566 this.activatedView.savedExtras = {};
1567 if (context.route) {
1568 const contextSnapshot = context.route.snapshot;
1569 this.activatedView.savedExtras.queryParams = contextSnapshot.queryParams;
1570 this.activatedView.savedExtras.fragment = contextSnapshot.fragment;
1571 }
1572 }
1573 const c = this.component;
1574 this.activatedView = null;
1575 this.activated = null;
1576 this._activatedRoute = null;
1577 this.deactivateEvents.emit(c);
1578 }
1579 }
1580 activateWith(activatedRoute, environmentInjector) {
1581 var _a, _b;
1582 if (this.isActivated) {
1583 throw new Error('Cannot activate an already activated outlet');
1584 }
1585 this._activatedRoute = activatedRoute;
1586 let cmpRef;
1587 let enteringView = this.stackCtrl.getExistingView(activatedRoute);
1588 if (enteringView) {
1589 cmpRef = this.activated = enteringView.ref;
1590 const saved = enteringView.savedData;
1591 if (saved) {
1592 // self-restore
1593 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1594 const context = this.getContext();
1595 context.children['contexts'] = saved;
1596 }
1597 // Updated activated route proxy for this component
1598 this.updateActivatedRouteProxy(cmpRef.instance, activatedRoute);
1599 }
1600 else {
1601 const snapshot = activatedRoute._futureSnapshot;
1602 /**
1603 * Angular 14 introduces a new `loadComponent` property to the route config.
1604 * This function will assign a `component` property to the route snapshot.
1605 * We check for the presence of this property to determine if the route is
1606 * using standalone components.
1607 */
1608 const childContexts = this.parentContexts.getOrCreateContext(this.name).children;
1609 // We create an activated route proxy object that will maintain future updates for this component
1610 // over its lifecycle in the stack.
1611 const component$ = new BehaviorSubject(null);
1612 const activatedRouteProxy = this.createActivatedRouteProxy(component$, activatedRoute);
1613 const injector = new OutletInjector(activatedRouteProxy, childContexts, this.location.injector);
1614 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1615 const component = (_a = snapshot.routeConfig.component) !== null && _a !== void 0 ? _a : snapshot.component;
1616 cmpRef = this.activated = this.location.createComponent(component, {
1617 index: this.location.length,
1618 injector,
1619 environmentInjector: environmentInjector !== null && environmentInjector !== void 0 ? environmentInjector : this.environmentInjector,
1620 });
1621 // Once the component is created we can push it to our local subject supplied to the proxy
1622 component$.next(cmpRef.instance);
1623 // Calling `markForCheck` to make sure we will run the change detection when the
1624 // `RouterOutlet` is inside a `ChangeDetectionStrategy.OnPush` component.
1625 enteringView = this.stackCtrl.createView(this.activated, activatedRoute);
1626 // Store references to the proxy by component
1627 this.proxyMap.set(cmpRef.instance, activatedRouteProxy);
1628 this.currentActivatedRoute$.next({ component: cmpRef.instance, activatedRoute });
1629 }
1630 (_b = this.inputBinder) === null || _b === void 0 ? void 0 : _b.bindActivatedRouteToOutletComponent(this);
1631 this.activatedView = enteringView;
1632 /**
1633 * The top outlet is set prior to the entering view's transition completing,
1634 * so that when we have nested outlets (e.g. ion-tabs inside an ion-router-outlet),
1635 * the tabs outlet will be assigned as the top outlet when a view inside tabs is
1636 * activated.
1637 *
1638 * In this scenario, activeWith is called for both the tabs and the root router outlet.
1639 * To avoid a race condition, we assign the top outlet synchronously.
1640 */
1641 this.navCtrl.setTopOutlet(this);
1642 const leavingView = this.stackCtrl.getActiveView();
1643 this.stackWillChange.emit({
1644 enteringView,
1645 tabSwitch: isTabSwitch(enteringView, leavingView),
1646 });
1647 this.stackCtrl.setActive(enteringView).then((data) => {
1648 this.activateEvents.emit(cmpRef.instance);
1649 this.stackDidChange.emit(data);
1650 });
1651 }
1652 /**
1653 * Returns `true` if there are pages in the stack to go back.
1654 */
1655 canGoBack(deep = 1, stackId) {
1656 return this.stackCtrl.canGoBack(deep, stackId);
1657 }
1658 /**
1659 * Resolves to `true` if it the outlet was able to sucessfully pop the last N pages.
1660 */
1661 pop(deep = 1, stackId) {
1662 return this.stackCtrl.pop(deep, stackId);
1663 }
1664 /**
1665 * Returns the URL of the active page of each stack.
1666 */
1667 getLastUrl(stackId) {
1668 const active = this.stackCtrl.getLastUrl(stackId);
1669 return active ? active.url : undefined;
1670 }
1671 /**
1672 * Returns the RouteView of the active page of each stack.
1673 * @internal
1674 */
1675 getLastRouteView(stackId) {
1676 return this.stackCtrl.getLastUrl(stackId);
1677 }
1678 /**
1679 * Returns the root view in the tab stack.
1680 * @internal
1681 */
1682 getRootView(stackId) {
1683 return this.stackCtrl.getRootUrl(stackId);
1684 }
1685 /**
1686 * Returns the active stack ID. In the context of ion-tabs, it means the active tab.
1687 */
1688 getActiveStackId() {
1689 return this.stackCtrl.getActiveStackId();
1690 }
1691 /**
1692 * Since the activated route can change over the life time of a component in an ion router outlet, we create
1693 * a proxy so that we can update the values over time as a user navigates back to components already in the stack.
1694 */
1695 createActivatedRouteProxy(component$, activatedRoute) {
1696 const proxy = new ActivatedRoute();
1697 proxy._futureSnapshot = activatedRoute._futureSnapshot;
1698 proxy._routerState = activatedRoute._routerState;
1699 proxy.snapshot = activatedRoute.snapshot;
1700 proxy.outlet = activatedRoute.outlet;
1701 proxy.component = activatedRoute.component;
1702 // Setup wrappers for the observables so consumers don't have to worry about switching to new observables as the state updates
1703 proxy._paramMap = this.proxyObservable(component$, 'paramMap');
1704 proxy._queryParamMap = this.proxyObservable(component$, 'queryParamMap');
1705 proxy.url = this.proxyObservable(component$, 'url');
1706 proxy.params = this.proxyObservable(component$, 'params');
1707 proxy.queryParams = this.proxyObservable(component$, 'queryParams');
1708 proxy.fragment = this.proxyObservable(component$, 'fragment');
1709 proxy.data = this.proxyObservable(component$, 'data');
1710 return proxy;
1711 }
1712 /**
1713 * Create a wrapped observable that will switch to the latest activated route matched by the given component
1714 */
1715 proxyObservable(component$, path) {
1716 return component$.pipe(
1717 // First wait until the component instance is pushed
1718 filter((component) => !!component), switchMap((component) => this.currentActivatedRoute$.pipe(filter((current) => current !== null && current.component === component), switchMap((current) => current && current.activatedRoute[path]), distinctUntilChanged())));
1719 }
1720 /**
1721 * Updates the activated route proxy for the given component to the new incoming router state
1722 */
1723 updateActivatedRouteProxy(component, activatedRoute) {
1724 const proxy = this.proxyMap.get(component);
1725 if (!proxy) {
1726 throw new Error(`Could not find activated route proxy for view`);
1727 }
1728 proxy._futureSnapshot = activatedRoute._futureSnapshot;
1729 proxy._routerState = activatedRoute._routerState;
1730 proxy.snapshot = activatedRoute.snapshot;
1731 proxy.outlet = activatedRoute.outlet;
1732 proxy.component = activatedRoute.component;
1733 this.currentActivatedRoute$.next({ component, activatedRoute });
1734 }
1735}
1736/** @nocollapse */ IonRouterOutlet.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: IonRouterOutlet, deps: [{ token: 'name', attribute: true }, { token: 'tabs', attribute: true, optional: true }, { token: i1.Location }, { token: i0.ElementRef }, { token: i3.Router }, { token: i0.NgZone }, { token: i3.ActivatedRoute }, { token: IonRouterOutlet, optional: true, skipSelf: true }], target: i0.ɵɵFactoryTarget.Directive });
1737/** @nocollapse */ IonRouterOutlet.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: IonRouterOutlet, selector: "ion-router-outlet", inputs: { animated: "animated", animation: "animation", mode: "mode", swipeGesture: "swipeGesture", name: "name" }, outputs: { stackWillChange: "stackWillChange", stackDidChange: "stackDidChange", activateEvents: "activate", deactivateEvents: "deactivate" }, exportAs: ["outlet"], ngImport: i0 });
1738i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: IonRouterOutlet, decorators: [{
1739 type: Directive,
1740 args: [{
1741 selector: 'ion-router-outlet',
1742 exportAs: 'outlet',
1743 // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
1744 inputs: ['animated', 'animation', 'mode', 'swipeGesture'],
1745 }]
1746 }], ctorParameters: function () {
1747 return [{ type: undefined, decorators: [{
1748 type: Attribute,
1749 args: ['name']
1750 }] }, { type: undefined, decorators: [{
1751 type: Optional
1752 }, {
1753 type: Attribute,
1754 args: ['tabs']
1755 }] }, { type: i1.Location }, { type: i0.ElementRef }, { type: i3.Router }, { type: i0.NgZone }, { type: i3.ActivatedRoute }, { type: IonRouterOutlet, decorators: [{
1756 type: SkipSelf
1757 }, {
1758 type: Optional
1759 }] }];
1760 }, propDecorators: { name: [{
1761 type: Input
1762 }], stackWillChange: [{
1763 type: Output
1764 }], stackDidChange: [{
1765 type: Output
1766 }], activateEvents: [{
1767 type: Output,
1768 args: ['activate']
1769 }], deactivateEvents: [{
1770 type: Output,
1771 args: ['deactivate']
1772 }] } });
1773class OutletInjector {
1774 constructor(route, childContexts, parent) {
1775 this.route = route;
1776 this.childContexts = childContexts;
1777 this.parent = parent;
1778 }
1779 get(token, notFoundValue) {
1780 if (token === ActivatedRoute) {
1781 return this.route;
1782 }
1783 if (token === ChildrenOutletContexts) {
1784 return this.childContexts;
1785 }
1786 return this.parent.get(token, notFoundValue);
1787 }
1788}
1789// TODO: FW-4785 - Remove this once Angular 15 support is dropped
1790const INPUT_BINDER = new InjectionToken('');
1791/**
1792 * Injectable used as a tree-shakable provider for opting in to binding router data to component
1793 * inputs.
1794 *
1795 * The RouterOutlet registers itself with this service when an `ActivatedRoute` is attached or
1796 * activated. When this happens, the service subscribes to the `ActivatedRoute` observables (params,
1797 * queryParams, data) and sets the inputs of the component using `ComponentRef.setInput`.
1798 * Importantly, when an input does not have an item in the route data with a matching key, this
1799 * input is set to `undefined`. If it were not done this way, the previous information would be
1800 * retained if the data got removed from the route (i.e. if a query parameter is removed).
1801 *
1802 * The `RouterOutlet` should unregister itself when destroyed via `unsubscribeFromRouteData` so that
1803 * the subscriptions are cleaned up.
1804 */
1805class RoutedComponentInputBinder {
1806 constructor() {
1807 this.outletDataSubscriptions = new Map();
1808 }
1809 bindActivatedRouteToOutletComponent(outlet) {
1810 this.unsubscribeFromRouteData(outlet);
1811 this.subscribeToRouteData(outlet);
1812 }
1813 unsubscribeFromRouteData(outlet) {
1814 var _a;
1815 (_a = this.outletDataSubscriptions.get(outlet)) === null || _a === void 0 ? void 0 : _a.unsubscribe();
1816 this.outletDataSubscriptions.delete(outlet);
1817 }
1818 subscribeToRouteData(outlet) {
1819 const { activatedRoute } = outlet;
1820 const dataSubscription = combineLatest([activatedRoute.queryParams, activatedRoute.params, activatedRoute.data])
1821 .pipe(switchMap(([queryParams, params, data], index) => {
1822 data = Object.assign(Object.assign(Object.assign({}, queryParams), params), data);
1823 // Get the first result from the data subscription synchronously so it's available to
1824 // the component as soon as possible (and doesn't require a second change detection).
1825 if (index === 0) {
1826 return of(data);
1827 }
1828 // Promise.resolve is used to avoid synchronously writing the wrong data when
1829 // two of the Observables in the `combineLatest` stream emit one after
1830 // another.
1831 return Promise.resolve(data);
1832 }))
1833 .subscribe((data) => {
1834 // Outlet may have been deactivated or changed names to be associated with a different
1835 // route
1836 if (!outlet.isActivated ||
1837 !outlet.activatedComponentRef ||
1838 outlet.activatedRoute !== activatedRoute ||
1839 activatedRoute.component === null) {
1840 this.unsubscribeFromRouteData(outlet);
1841 return;
1842 }
1843 const mirror = reflectComponentType(activatedRoute.component);
1844 if (!mirror) {
1845 this.unsubscribeFromRouteData(outlet);
1846 return;
1847 }
1848 for (const { templateName } of mirror.inputs) {
1849 outlet.activatedComponentRef.setInput(templateName, data[templateName]);
1850 }
1851 });
1852 this.outletDataSubscriptions.set(outlet, dataSubscription);
1853 }
1854}
1855/** @nocollapse */ RoutedComponentInputBinder.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: RoutedComponentInputBinder, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1856/** @nocollapse */ RoutedComponentInputBinder.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: RoutedComponentInputBinder });
1857i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: RoutedComponentInputBinder, decorators: [{
1858 type: Injectable
1859 }] });
1860const provideComponentInputBinding = () => {
1861 return {
1862 provide: INPUT_BINDER,
1863 useFactory: componentInputBindingFactory,
1864 deps: [Router],
1865 };
1866};
1867function componentInputBindingFactory(router) {
1868 /**
1869 * We cast the router to any here, since the componentInputBindingEnabled
1870 * property is not available until Angular v16.
1871 */
1872 if (router === null || router === void 0 ? void 0 : router.componentInputBindingEnabled) {
1873 return new RoutedComponentInputBinder();
1874 }
1875 return null;
1876}
1877
1878const BACK_BUTTON_INPUTS = ['color', 'defaultHref', 'disabled', 'icon', 'mode', 'routerAnimation', 'text', 'type'];
1879let IonBackButton = class IonBackButton {
1880 constructor(routerOutlet, navCtrl, config, r, z, c) {
1881 this.routerOutlet = routerOutlet;
1882 this.navCtrl = navCtrl;
1883 this.config = config;
1884 this.r = r;
1885 this.z = z;
1886 c.detach();
1887 this.el = this.r.nativeElement;
1888 }
1889 /**
1890 * @internal
1891 */
1892 onClick(ev) {
1893 var _a;
1894 const defaultHref = this.defaultHref || this.config.get('backButtonDefaultHref');
1895 if ((_a = this.routerOutlet) === null || _a === void 0 ? void 0 : _a.canGoBack()) {
1896 this.navCtrl.setDirection('back', undefined, undefined, this.routerAnimation);
1897 this.routerOutlet.pop();
1898 ev.preventDefault();
1899 }
1900 else if (defaultHref != null) {
1901 this.navCtrl.navigateBack(defaultHref, { animation: this.routerAnimation });
1902 ev.preventDefault();
1903 }
1904 }
1905};
1906/** @nocollapse */ IonBackButton.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: IonBackButton, deps: [{ token: IonRouterOutlet, optional: true }, { token: NavController }, { token: Config }, { token: i0.ElementRef }, { token: i0.NgZone }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive });
1907/** @nocollapse */ IonBackButton.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: IonBackButton, inputs: { color: "color", defaultHref: "defaultHref", disabled: "disabled", icon: "icon", mode: "mode", routerAnimation: "routerAnimation", text: "text", type: "type" }, host: { listeners: { "click": "onClick($event)" } }, ngImport: i0 });
1908IonBackButton = __decorate([
1909 ProxyCmp({
1910 inputs: BACK_BUTTON_INPUTS,
1911 })
1912], IonBackButton);
1913i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: IonBackButton, decorators: [{
1914 type: Directive,
1915 args: [{
1916 // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
1917 inputs: BACK_BUTTON_INPUTS,
1918 }]
1919 }], ctorParameters: function () {
1920 return [{ type: IonRouterOutlet, decorators: [{
1921 type: Optional
1922 }] }, { type: NavController }, { type: Config }, { type: i0.ElementRef }, { type: i0.NgZone }, { type: i0.ChangeDetectorRef }];
1923 }, propDecorators: { defaultHref: [{
1924 type: Input
1925 }], routerAnimation: [{
1926 type: Input
1927 }], onClick: [{
1928 type: HostListener,
1929 args: ['click', ['$event']]
1930 }] } });
1931
1932/**
1933 * Adds support for Ionic routing directions and animations to the base Angular router link directive.
1934 *
1935 * When the router link is clicked, the directive will assign the direction and
1936 * animation so that the routing integration will transition correctly.
1937 */
1938class RouterLinkDelegateDirective {
1939 constructor(locationStrategy, navCtrl, elementRef, router, routerLink) {
1940 this.locationStrategy = locationStrategy;
1941 this.navCtrl = navCtrl;
1942 this.elementRef = elementRef;
1943 this.router = router;
1944 this.routerLink = routerLink;
1945 this.routerDirection = 'forward';
1946 }
1947 ngOnInit() {
1948 this.updateTargetUrlAndHref();
1949 }
1950 ngOnChanges() {
1951 this.updateTargetUrlAndHref();
1952 }
1953 updateTargetUrlAndHref() {
1954 var _a;
1955 if ((_a = this.routerLink) === null || _a === void 0 ? void 0 : _a.urlTree) {
1956 const href = this.locationStrategy.prepareExternalUrl(this.router.serializeUrl(this.routerLink.urlTree));
1957 this.elementRef.nativeElement.href = href;
1958 }
1959 }
1960 /**
1961 * @internal
1962 */
1963 onClick(ev) {
1964 this.navCtrl.setDirection(this.routerDirection, undefined, undefined, this.routerAnimation);
1965 /**
1966 * This prevents the browser from
1967 * performing a page reload when pressing
1968 * an Ionic component with routerLink.
1969 * The page reload interferes with routing
1970 * and causes ion-back-button to disappear
1971 * since the local history is wiped on reload.
1972 */
1973 ev.preventDefault();
1974 }
1975}
1976/** @nocollapse */ RouterLinkDelegateDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: RouterLinkDelegateDirective, deps: [{ token: i1.LocationStrategy }, { token: NavController }, { token: i0.ElementRef }, { token: i3.Router }, { token: i3.RouterLink, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
1977/** @nocollapse */ RouterLinkDelegateDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: RouterLinkDelegateDirective, selector: ":not(a):not(area)[routerLink]", inputs: { routerDirection: "routerDirection", routerAnimation: "routerAnimation" }, host: { listeners: { "click": "onClick($event)" } }, usesOnChanges: true, ngImport: i0 });
1978i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: RouterLinkDelegateDirective, decorators: [{
1979 type: Directive,
1980 args: [{
1981 selector: ':not(a):not(area)[routerLink]',
1982 }]
1983 }], ctorParameters: function () {
1984 return [{ type: i1.LocationStrategy }, { type: NavController }, { type: i0.ElementRef }, { type: i3.Router }, { type: i3.RouterLink, decorators: [{
1985 type: Optional
1986 }] }];
1987 }, propDecorators: { routerDirection: [{
1988 type: Input
1989 }], routerAnimation: [{
1990 type: Input
1991 }], onClick: [{
1992 type: HostListener,
1993 args: ['click', ['$event']]
1994 }] } });
1995class RouterLinkWithHrefDelegateDirective {
1996 constructor(locationStrategy, navCtrl, elementRef, router, routerLink) {
1997 this.locationStrategy = locationStrategy;
1998 this.navCtrl = navCtrl;
1999 this.elementRef = elementRef;
2000 this.router = router;
2001 this.routerLink = routerLink;
2002 this.routerDirection = 'forward';
2003 }
2004 ngOnInit() {
2005 this.updateTargetUrlAndHref();
2006 }
2007 ngOnChanges() {
2008 this.updateTargetUrlAndHref();
2009 }
2010 updateTargetUrlAndHref() {
2011 var _a;
2012 if ((_a = this.routerLink) === null || _a === void 0 ? void 0 : _a.urlTree) {
2013 const href = this.locationStrategy.prepareExternalUrl(this.router.serializeUrl(this.routerLink.urlTree));
2014 this.elementRef.nativeElement.href = href;
2015 }
2016 }
2017 /**
2018 * @internal
2019 */
2020 onClick() {
2021 this.navCtrl.setDirection(this.routerDirection, undefined, undefined, this.routerAnimation);
2022 }
2023}
2024/** @nocollapse */ RouterLinkWithHrefDelegateDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: RouterLinkWithHrefDelegateDirective, deps: [{ token: i1.LocationStrategy }, { token: NavController }, { token: i0.ElementRef }, { token: i3.Router }, { token: i3.RouterLink, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
2025/** @nocollapse */ RouterLinkWithHrefDelegateDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: RouterLinkWithHrefDelegateDirective, selector: "a[routerLink],area[routerLink]", inputs: { routerDirection: "routerDirection", routerAnimation: "routerAnimation" }, host: { listeners: { "click": "onClick()" } }, usesOnChanges: true, ngImport: i0 });
2026i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: RouterLinkWithHrefDelegateDirective, decorators: [{
2027 type: Directive,
2028 args: [{
2029 selector: 'a[routerLink],area[routerLink]',
2030 }]
2031 }], ctorParameters: function () {
2032 return [{ type: i1.LocationStrategy }, { type: NavController }, { type: i0.ElementRef }, { type: i3.Router }, { type: i3.RouterLink, decorators: [{
2033 type: Optional
2034 }] }];
2035 }, propDecorators: { routerDirection: [{
2036 type: Input
2037 }], routerAnimation: [{
2038 type: Input
2039 }], onClick: [{
2040 type: HostListener,
2041 args: ['click']
2042 }] } });
2043
2044const NAV_INPUTS = ['animated', 'animation', 'root', 'rootParams', 'swipeGesture'];
2045const NAV_METHODS = [
2046 'push',
2047 'insert',
2048 'insertPages',
2049 'pop',
2050 'popTo',
2051 'popToRoot',
2052 'removeIndex',
2053 'setRoot',
2054 'setPages',
2055 'getActive',
2056 'getByIndex',
2057 'canGoBack',
2058 'getPrevious',
2059];
2060let IonNav = class IonNav {
2061 constructor(ref, environmentInjector, injector, angularDelegate, z, c) {
2062 this.z = z;
2063 c.detach();
2064 this.el = ref.nativeElement;
2065 ref.nativeElement.delegate = angularDelegate.create(environmentInjector, injector);
2066 proxyOutputs(this, this.el, ['ionNavDidChange', 'ionNavWillChange']);
2067 }
2068};
2069/** @nocollapse */ IonNav.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: IonNav, deps: [{ token: i0.ElementRef }, { token: i0.EnvironmentInjector }, { token: i0.Injector }, { token: AngularDelegate }, { token: i0.NgZone }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive });
2070/** @nocollapse */ IonNav.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: IonNav, inputs: { animated: "animated", animation: "animation", root: "root", rootParams: "rootParams", swipeGesture: "swipeGesture" }, ngImport: i0 });
2071IonNav = __decorate([
2072 ProxyCmp({
2073 inputs: NAV_INPUTS,
2074 methods: NAV_METHODS,
2075 })
2076], IonNav);
2077i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: IonNav, decorators: [{
2078 type: Directive,
2079 args: [{
2080 // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
2081 inputs: NAV_INPUTS,
2082 }]
2083 }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.EnvironmentInjector }, { type: i0.Injector }, { type: AngularDelegate }, { type: i0.NgZone }, { type: i0.ChangeDetectorRef }]; } });
2084
2085// eslint-disable-next-line @angular-eslint/directive-class-suffix
2086class IonTabs {
2087 constructor(navCtrl) {
2088 this.navCtrl = navCtrl;
2089 /**
2090 * Emitted before the tab view is changed.
2091 */
2092 this.ionTabsWillChange = new EventEmitter();
2093 /**
2094 * Emitted after the tab view is changed.
2095 */
2096 this.ionTabsDidChange = new EventEmitter();
2097 this.tabBarSlot = 'bottom';
2098 }
2099 ngAfterContentInit() {
2100 this.detectSlotChanges();
2101 }
2102 ngAfterContentChecked() {
2103 this.detectSlotChanges();
2104 }
2105 /**
2106 * @internal
2107 */
2108 onStackWillChange({ enteringView, tabSwitch }) {
2109 const stackId = enteringView.stackId;
2110 if (tabSwitch && stackId !== undefined) {
2111 this.ionTabsWillChange.emit({ tab: stackId });
2112 }
2113 }
2114 /**
2115 * @internal
2116 */
2117 onStackDidChange({ enteringView, tabSwitch }) {
2118 const stackId = enteringView.stackId;
2119 if (tabSwitch && stackId !== undefined) {
2120 if (this.tabBar) {
2121 this.tabBar.selectedTab = stackId;
2122 }
2123 this.ionTabsDidChange.emit({ tab: stackId });
2124 }
2125 }
2126 /**
2127 * When a tab button is clicked, there are several scenarios:
2128 * 1. If the selected tab is currently active (the tab button has been clicked
2129 * again), then it should go to the root view for that tab.
2130 *
2131 * a. Get the saved root view from the router outlet. If the saved root view
2132 * matches the tabRootUrl, set the route view to this view including the
2133 * navigation extras.
2134 * b. If the saved root view from the router outlet does
2135 * not match, navigate to the tabRootUrl. No navigation extras are
2136 * included.
2137 *
2138 * 2. If the current tab tab is not currently selected, get the last route
2139 * view from the router outlet.
2140 *
2141 * a. If the last route view exists, navigate to that view including any
2142 * navigation extras
2143 * b. If the last route view doesn't exist, then navigate
2144 * to the default tabRootUrl
2145 */
2146 select(tabOrEvent) {
2147 const isTabString = typeof tabOrEvent === 'string';
2148 const tab = isTabString ? tabOrEvent : tabOrEvent.detail.tab;
2149 const alreadySelected = this.outlet.getActiveStackId() === tab;
2150 const tabRootUrl = `${this.outlet.tabsPrefix}/${tab}`;
2151 /**
2152 * If this is a nested tab, prevent the event
2153 * from bubbling otherwise the outer tabs
2154 * will respond to this event too, causing
2155 * the app to get directed to the wrong place.
2156 */
2157 if (!isTabString) {
2158 tabOrEvent.stopPropagation();
2159 }
2160 if (alreadySelected) {
2161 const activeStackId = this.outlet.getActiveStackId();
2162 const activeView = this.outlet.getLastRouteView(activeStackId);
2163 // If on root tab, do not navigate to root tab again
2164 if ((activeView === null || activeView === void 0 ? void 0 : activeView.url) === tabRootUrl) {
2165 return;
2166 }
2167 const rootView = this.outlet.getRootView(tab);
2168 const navigationExtras = rootView && tabRootUrl === rootView.url && rootView.savedExtras;
2169 return this.navCtrl.navigateRoot(tabRootUrl, Object.assign(Object.assign({}, navigationExtras), { animated: true, animationDirection: 'back' }));
2170 }
2171 else {
2172 const lastRoute = this.outlet.getLastRouteView(tab);
2173 /**
2174 * If there is a lastRoute, goto that, otherwise goto the fallback url of the
2175 * selected tab
2176 */
2177 const url = (lastRoute === null || lastRoute === void 0 ? void 0 : lastRoute.url) || tabRootUrl;
2178 const navigationExtras = lastRoute === null || lastRoute === void 0 ? void 0 : lastRoute.savedExtras;
2179 return this.navCtrl.navigateRoot(url, Object.assign(Object.assign({}, navigationExtras), { animated: true, animationDirection: 'back' }));
2180 }
2181 }
2182 getSelected() {
2183 return this.outlet.getActiveStackId();
2184 }
2185 /**
2186 * Detects changes to the slot attribute of the tab bar.
2187 *
2188 * If the slot attribute has changed, then the tab bar
2189 * should be relocated to the new slot position.
2190 */
2191 detectSlotChanges() {
2192 this.tabBars.forEach((tabBar) => {
2193 // el is a protected attribute from the generated component wrapper
2194 const currentSlot = tabBar.el.getAttribute('slot');
2195 if (currentSlot !== this.tabBarSlot) {
2196 this.tabBarSlot = currentSlot;
2197 this.relocateTabBar();
2198 }
2199 });
2200 }
2201 /**
2202 * Relocates the tab bar to the new slot position.
2203 */
2204 relocateTabBar() {
2205 /**
2206 * `el` is a protected attribute from the generated component wrapper.
2207 * To avoid having to manually create the wrapper for tab bar, we
2208 * cast the tab bar to any and access the protected attribute.
2209 */
2210 const tabBar = this.tabBar.el;
2211 if (this.tabBarSlot === 'top') {
2212 /**
2213 * A tab bar with a slot of "top" should be inserted
2214 * at the top of the container.
2215 */
2216 this.tabsInner.nativeElement.before(tabBar);
2217 }
2218 else {
2219 /**
2220 * A tab bar with a slot of "bottom" or without a slot
2221 * should be inserted at the end of the container.
2222 */
2223 this.tabsInner.nativeElement.after(tabBar);
2224 }
2225 }
2226}
2227/** @nocollapse */ IonTabs.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: IonTabs, deps: [{ token: NavController }], target: i0.ɵɵFactoryTarget.Directive });
2228/** @nocollapse */ IonTabs.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: IonTabs, selector: "ion-tabs", outputs: { ionTabsWillChange: "ionTabsWillChange", ionTabsDidChange: "ionTabsDidChange" }, host: { listeners: { "ionTabButtonClick": "select($event)" } }, viewQueries: [{ propertyName: "tabsInner", first: true, predicate: ["tabsInner"], descendants: true, read: ElementRef, static: true }], ngImport: i0 });
2229i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: IonTabs, decorators: [{
2230 type: Directive,
2231 args: [{
2232 selector: 'ion-tabs',
2233 }]
2234 }], ctorParameters: function () { return [{ type: NavController }]; }, propDecorators: { tabsInner: [{
2235 type: ViewChild,
2236 args: ['tabsInner', { read: ElementRef, static: true }]
2237 }], ionTabsWillChange: [{
2238 type: Output
2239 }], ionTabsDidChange: [{
2240 type: Output
2241 }], select: [{
2242 type: HostListener,
2243 args: ['ionTabButtonClick', ['$event']]
2244 }] } });
2245
2246const raf = (h) => {
2247 if (typeof __zone_symbol__requestAnimationFrame === 'function') {
2248 return __zone_symbol__requestAnimationFrame(h);
2249 }
2250 if (typeof requestAnimationFrame === 'function') {
2251 return requestAnimationFrame(h);
2252 }
2253 return setTimeout(h);
2254};
2255
2256// TODO(FW-2827): types
2257class ValueAccessor {
2258 constructor(injector, elementRef) {
2259 this.injector = injector;
2260 this.elementRef = elementRef;
2261 this.onChange = () => {
2262 /**/
2263 };
2264 this.onTouched = () => {
2265 /**/
2266 };
2267 }
2268 writeValue(value) {
2269 this.elementRef.nativeElement.value = this.lastValue = value;
2270 setIonicClasses(this.elementRef);
2271 }
2272 /**
2273 * Notifies the ControlValueAccessor of a change in the value of the control.
2274 *
2275 * This is called by each of the ValueAccessor directives when we want to update
2276 * the status and validity of the form control. For example with text components this
2277 * is called when the ionInput event is fired. For select components this is called
2278 * when the ionChange event is fired.
2279 *
2280 * This also updates the Ionic form status classes on the element.
2281 *
2282 * @param el The component element.
2283 * @param value The new value of the control.
2284 */
2285 handleValueChange(el, value) {
2286 if (el === this.elementRef.nativeElement) {
2287 if (value !== this.lastValue) {
2288 this.lastValue = value;
2289 this.onChange(value);
2290 }
2291 setIonicClasses(this.elementRef);
2292 }
2293 }
2294 _handleBlurEvent(el) {
2295 if (el === this.elementRef.nativeElement) {
2296 this.onTouched();
2297 setIonicClasses(this.elementRef);
2298 }
2299 }
2300 registerOnChange(fn) {
2301 this.onChange = fn;
2302 }
2303 registerOnTouched(fn) {
2304 this.onTouched = fn;
2305 }
2306 setDisabledState(isDisabled) {
2307 this.elementRef.nativeElement.disabled = isDisabled;
2308 }
2309 ngOnDestroy() {
2310 if (this.statusChanges) {
2311 this.statusChanges.unsubscribe();
2312 }
2313 }
2314 ngAfterViewInit() {
2315 let ngControl;
2316 try {
2317 ngControl = this.injector.get(NgControl);
2318 }
2319 catch (_a) {
2320 /* No FormControl or ngModel binding */
2321 }
2322 if (!ngControl) {
2323 return;
2324 }
2325 // Listen for changes in validity, disabled, or pending states
2326 if (ngControl.statusChanges) {
2327 this.statusChanges = ngControl.statusChanges.subscribe(() => setIonicClasses(this.elementRef));
2328 }
2329 /**
2330 * TODO FW-2787: Remove this in favor of https://github.com/angular/angular/issues/10887
2331 * whenever it is implemented.
2332 */
2333 const formControl = ngControl.control;
2334 if (formControl) {
2335 const methodsToPatch = ['markAsTouched', 'markAllAsTouched', 'markAsUntouched', 'markAsDirty', 'markAsPristine'];
2336 methodsToPatch.forEach((method) => {
2337 if (typeof formControl[method] !== 'undefined') {
2338 const oldFn = formControl[method].bind(formControl);
2339 formControl[method] = (...params) => {
2340 oldFn(...params);
2341 setIonicClasses(this.elementRef);
2342 };
2343 }
2344 });
2345 }
2346 }
2347}
2348/** @nocollapse */ ValueAccessor.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: ValueAccessor, deps: [{ token: i0.Injector }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
2349/** @nocollapse */ ValueAccessor.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: ValueAccessor, host: { listeners: { "ionBlur": "_handleBlurEvent($event.target)" } }, ngImport: i0 });
2350i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: ValueAccessor, decorators: [{
2351 type: Directive
2352 }], ctorParameters: function () { return [{ type: i0.Injector }, { type: i0.ElementRef }]; }, propDecorators: { _handleBlurEvent: [{
2353 type: HostListener,
2354 args: ['ionBlur', ['$event.target']]
2355 }] } });
2356const setIonicClasses = (element) => {
2357 raf(() => {
2358 const input = element.nativeElement;
2359 const hasValue = input.value != null && input.value.toString().length > 0;
2360 const classes = getClasses(input);
2361 setClasses(input, classes);
2362 const item = input.closest('ion-item');
2363 if (item) {
2364 if (hasValue) {
2365 setClasses(item, [...classes, 'item-has-value']);
2366 }
2367 else {
2368 setClasses(item, classes);
2369 }
2370 }
2371 });
2372};
2373const getClasses = (element) => {
2374 const classList = element.classList;
2375 const classes = [];
2376 for (let i = 0; i < classList.length; i++) {
2377 const item = classList.item(i);
2378 if (item !== null && startsWith(item, 'ng-')) {
2379 classes.push(`ion-${item.substring(3)}`);
2380 }
2381 }
2382 return classes;
2383};
2384const setClasses = (element, classes) => {
2385 const classList = element.classList;
2386 classList.remove('ion-valid', 'ion-invalid', 'ion-touched', 'ion-untouched', 'ion-dirty', 'ion-pristine');
2387 classList.add(...classes);
2388};
2389const startsWith = (input, search) => {
2390 return input.substring(0, search.length) === search;
2391};
2392
2393/**
2394 * Provides a way to customize when activated routes get reused.
2395 */
2396class IonicRouteStrategy {
2397 /**
2398 * Whether the given route should detach for later reuse.
2399 */
2400 shouldDetach(_route) {
2401 return false;
2402 }
2403 /**
2404 * Returns `false`, meaning the route (and its subtree) is never reattached
2405 */
2406 shouldAttach(_route) {
2407 return false;
2408 }
2409 /**
2410 * A no-op; the route is never stored since this strategy never detaches routes for later re-use.
2411 */
2412 store(_route, _detachedTree) {
2413 return;
2414 }
2415 /**
2416 * Returns `null` because this strategy does not store routes for later re-use.
2417 */
2418 retrieve(_route) {
2419 return null;
2420 }
2421 /**
2422 * Determines if a route should be reused.
2423 * This strategy returns `true` when the future route config and
2424 * current route config are identical and all route parameters are identical.
2425 */
2426 shouldReuseRoute(future, curr) {
2427 if (future.routeConfig !== curr.routeConfig) {
2428 return false;
2429 }
2430 // checking router params
2431 const futureParams = future.params;
2432 const currentParams = curr.params;
2433 const keysA = Object.keys(futureParams);
2434 const keysB = Object.keys(currentParams);
2435 if (keysA.length !== keysB.length) {
2436 return false;
2437 }
2438 // Test for A's keys different from B.
2439 for (const key of keysA) {
2440 if (currentParams[key] !== futureParams[key]) {
2441 return false;
2442 }
2443 }
2444 return true;
2445 }
2446}
2447
2448// TODO(FW-2827): types
2449class OverlayBaseController {
2450 constructor(ctrl) {
2451 this.ctrl = ctrl;
2452 }
2453 /**
2454 * Creates a new overlay
2455 */
2456 create(opts) {
2457 return this.ctrl.create((opts || {}));
2458 }
2459 /**
2460 * When `id` is not provided, it dismisses the top overlay.
2461 */
2462 dismiss(data, role, id) {
2463 return this.ctrl.dismiss(data, role, id);
2464 }
2465 /**
2466 * Returns the top overlay.
2467 */
2468 getTop() {
2469 return this.ctrl.getTop();
2470 }
2471}
2472
2473/**
2474 * Generated bundle index. Do not edit.
2475 */
2476
2477export { AngularDelegate, Config, ConfigToken, DomController, IonBackButton, IonModal, IonNav, IonPopover, IonRouterOutlet, IonTabs, IonicRouteStrategy, MenuController, NavController, NavParams, OverlayBaseController, Platform, ProxyCmp, RouterLinkDelegateDirective, RouterLinkWithHrefDelegateDirective, ValueAccessor, bindLifecycleEvents, provideComponentInputBinding, raf, setIonicClasses };
2478//# sourceMappingURL=ionic-angular-common.mjs.map