UNPKG

@ionic/angular

Version:

Angular specific wrappers for @ionic/core

2,792 lines 112 kB
import * as i0 from '@angular/core';
import { 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';
import * as i3 from '@angular/router';
import { NavigationStart, NavigationCancel, NavigationError, PRIMARY_OUTLET, ChildrenOutletContexts, ActivatedRoute, Router } from '@angular/router';
import * as i1 from '@angular/common';
import { DOCUMENT } from '@angular/common';
import { isPlatform, getPlatforms, LIFECYCLE_WILL_ENTER, LIFECYCLE_DID_ENTER, LIFECYCLE_WILL_LEAVE, LIFECYCLE_DID_LEAVE, LIFECYCLE_WILL_UNLOAD, componentOnReady } from '@ionic/core/components';
import { Subject, fromEvent, BehaviorSubject, combineLatest, of } from 'rxjs';
import { __decorate } from 'tslib';
import { filter, switchMap, distinctUntilChanged } from 'rxjs/operators';
import { NgControl } from '@angular/forms';

class DomController {
    /**
     * Schedules a task to run during the READ phase of the next frame.
     * This task should only read the DOM, but never modify it.
     */
    read(cb) {
        getQueue().read(cb);
    }
    /**
     * Schedules a task to run during the WRITE phase of the next frame.
     * This task should write the DOM, but never READ it.
     */
    write(cb) {
        getQueue().write(cb);
    }
    /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DomController, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
    /** @nocollapse */ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DomController, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DomController, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root',
                }]
        }] });
const getQueue = () => {
    const win = typeof window !== 'undefined' ? window : null;
    if (win != null) {
        const Ionic = win.Ionic;
        if (Ionic?.queue) {
            return Ionic.queue;
        }
        return {
            read: (cb) => win.requestAnimationFrame(cb),
            write: (cb) => win.requestAnimationFrame(cb),
        };
    }
    return {
        read: (cb) => cb(),
        write: (cb) => cb(),
    };
};

class MenuController {
    menuController;
    constructor(menuController) {
        this.menuController = menuController;
    }
    /**
     * Programmatically open the Menu.
     * @param [menuId]  Optionally get the menu by its id, or side.
     * @return returns a promise when the menu is fully opened
     */
    open(menuId) {
        return this.menuController.open(menuId);
    }
    /**
     * Programmatically close the Menu. If no `menuId` is given as the first
     * argument then it'll close any menu which is open. If a `menuId`
     * is given then it'll close that exact menu.
     * @param [menuId]  Optionally get the menu by its id, or side.
     * @return returns a promise when the menu is fully closed
     */
    close(menuId) {
        return this.menuController.close(menuId);
    }
    /**
     * Toggle the menu. If it's closed, it will open, and if opened, it
     * will close.
     * @param [menuId]  Optionally get the menu by its id, or side.
     * @return returns a promise when the menu has been toggled
     */
    toggle(menuId) {
        return this.menuController.toggle(menuId);
    }
    /**
     * Used to enable or disable a menu. For example, there could be multiple
     * left menus, but only one of them should be able to be opened at the same
     * time. If there are multiple menus on the same side, then enabling one menu
     * will also automatically disable all the others that are on the same side.
     * @param [menuId]  Optionally get the menu by its id, or side.
     * @return Returns the instance of the menu, which is useful for chaining.
     */
    enable(shouldEnable, menuId) {
        return this.menuController.enable(shouldEnable, menuId);
    }
    /**
     * Used to enable or disable the ability to swipe open the menu.
     * @param shouldEnable  True if it should be swipe-able, false if not.
     * @param [menuId]  Optionally get the menu by its id, or side.
     * @return Returns the instance of the menu, which is useful for chaining.
     */
    swipeGesture(shouldEnable, menuId) {
        return this.menuController.swipeGesture(shouldEnable, menuId);
    }
    /**
     * @param [menuId] Optionally get the menu by its id, or side.
     * @return Returns true if the specified menu is currently open, otherwise false.
     * If the menuId is not specified, it returns true if ANY menu is currenly open.
     */
    isOpen(menuId) {
        return this.menuController.isOpen(menuId);
    }
    /**
     * @param [menuId]  Optionally get the menu by its id, or side.
     * @return Returns true if the menu is currently enabled, otherwise false.
     */
    isEnabled(menuId) {
        return this.menuController.isEnabled(menuId);
    }
    /**
     * Used to get a menu instance. If a `menuId` is not provided then it'll
     * return the first menu found. If a `menuId` is `left` or `right`, then
     * it'll return the enabled menu on that side. Otherwise, if a `menuId` is
     * provided, then it'll try to find the menu using the menu's `id`
     * property. If a menu is not found then it'll return `null`.
     * @param [menuId]  Optionally get the menu by its id, or side.
     * @return Returns the instance of the menu if found, otherwise `null`.
     */
    get(menuId) {
        return this.menuController.get(menuId);
    }
    /**
     * @return Returns the instance of the menu already opened, otherwise `null`.
     */
    getOpen() {
        return this.menuController.getOpen();
    }
    /**
     * @return Returns an array of all menu instances.
     */
    getMenus() {
        return this.menuController.getMenus();
    }
    registerAnimation(name, animation) {
        return this.menuController.registerAnimation(name, animation);
    }
    isAnimating() {
        return this.menuController.isAnimating();
    }
    _getOpenSync() {
        return this.menuController._getOpenSync();
    }
    _createAnimation(type, menuCmp) {
        return this.menuController._createAnimation(type, menuCmp);
    }
    _register(menu) {
        return this.menuController._register(menu);
    }
    _unregister(menu) {
        return this.menuController._unregister(menu);
    }
    _setOpen(menu, shouldOpen, animated) {
        return this.menuController._setOpen(menu, shouldOpen, animated);
    }
}

class Platform {
    doc;
    _readyPromise;
    win;
    /**
     * @hidden
     */
    backButton = new Subject();
    /**
     * The keyboardDidShow event emits when the
     * on-screen keyboard is presented.
     */
    keyboardDidShow = new Subject();
    /**
     * The keyboardDidHide event emits when the
     * on-screen keyboard is hidden.
     */
    keyboardDidHide = new Subject();
    /**
     * The pause event emits when the native platform puts the application
     * into the background, typically when the user switches to a different
     * application. This event would emit when a Cordova app is put into
     * the background, however, it would not fire on a standard web browser.
     */
    pause = new Subject();
    /**
     * The resume event emits when the native platform pulls the application
     * out from the background. This event would emit when a Cordova app comes
     * out from the background, however, it would not fire on a standard web browser.
     */
    resume = new Subject();
    /**
     * The resize event emits when the browser window has changed dimensions. This
     * could be from a browser window being physically resized, or from a device
     * changing orientation.
     */
    resize = new Subject();
    constructor(doc, zone) {
        this.doc = doc;
        zone.run(() => {
            this.win = doc.defaultView;
            this.backButton.subscribeWithPriority = function (priority, callback) {
                return this.subscribe((ev) => {
                    return ev.register(priority, (processNextHandler) => zone.run(() => callback(processNextHandler)));
                });
            };
            proxyEvent(this.pause, doc, 'pause', zone);
            proxyEvent(this.resume, doc, 'resume', zone);
            proxyEvent(this.backButton, doc, 'ionBackButton', zone);
            proxyEvent(this.resize, this.win, 'resize', zone);
            proxyEvent(this.keyboardDidShow, this.win, 'ionKeyboardDidShow', zone);
            proxyEvent(this.keyboardDidHide, this.win, 'ionKeyboardDidHide', zone);
            let readyResolve;
            this._readyPromise = new Promise((res) => {
                readyResolve = res;
            });
            if (this.win?.['cordova']) {
                doc.addEventListener('deviceready', () => {
                    readyResolve('cordova');
                }, { once: true });
            }
            else {
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                readyResolve('dom');
            }
        });
    }
    /**
     * @returns returns true/false based on platform.
     * @description
     * Depending on the platform the user is on, `is(platformName)` will
     * return `true` or `false`. Note that the same app can return `true`
     * for more than one platform name. For example, an app running from
     * an iPad would return `true` for the platform names: `mobile`,
     * `ios`, `ipad`, and `tablet`. Additionally, if the app was running
     * from Cordova then `cordova` would be true, and if it was running
     * from a web browser on the iPad then `mobileweb` would be `true`.
     *
     * ```
     * import { Platform } from 'ionic-angular';
     *
     * @Component({...})
     * export MyPage {
     *   constructor(public platform: Platform) {
     *     if (this.platform.is('ios')) {
     *       // This will only print when on iOS
     *       console.log('I am an iOS device!');
     *     }
     *   }
     * }
     * ```
     *
     * | Platform Name   | Description                        |
     * |-----------------|------------------------------------|
     * | android         | on a device running Android.       |
     * | capacitor       | on a device running Capacitor.     |
     * | cordova         | on a device running Cordova.       |
     * | ios             | on a device running iOS.           |
     * | ipad            | on an iPad device.                 |
     * | iphone          | on an iPhone device.               |
     * | phablet         | on a phablet device.               |
     * | tablet          | on a tablet device.                |
     * | electron        | in Electron on a desktop device.   |
     * | pwa             | as a PWA app.                      |
     * | mobile          | on a mobile device.                |
     * | mobileweb       | on a mobile device in a browser.   |
     * | desktop         | on a desktop device.               |
     * | hybrid          | is a cordova or capacitor app.     |
     *
     */
    is(platformName) {
        return isPlatform(this.win, platformName);
    }
    /**
     * @returns the array of platforms
     * @description
     * Depending on what device you are on, `platforms` can return multiple values.
     * Each possible value is a hierarchy of platforms. For example, on an iPhone,
     * it would return `mobile`, `ios`, and `iphone`.
     *
     * ```
     * import { Platform } from 'ionic-angular';
     *
     * @Component({...})
     * export MyPage {
     *   constructor(public platform: Platform) {
     *     // This will print an array of the current platforms
     *     console.log(this.platform.platforms());
     *   }
     * }
     * ```
     */
    platforms() {
        return getPlatforms(this.win);
    }
    /**
     * Returns a promise when the platform is ready and native functionality
     * can be called. If the app is running from within a web browser, then
     * the promise will resolve when the DOM is ready. When the app is running
     * from an application engine such as Cordova, then the promise will
     * resolve when Cordova triggers the `deviceready` event.
     *
     * The resolved value is the `readySource`, which states which platform
     * ready was used. For example, when Cordova is ready, the resolved ready
     * source is `cordova`. The default ready source value will be `dom`. The
     * `readySource` is useful if different logic should run depending on the
     * platform the app is running from. For example, only Cordova can execute
     * the status bar plugin, so the web should not run status bar plugin logic.
     *
     * ```
     * import { Component } from '@angular/core';
     * import { Platform } from 'ionic-angular';
     *
     * @Component({...})
     * export MyApp {
     *   constructor(public platform: Platform) {
     *     this.platform.ready().then((readySource) => {
     *       console.log('Platform ready from', readySource);
     *       // Platform now ready, execute any required native code
     *     });
     *   }
     * }
     * ```
     */
    ready() {
        return this._readyPromise;
    }
    /**
     * Returns if this app is using right-to-left language direction or not.
     * We recommend the app's `index.html` file already has the correct `dir`
     * attribute value set, such as `<html dir="ltr">` or `<html dir="rtl">`.
     * [W3C: Structural markup and right-to-left text in HTML](http://www.w3.org/International/questions/qa-html-dir)
     */
    get isRTL() {
        return this.doc.dir === 'rtl';
    }
    /**
     * Get the query string parameter
     */
    getQueryParam(key) {
        return readQueryParam(this.win.location.href, key);
    }
    /**
     * Returns `true` if the app is in landscape mode.
     */
    isLandscape() {
        return !this.isPortrait();
    }
    /**
     * Returns `true` if the app is in portrait mode.
     */
    isPortrait() {
        return this.win.matchMedia?.('(orientation: portrait)').matches;
    }
    testUserAgent(expression) {
        const nav = this.win.navigator;
        return !!(nav?.userAgent && nav.userAgent.indexOf(expression) >= 0);
    }
    /**
     * Get the current url.
     */
    url() {
        return this.win.location.href;
    }
    /**
     * Gets the width of the platform's viewport using `window.innerWidth`.
     */
    width() {
        return this.win.innerWidth;
    }
    /**
     * Gets the height of the platform's viewport using `window.innerHeight`.
     */
    height() {
        return this.win.innerHeight;
    }
    /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: Platform, deps: [{ token: DOCUMENT }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
    /** @nocollapse */ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: Platform, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: Platform, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root',
                }]
        }], ctorParameters: function () { return [{ type: undefined, decorators: [{
                    type: Inject,
                    args: [DOCUMENT]
                }] }, { type: i0.NgZone }]; } });
const readQueryParam = (url, key) => {
    key = key.replace(/[[\]\\]/g, '\\$&');
    const regex = new RegExp('[\\?&]' + key + '=([^&#]*)');
    const results = regex.exec(url);
    return results ? decodeURIComponent(results[1].replace(/\+/g, ' ')) : null;
};
const proxyEvent = (emitter, el, eventName, zone) => {
    if (el) {
        el.addEventListener(eventName, (ev) => {
            /**
             * `zone.run` is required to make sure that we are running inside the Angular zone
             * at all times. This is necessary since an app that has Capacitor will
             * override the `document.addEventListener` with its own implementation.
             * The override causes the event to no longer be in the Angular zone.
             */
            zone.run(() => {
                // ?? cordova might emit "null" events
                const value = ev != null ? ev.detail : undefined;
                emitter.next(value);
            });
        });
    }
};

class NavController {
    location;
    serializer;
    router;
    topOutlet;
    direction = DEFAULT_DIRECTION;
    animated = DEFAULT_ANIMATED;
    animationBuilder;
    guessDirection = 'forward';
    guessAnimation;
    lastNavId = -1;
    constructor(platform, location, serializer, router) {
        this.location = location;
        this.serializer = serializer;
        this.router = router;
        // Subscribe to router events to detect direction
        if (router) {
            router.events.subscribe((ev) => {
                if (ev instanceof NavigationStart) {
                    // restoredState is set if the browser back/forward button is used
                    const id = ev.restoredState ? ev.restoredState.navigationId : ev.id;
                    this.guessDirection = this.guessAnimation = id < this.lastNavId ? 'back' : 'forward';
                    this.lastNavId = this.guessDirection === 'forward' ? ev.id : id;
                }
                // Reset explicit direction when navigation is canceled (e.g., guard rejection)
                // to prevent stale direction from leaking into the next navigation
                if (ev instanceof NavigationCancel || ev instanceof NavigationError) {
                    this.direction = DEFAULT_DIRECTION;
                    this.animated = DEFAULT_ANIMATED;
                    this.animationBuilder = undefined;
                }
            });
        }
        // Subscribe to backButton events
        platform.backButton.subscribeWithPriority(0, (processNextHandler) => {
            this.pop();
            processNextHandler();
        });
    }
    /**
     * This method uses Angular's [Router](https://angular.io/api/router/Router) under the hood,
     * it's equivalent to calling `this.router.navigateByUrl()`, but it's explicit about the **direction** of the transition.
     *
     * Going **forward** means that a new page is going to be pushed to the stack of the outlet (ion-router-outlet),
     * and that it will show a "forward" animation by default.
     *
     * Navigating forward can also be triggered in a declarative manner by using the `[routerDirection]` directive:
     *
     * ```html
     * <a routerLink="/path/to/page" routerDirection="forward">Link</a>
     * ```
     */
    navigateForward(url, options = {}) {
        this.setDirection('forward', options.animated, options.animationDirection, options.animation);
        return this.navigate(url, options);
    }
    /**
     * This method uses Angular's [Router](https://angular.io/api/router/Router) under the hood,
     * it's equivalent to calling:
     *
     * ```ts
     * this.navController.setDirection('back');
     * this.router.navigateByUrl(path);
     * ```
     *
     * Going **back** means that all the pages in the stack until the navigated page is found will be popped,
     * and that it will show a "back" animation by default.
     *
     * Navigating back can also be triggered in a declarative manner by using the `[routerDirection]` directive:
     *
     * ```html
     * <a routerLink="/path/to/page" routerDirection="back">Link</a>
     * ```
     */
    navigateBack(url, options = {}) {
        this.setDirection('back', options.animated, options.animationDirection, options.animation);
        return this.navigate(url, options);
    }
    /**
     * This method uses Angular's [Router](https://angular.io/api/router/Router) under the hood,
     * it's equivalent to calling:
     *
     * ```ts
     * this.navController.setDirection('root');
     * this.router.navigateByUrl(path);
     * ```
     *
     * Going **root** means that all existing pages in the stack will be removed,
     * and the navigated page will become the single page in the stack.
     *
     * Navigating root can also be triggered in a declarative manner by using the `[routerDirection]` directive:
     *
     * ```html
     * <a routerLink="/path/to/page" routerDirection="root">Link</a>
     * ```
     */
    navigateRoot(url, options = {}) {
        this.setDirection('root', options.animated, options.animationDirection, options.animation);
        return this.navigate(url, options);
    }
    /**
     * Same as [Location](https://angular.io/api/common/Location)'s back() method.
     * It will use the standard `window.history.back()` under the hood, but featuring a `back` animation
     * by default.
     */
    back(options = { animated: true, animationDirection: 'back' }) {
        this.setDirection('back', options.animated, options.animationDirection, options.animation);
        return this.location.back();
    }
    /**
     * This methods goes back in the context of Ionic's stack navigation.
     *
     * It recursively finds the top active `ion-router-outlet` and calls `pop()`.
     * This is the recommended way to go back when you are using `ion-router-outlet`.
     *
     * Resolves to `true` if it was able to pop.
     */
    async pop() {
        let outlet = this.topOutlet;
        while (outlet) {
            if (await outlet.pop()) {
                return true;
            }
            else {
                outlet = outlet.parentOutlet;
            }
        }
        return false;
    }
    /**
     * This methods specifies the direction of the next navigation performed by the Angular router.
     *
     * `setDirection()` does not trigger any transition, it just sets some flags to be consumed by `ion-router-outlet`.
     *
     * It's recommended to use `navigateForward()`, `navigateBack()` and `navigateRoot()` instead of `setDirection()`.
     */
    setDirection(direction, animated, animationDirection, animationBuilder) {
        this.direction = direction;
        this.animated = getAnimation(direction, animated, animationDirection);
        this.animationBuilder = animationBuilder;
    }
    /**
     * @internal
     */
    setTopOutlet(outlet) {
        this.topOutlet = outlet;
    }
    /**
     * @internal
     */
    consumeTransition() {
        let direction = 'root';
        let animation;
        const animationBuilder = this.animationBuilder;
        if (this.direction === 'auto') {
            direction = this.guessDirection;
            animation = this.guessAnimation;
        }
        else {
            animation = this.animated;
            direction = this.direction;
        }
        this.direction = DEFAULT_DIRECTION;
        this.animated = DEFAULT_ANIMATED;
        this.animationBuilder = undefined;
        return {
            direction,
            animation,
            animationBuilder,
        };
    }
    navigate(url, options) {
        if (Array.isArray(url)) {
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            return this.router.navigate(url, options);
        }
        else {
            /**
             * navigateByUrl ignores any properties that
             * would change the url, so things like queryParams
             * would be ignored unless we create a url tree
             * More Info: https://github.com/angular/angular/issues/18798
             */
            const urlTree = this.serializer.parse(url.toString());
            if (options.queryParams !== undefined) {
                urlTree.queryParams = { ...options.queryParams };
            }
            if (options.fragment !== undefined) {
                urlTree.fragment = options.fragment;
            }
            /**
             * `navigateByUrl` will still apply `NavigationExtras` properties
             * that do not modify the url, such as `replaceUrl` which is why
             * `options` is passed in here.
             */
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            return this.router.navigateByUrl(urlTree, options);
        }
    }
    /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NavController, deps: [{ token: Platform }, { token: i1.Location }, { token: i3.UrlSerializer }, { token: i3.Router, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
    /** @nocollapse */ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NavController, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NavController, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root',
                }]
        }], ctorParameters: function () { return [{ type: Platform }, { type: i1.Location }, { type: i3.UrlSerializer }, { type: i3.Router, decorators: [{
                    type: Optional
                }] }]; } });
const getAnimation = (direction, animated, animationDirection) => {
    if (animated === false) {
        return undefined;
    }
    if (animationDirection !== undefined) {
        return animationDirection;
    }
    if (direction === 'forward' || direction === 'back') {
        return direction;
    }
    else if (direction === 'root' && animated === true) {
        return 'forward';
    }
    return undefined;
};
const DEFAULT_DIRECTION = 'auto';
const DEFAULT_ANIMATED = undefined;

class Config {
    get(key, fallback) {
        const c = getConfig();
        if (c) {
            return c.get(key, fallback);
        }
        return null;
    }
    getBoolean(key, fallback) {
        const c = getConfig();
        if (c) {
            return c.getBoolean(key, fallback);
        }
        return false;
    }
    getNumber(key, fallback) {
        const c = getConfig();
        if (c) {
            return c.getNumber(key, fallback);
        }
        return 0;
    }
    /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: Config, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
    /** @nocollapse */ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: Config, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: Config, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root',
                }]
        }] });
const ConfigToken = new InjectionToken('USERCONFIG');
const getConfig = () => {
    if (typeof window !== 'undefined') {
        const Ionic = window.Ionic;
        if (Ionic?.config) {
            return Ionic.config;
        }
    }
    return null;
};

/**
 * @description
 * NavParams are an object that exists on a page and can contain data for that particular view.
 * Similar to how data was pass to a view in V1 with `$stateParams`, NavParams offer a much more flexible
 * option with a simple `get` method.
 *
 * @usage
 * ```ts
 * import { NavParams } from '@ionic/angular';
 *
 * export class MyClass{
 *
 *  constructor(navParams: NavParams){
 *    // userParams is an object we have in our nav-parameters
 *    navParams.get('userParams');
 *  }
 *
 * }
 * ```
 */
class NavParams {
    data;
    constructor(data = {}) {
        this.data = data;
        console.warn(`[Ionic Warning]: NavParams has been deprecated in favor of using Angular's input API. Developers should migrate to either the @Input decorator or the Signals-based input API.`);
    }
    /**
     * Get the value of a nav-parameter for the current view
     *
     * ```ts
     * import { NavParams } from 'ionic-angular';
     *
     * export class MyClass{
     *  constructor(public navParams: NavParams){
     *    // userParams is an object we have in our nav-parameters
     *    this.navParams.get('userParams');
     *  }
     * }
     * ```
     *
     * @param param Which param you want to look up
     */
    get(param) {
        return this.data[param];
    }
}

// Token for injecting the modal element
const IonModalToken = new InjectionToken('IonModalToken');
// TODO(FW-2827): types
class AngularDelegate {
    zone = inject(NgZone);
    applicationRef = inject(ApplicationRef);
    config = inject(ConfigToken);
    create(environmentInjector, injector, elementReferenceKey, customInjector) {
        return new AngularFrameworkDelegate(environmentInjector, injector, this.applicationRef, this.zone, elementReferenceKey, this.config.useSetInputAPI ?? false, customInjector);
    }
    /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: AngularDelegate, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
    /** @nocollapse */ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: AngularDelegate });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: AngularDelegate, decorators: [{
            type: Injectable
        }] });
class AngularFrameworkDelegate {
    environmentInjector;
    injector;
    applicationRef;
    zone;
    elementReferenceKey;
    enableSignalsSupport;
    customInjector;
    elRefMap = new WeakMap();
    elEventsMap = new WeakMap();
    constructor(environmentInjector, injector, applicationRef, zone, elementReferenceKey, enableSignalsSupport, customInjector) {
        this.environmentInjector = environmentInjector;
        this.injector = injector;
        this.applicationRef = applicationRef;
        this.zone = zone;
        this.elementReferenceKey = elementReferenceKey;
        this.enableSignalsSupport = enableSignalsSupport;
        this.customInjector = customInjector;
    }
    attachViewToDom(container, component, params, cssClasses) {
        return this.zone.run(() => {
            return new Promise((resolve) => {
                const componentProps = {
                    ...params,
                };
                /**
                 * Ionic Angular passes a reference to a modal
                 * or popover that can be accessed using a
                 * variable in the overlay component. If
                 * elementReferenceKey is defined, then we should
                 * pass a reference to the component using
                 * elementReferenceKey as the key.
                 */
                if (this.elementReferenceKey !== undefined) {
                    componentProps[this.elementReferenceKey] = container;
                }
                const el = attachView(this.zone, this.environmentInjector, this.injector, this.applicationRef, this.elRefMap, this.elEventsMap, container, component, componentProps, cssClasses, this.elementReferenceKey, this.enableSignalsSupport, this.customInjector);
                resolve(el);
            });
        });
    }
    removeViewFromDom(_container, component) {
        return this.zone.run(() => {
            return new Promise((resolve) => {
                const componentRef = this.elRefMap.get(component);
                if (componentRef) {
                    componentRef.destroy();
                    this.elRefMap.delete(component);
                    const unbindEvents = this.elEventsMap.get(component);
                    if (unbindEvents) {
                        unbindEvents();
                        this.elEventsMap.delete(component);
                    }
                }
                resolve();
            });
        });
    }
}
const attachView = (zone, environmentInjector, injector, applicationRef, elRefMap, elEventsMap, container, component, params, cssClasses, elementReferenceKey, enableSignalsSupport, customInjector) => {
    /**
     * Wraps the injector with a custom injector that
     * provides NavParams to the component.
     *
     * NavParams is a legacy feature from Ionic v3 that allows
     * Angular developers to provide data to a component
     * and access it by providing NavParams as a dependency
     * in the constructor.
     *
     * The modern approach is to access the data directly
     * from the component's class instance.
     */
    const providers = getProviders(params);
    // If this is an ion-modal, provide the modal element as an injectable
    // so components inside the modal can inject it directly
    if (container.tagName.toLowerCase() === 'ion-modal') {
        providers.push({
            provide: IonModalToken,
            useValue: container,
        });
    }
    const childInjector = Injector.create({
        providers,
        parent: customInjector ?? injector,
    });
    const componentRef = createComponent(component, {
        environmentInjector,
        elementInjector: childInjector,
    });
    const instance = componentRef.instance;
    const hostElement = componentRef.location.nativeElement;
    if (params) {
        /**
         * For modals and popovers, a reference to the component is
         * added to `params` during the call to attachViewToDom. If
         * a reference using this name is already set, this means
         * the app is trying to use the name as a component prop,
         * which will cause collisions.
         */
        if (elementReferenceKey && instance[elementReferenceKey] !== undefined) {
            console.error(`[Ionic Error]: ${elementReferenceKey} is a reserved property when using ${container.tagName.toLowerCase()}. Rename or remove the "${elementReferenceKey}" property from ${component.name}.`);
        }
        /**
         * Angular 14.1 added support for setInput
         * so we need to fall back to Object.assign
         * for Angular 14.0.
         */
        if (enableSignalsSupport === true && componentRef.setInput !== undefined) {
            const { modal, popover, ...otherParams } = params;
            /**
             * Any key/value pairs set in componentProps
             * must be set as inputs on the component instance.
             */
            for (const key in otherParams) {
                componentRef.setInput(key, otherParams[key]);
            }
            /**
             * Using setInput will cause an error when
             * setting modal/popover on a component that
             * does not define them as an input. For backwards
             * compatibility purposes we fall back to using
             * Object.assign for these properties.
             */
            if (modal !== undefined) {
                Object.assign(instance, { modal });
            }
            if (popover !== undefined) {
                Object.assign(instance, { popover });
            }
        }
        else {
            Object.assign(instance, params);
        }
    }
    if (cssClasses) {
        for (const cssClass of cssClasses) {
            hostElement.classList.add(cssClass);
        }
    }
    const unbindEvents = bindLifecycleEvents(zone, instance, hostElement);
    container.appendChild(hostElement);
    applicationRef.attachView(componentRef.hostView);
    /**
     * Run change detection so template bindings (e.g. `<ion-nav [root]="rootPage">`)
     * apply during this synchronous pass, before the web component runs its load
     * lifecycle. `createComponent` only runs the creation pass, so without this the
     * binding lands after the element has loaded, too late for `ion-nav` to read it.
     */
    componentRef.changeDetectorRef.detectChanges();
    elRefMap.set(hostElement, componentRef);
    elEventsMap.set(hostElement, unbindEvents);
    return hostElement;
};
const LIFECYCLES = [
    LIFECYCLE_WILL_ENTER,
    LIFECYCLE_DID_ENTER,
    LIFECYCLE_WILL_LEAVE,
    LIFECYCLE_DID_LEAVE,
    LIFECYCLE_WILL_UNLOAD,
];
const bindLifecycleEvents = (zone, instance, element) => {
    return zone.run(() => {
        const unregisters = LIFECYCLES.filter((eventName) => typeof instance[eventName] === 'function').map((eventName) => {
            const handler = (ev) => instance[eventName](ev.detail);
            element.addEventListener(eventName, handler);
            return () => element.removeEventListener(eventName, handler);
        });
        return () => unregisters.forEach((fn) => fn());
    });
};
const NavParamsToken = new InjectionToken('NavParamsToken');
const getProviders = (params) => {
    return [
        {
            provide: NavParamsToken,
            useValue: params,
        },
        {
            provide: NavParams,
            useFactory: provideNavParamsInjectable,
            deps: [NavParamsToken],
        },
    ];
};
const provideNavParamsInjectable = (params) => {
    return new NavParams(params);
};

// TODO: Is there a way we can grab this from angular-component-lib instead?
/* eslint-disable */
/* tslint:disable */
const proxyInputs = (Cmp, inputs) => {
    const Prototype = Cmp.prototype;
    inputs.forEach((item) => {
        Object.defineProperty(Prototype, item, {
            get() {
                return this.el[item];
            },
            set(val) {
                this.z.runOutsideAngular(() => (this.el[item] = val));
            },
        });
    });
};
const proxyMethods = (Cmp, methods) => {
    const Prototype = Cmp.prototype;
    methods.forEach((methodName) => {
        Prototype[methodName] = function () {
            const args = arguments;
            return this.z.runOutsideAngular(() => this.el[methodName].apply(this.el, args));
        };
    });
};
const proxyOutputs = (instance, el, events) => {
    events.forEach((eventName) => (instance[eventName] = fromEvent(el, eventName)));
};
// tslint:disable-next-line: only-arrow-functions
function ProxyCmp(opts) {
    const decorator = function (cls) {
        const { defineCustomElementFn, inputs, methods } = opts;
        if (defineCustomElementFn !== undefined) {
            defineCustomElementFn();
        }
        if (inputs) {
            proxyInputs(cls, inputs);
        }
        if (methods) {
            proxyMethods(cls, methods);
        }
        return cls;
    };
    return decorator;
}

const MODAL_INPUTS = [
    'animated',
    'keepContentsMounted',
    'backdropBreakpoint',
    'backdropDismiss',
    'breakpoints',
    'canDismiss',
    'cssClass',
    'enterAnimation',
    'expandToScroll',
    'event',
    'focusTrap',
    'handle',
    'handleBehavior',
    'initialBreakpoint',
    'isOpen',
    'keyboardClose',
    'leaveAnimation',
    'mode',
    'presentingElement',
    'showBackdrop',
    'translucent',
    'trigger',
];
const MODAL_METHODS = [
    'present',
    'dismiss',
    'onDidDismiss',
    'onWillDismiss',
    'setCurrentBreakpoint',
    'getCurrentBreakpoint',
];
let IonModal = 
/**
 * @Component extends from @Directive
 * so by defining the inputs here we
 * do not need to re-define them for the
 * lazy loaded popover.
 */
class IonModal {
    z;
    // TODO(FW-2827): type
    template;
    isCmpOpen = false;
    el;
    constructor(c, r, z) {
        this.z = z;
        this.el = r.nativeElement;
        this.el.addEventListener('ionMount', () => {
            this.isCmpOpen = true;
            c.detectChanges();
        });
        this.el.addEventListener('didDismiss', () => {
            this.isCmpOpen = false;
            c.detectChanges();
        });
        proxyOutputs(this, this.el, [
            'ionModalDidPresent',
            'ionModalWillPresent',
            'ionModalWillDismiss',
            'ionModalDidDismiss',
            'ionBreakpointDidChange',
            'didPresent',
            'willPresent',
            'willDismiss',
            'didDismiss',
            'ionDragStart',
            'ionDragMove',
            'ionDragEnd',
        ]);
    }
    /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: IonModal, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ElementRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Directive });
    /** @nocollapse */ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: IonModal, selector: "ion-modal", inputs: { animated: "animated", keepContentsMounted: "keepContentsMounted", backdropBreakpoint: "backdropBreakpoint", backdropDismiss: "backdropDismiss", breakpoints: "breakpoints", canDismiss: "canDismiss", cssClass: "cssClass", enterAnimation: "enterAnimation", expandToScroll: "expandToScroll", event: "event", focusTrap: "focusTrap", 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 });
};
IonModal = __decorate([
    ProxyCmp({
        inputs: MODAL_INPUTS,
        methods: MODAL_METHODS,
    })
    /**
     * @Component extends from @Directive
     * so by defining the inputs here we
     * do not need to re-define them for the
     * lazy loaded popover.
     */
], IonModal);
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: IonModal, decorators: [{
            type: Directive,
            args: [{
                    selector: 'ion-modal',
                    // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
                    inputs: MODAL_INPUTS,
                }]
        }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: i0.NgZone }]; }, propDecorators: { template: [{
                type: ContentChild,
                args: [TemplateRef, { static: false }]
            }] } });

const POPOVER_INPUTS = [
    'alignment',
    'animated',
    'arrow',
    'keepContentsMounted',
    'backdropDismiss',
    'cssClass',
    'dismissOnSelect',
    'enterAnimation',
    'event',
    'focusTrap',
    'isOpen',
    'keyboardClose',
    'leaveAnimation',
    'mode',
    'showBackdrop',
    'translucent',
    'trigger',
    'triggerAction',
    'reference',
    'size',
    'side',
];
const POPOVER_METHODS = ['present', 'dismiss', 'onDidDismiss', 'onWillDismiss'];
let IonPopover = 
/**
 * @Component extends from @Directive
 * so by defining the inputs here we
 * do not need to re-define them for the
 * lazy loaded popover.
 */
class IonPopover {
    z;
    // TODO(FW-2827): type
    template;
    isCmpOpen = false;
    el;
    constructor(c, r, z) {
        this.z = z;
        this.el = r.nativeElement;
        this.el.addEventListener('ionMount', () => {
            this.isCmpOpen = true;
            c.detectChanges();
        });
        this.el.addEventListener('didDismiss', () => {
            this.isCmpOpen = false;
            c.detectChanges();
        });
        proxyOutputs(this, this.el, [
            'ionPopoverDidPresent',
            'ionPopoverWillPresent',
            'ionPopoverWillDismiss',
            'ionPopoverDidDismiss',
            'didPresent',
            'willPresent',
            'willDismiss',
            'didDismiss',
        ]);
    }
    /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: IonPopover, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ElementRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Directive });
    /** @nocollapse */ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.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", focusTrap: "focusTrap", 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 });
};
IonPopover = __decorate([
    ProxyCmp({
        inputs: POPOVER_INPUTS,
        methods: POPOVER_METHODS,
    })
    /**
     * @Component extends from @Directive
     * so by defining the inputs here we
     * do not need to re-define them for the
     * lazy loaded popover.
     */
], IonPopover);
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: IonPopover, decorators: [{
            type: Directive,
            args: [{
                    selector: 'ion-popover',
                    // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
                    inputs: POPOVER_INPUTS,
                }]
        }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: i0.NgZone }]; }, propDecorators: { template: [{
                type: ContentChild,
                args: [TemplateRef, { static: false }]
            }] } });

const insertView = (views, view, direction) => {
    if (direction === 'root') {
        return setRoot(views, view);
    }
    else if (direction === 'forward') {
        return setForward(views, view);
    }
    else {
        return setBack(views, view);
    }
};
const setRoot = (views, view) => {
    views = views.filter((v) => v.stackId !== view.stackId);
    views.push(view);
    return views;
};
const setForward = (views, view) => {
    const index = views.indexOf(view);
    if (index >= 0) {
        views = views.filter((v) => v.stackId !== view.stackId || v.id <= view.id);
    }
    else {
        views.push(view);
    }
    return views;
};
const setBack = (views, view) => {
    const index = views.indexOf(view);
    if (index >= 0) {
        return views.filter((v) => v.stackId !== view.stackId || v.id <= view.id);
    }
    else {
        return setRoot(views, view);
    }
};
const getUrl = (router, activatedRoute) => {
    const urlTree = router.createUrlTree(['.'], { relativeTo: activatedRoute });
    return router.serializeUrl(urlTree);
};
const isTabSwitch = (enteringView, leavingView) => {
    if (!leavingView) {
        return true;
    }
    return enteringView.stackId !== leavingView.stackId;
};
const computeStackId = (prefixUrl, url) => {
    if (!prefixUrl) {
        return undefined;
    }
    const segments = toSegments(url);
    for (let i = 0; i < segments.length; i++) {
        if (i >= prefixUrl.length) {
            return segments[i];
        }
        if (segments[i] !== prefixUrl[i]) {
            return undefined;
        }
    }
    return undefined;
};
const toSegments = (path) => {
    return path
        .split('/')
        .map((s) => s.trim())
        .filter((s) => s !== '');
};
const destroyView = (view) => {
    if (view) {
        view.ref.destroy();
        view.unlistenEvents();
    }
};

// TODO(FW-2827): types
class StackController {
    containerEl;
    router;
    navCtrl;
    zone;
    location;
    views = [];
    runningTask;
    skipTransition = false;
    tabsPrefix;
    activeView;
    nextId = 0;
    constructor(tabsPrefix, containerEl, router, navCtrl, zone, location) {
        this.containerEl = containerEl;
        this.router = router;
        this.navCtrl = navCtrl;
        this.zone = zone;
        this.location = location;
        this.tabsPrefix = tabsPrefix !== undefined ? toSegments(tabsPrefix) : undefined;
    }
    createView(ref, activatedRoute) {
        const url = getUrl(this.router, activatedRoute);
        const element = ref?.location?.nativeElement;
        const unlistenEvents = bindLifecycleEvents(this.zone, ref.instance, element);
        return {
            id: this.nextId++,
            stackId: computeStackId(this.tabsPrefix, url),
            unlistenEvents,
            element,
            ref,
            url,
        };
    }
    getExistingView(activatedRoute) {
        const activatedUrlKey = getUrl(this.router, activatedRoute);
        const view = this.views.find((vw) => vw.url === activatedUrlKey);
        if (view) {
            view.ref.changeDetectorRef.reattach();
        }
        return view;
    }
    setActive(enteringView) {
        const consumeResult = this.navCtrl.consumeTransition();
        let { direction, animation, animationBuilder } = consumeResult;
        const leavingView = this.activeView;
        const tabSwitch = isTabSwitch(enteringView, leavingView);
        if (tabSwitch) {
            direction = 'back';
            animation = undefined;
        }
        const viewsSnapshot = this.views.slice();
        let currentNavigation;
        const router = this.router;
        // Angular >= 7.2.0
        if (router.getCurrentNavigation) {
            currentNavigation = router.getCurrentNavigation();
            // Angular < 7.2.0
        }
        else if (router.navigations?.value) {
            currentNavigation = router.navigations.value;
        }
        /**
         * If the navigation action
         * sets `replaceUrl: true`
         * then we need to make sure
         * we remove the last item
         * from our views stack
         */
        if (currentNavigation?.extras?.replaceUrl) {
            if (this.views.length > 0) {
                this.views.splice(-1, 1);
            }
        }
        const reused = this.views.includes(enteringView);
        const views = this.insertView(enteringView, direction);
        // Trigger change detection before transition starts
        // This will call ngOnInit() the first time too, just after the view
        // was attached to the dom, but BEFORE the transition starts
        if (!reused) {
            enteringView.ref.changeDetectorRef.detectChanges();
        }
        /**
         * If we are going back from a page that
         * was presented using a custom animation
         * we should default to using that
         * unless the developer explicitly
         * provided another animation.
         */
        const customAnimation = enteringView.animationBuilder;
        if (animationBuilder === undefined && direction === 'back' && !tabSwitch && customAnimation !== undefined) {
            animationBuilder = customAnimation;
        }
        /**
         * Save any custom animation so that navigating
         * back will use this custom animation by default.
         */
        if (leavingView) {
            leavingView.animationBuilder = animationBuilder;
        }
        // Wait until previous transitions finish
        return this.zone.runOutsideAngular(() => {
            return this.wait(() => {
                // disconnect leaving page from change detection to
                // reduce jank during the page transition
                if (leavingView) {
                    leavingView.ref.changeDetectorRef.detach();
                }
                // In case the enteringView is the same as the leavingPage we need to reattach()
                enteringView.ref.changeDetectorRef.reattach();
                return this.transition(enteringView, leavingView, animation, this.canGoBack(1), false, animationBuilder)
                    .then(() => cleanupAsync(enteringView, views, viewsSnapshot, this.location, this.zone))
                    .then(() => ({
                    enteringView,
                    direction,
                    animation,
                    tabSwitch,
                }));
            });
        });
    }
    canGoBack(deep, stackId = this.getActiveStackId()) {
        return this.getStack(stackId).length > deep;
    }
    pop(deep, stackId = this.getActiveStackId()) {
        return this.zone.run(() => {
            const views = this.getStack(stackId);
            if (views.length <= deep) {
                return Promise.resolve(false);
            }
            const view = views[views.length - deep - 1];
            let url = view.url;
            const viewSavedData = view.savedData;
            if (viewSavedData) {
                const primaryOutlet = viewSavedData.get('primary');
                if (primaryOutlet?.route?._routerState?.snapshot.url) {
                    url = primaryOutlet.route._routerState.snapshot.url;
                }
            }
            const { animationBuilder } = this.navCtrl.consumeTransition();
            return this.navCtrl.navigateBack(url, { ...view.savedExtras, animation: animationBuilder }).then(() => true);
        });
    }
    startBackTransition() {
        const leavingView = this.activeView;
        if (leavingView) {
            const views = this.getStack(leavingView.stackId);
            const enteringView = views[views.length - 2];
            const customAnimation = enteringView.animationBuilder;
            return this.wait(() => {
                return this.transition(enteringView, // entering view
                leavingView, // leaving view
                'back', this.canGoBack(2), true, customAnimation);
            });
        }
        return Promise.resolve();
    }
    endBackTransition(shouldComplete) {
        if (shouldComplete) {
            this.skipTransition = true;
            this.pop(1);
        }
        else if (this.activeView) {
            cleanup(this.activeView, this.views, this.views, this.location, this.zone);
        }
    }
    getLastUrl(stackId) {
        const views = this.getStack(stackId);
        return views.length > 0 ? views[views.length - 1] : undefined;
    }
    /**
     * @internal
     */
    getRootUrl(stackId) {
        const views = this.getStack(stackId);
        return views.length > 0 ? views[0] : undefined;
    }
    getActiveStackId() {
        return this.activeView ? this.activeView.stackId : undefined;
    }
    /**
     * @internal
     */
    getActiveView() {
        return this.activeView;
    }
    hasRunningTask() {
        return this.runningTask !== undefined;
    }
    destroy() {
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        this.containerEl = undefined;
        this.views.forEach(destroyView);
        this.activeView = undefined;
        this.views = [];
    }
    getStack(stackId) {
        return this.views.filter((v) => v.stackId === stackId);
    }
    insertView(enteringView, direction) {
        this.activeView = enteringView;
        this.views = insertView(this.views, enteringView, direction);
        return this.views.slice();
    }
    transition(enteringView, leavingView, direction, showGoBack, progressAnimation, animationBuilder) {
        if (this.skipTransition) {
            this.skipTransition = false;
            return Promise.resolve(false);
        }
        if (leavingView === enteringView) {
            return Promise.resolve(false);
        }
        const enteringEl = enteringView ? enteringView.element : undefined;
        const leavingEl = leavingView ? leavingView.element : undefined;
        const containerEl = this.containerEl;
        if (enteringEl && enteringEl !== leavingEl) {
            enteringEl.classList.add('ion-page');
            enteringEl.classList.add('ion-page-invisible');
            // containerEl is set to undefined when the stack controller is destroyed,
            // so a transition that resolves after its outlet was destroyed must not
            // dereference it (see destroy()).
            if (containerEl?.commit) {
                return containerEl.commit(enteringEl, leavingEl, {
                    duration: direction === undefined ? 0 : undefined,
                    direction,
                    showGoBack,
                    progressAnimation,
                    animationBuilder,
                });
            }
        }
        return Promise.resolve(false);
    }
    async wait(task) {
        if (this.runningTask !== undefined) {
            await this.runningTask;
            this.runningTask = undefined;
        }
        const promise = (this.runningTask = task());
        promise.finally(() => (this.runningTask = undefined));
        return promise;
    }
}
const cleanupAsync = (activeRoute, views, viewsSnapshot, location, zone) => {
    if (typeof requestAnimationFrame === 'function') {
        return new Promise((resolve) => {
            requestAnimationFrame(() => {
                cleanup(activeRoute, views, viewsSnapshot, location, zone);
                resolve();
            });
        });
    }
    return Promise.resolve();
};
const cleanup = (activeRoute, views, viewsSnapshot, location, zone) => {
    /**
     * Re-enter the Angular zone when destroying page components. This will allow
     * lifecycle events (`ngOnDestroy`) to be run inside the Angular zone.
     */
    zone.run(() => viewsSnapshot.filter((view) => !views.includes(view)).forEach(destroyView));
    views.forEach((view) => {
        /**
         * In the event that a user navigated multiple
         * times in rapid succession, we want to make sure
         * we don't pre-emptively detach a view while
         * it is in mid-transition.
         *
         * In this instance we also do not care about query
         * params or fragments as it will be the same view regardless
         */
        const locationWithoutParams = location.path().split('?')[0];
        const locationWithoutFragment = locationWithoutParams.split('#')[0];
        if (view !== activeRoute && view.url !== locationWithoutFragment) {
            const element = view.element;
            element.setAttribute('aria-hidden', 'true');
            element.classList.add('ion-page-hidden');
            view.ref.changeDetectorRef.detach();
        }
    });
};

// TODO(FW-2827): types
class IonRouterOutlet {
    parentOutlet;
    nativeEl;
    activatedView = null;
    tabsPrefix;
    _swipeGesture;
    stackCtrl;
    // Maintain map of activated route proxies for each component instance
    proxyMap = new WeakMap();
    // Keep the latest activated route in a subject for the proxy routes to switch map to
    currentActivatedRoute$ = new BehaviorSubject(null);
    activated = null;
    /** @internal */
    get activatedComponentRef() {
        return this.activated;
    }
    _activatedRoute = null;
    /**
     * The name of the outlet
     */
    name = PRIMARY_OUTLET;
    /** @internal */
    stackWillChange = new EventEmitter();
    /** @internal */
    stackDidChange = new EventEmitter();
    // eslint-disable-next-line @angular-eslint/no-output-rename
    activateEvents = new EventEmitter();
    // eslint-disable-next-line @angular-eslint/no-output-rename
    deactivateEvents = new EventEmitter();
    parentContexts = inject(ChildrenOutletContexts);
    location = inject(ViewContainerRef);
    environmentInjector = inject(EnvironmentInjector);
    inputBinder = inject(INPUT_BINDER, { optional: true });
    /** @nodoc */
    supportsBindingToComponentInputs = true;
    // Ionic providers
    config = inject(Config);
    navCtrl = inject(NavController);
    set animation(animation) {
        this.nativeEl.animation = animation;
    }
    set animated(animated) {
        this.nativeEl.animated = animated;
    }
    set swipeGesture(swipe) {
        this._swipeGesture = swipe;
        this.nativeEl.swipeHandler = swipe
            ? {
                canStart: () => this.stackCtrl.canGoBack(1) && !this.stackCtrl.hasRunningTask(),
                onStart: () => this.stackCtrl.startBackTransition(),
                onEnd: (shouldContinue) => this.stackCtrl.endBackTransition(shouldContinue),
            }
            : undefined;
    }
    constructor(name, tabs, commonLocation, elementRef, router, zone, activatedRoute, parentOutlet) {
        this.parentOutlet = parentOutlet;
        this.nativeEl = elementRef.nativeElement;
        this.name = name || PRIMARY_OUTLET;
        this.tabsPrefix = tabs === 'true' ? getUrl(router, activatedRoute) : undefined;
        this.stackCtrl = new StackController(this.tabsPrefix, this.nativeEl, router, this.navCtrl, zone, commonLocation);
        this.parentContexts.onChildOutletCreated(this.name, this);
    }
    ngOnDestroy() {
        this.stackCtrl.destroy();
        this.inputBinder?.unsubscribeFromRouteData(this);
    }
    getContext() {
        return this.parentContexts.getContext(this.name);
    }
    ngOnInit() {
        this.initializeOutletWithName();
    }
    // Note: Ionic deviates from the Angular Router implementation here
    initializeOutletWithName() {
        if (!this.activated) {
            // If the outlet was not instantiated at the time the route got activated we need to populate
            // the outlet when it is initialized (ie inside a NgIf)
            const context = this.getContext();
            if (context?.route) {
                this.activateWith(context.route, context.injector);
            }
        }
        new Promise((resolve) => componentOnReady(this.nativeEl, resolve)).then(() => {
            if (this._swipeGesture === undefined) {
                this.swipeGesture = this.config.getBoolean('swipeBackEnabled', this.nativeEl.mode === 'ios');
            }
        });
    }
    get isActivated() {
        return !!this.activated;
    }
    get component() {
        if (!this.activated) {
            throw new Error('Outlet is not activated');
        }
        return this.activated.instance;
    }
    get activatedRoute() {
        if (!this.activated) {
            throw new Error('Outlet is not activated');
        }
        return this._activatedRoute;
    }
    get activatedRouteData() {
        if (this._activatedRoute) {
            return this._activatedRoute.snapshot.data;
        }
        return {};
    }
    /**
     * Called when the `RouteReuseStrategy` instructs to detach the subtree
     */
    detach() {
        throw new Error('incompatible reuse strategy');
    }
    /**
     * Called when the `RouteReuseStrategy` instructs to re-attach a previously detached subtree
     */
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    attach(_ref, _activatedRoute) {
        throw new Error('incompatible reuse strategy');
    }
    deactivate() {
        if (this.activated) {
            if (this.activatedView) {
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                const context = this.getContext();
                this.activatedView.savedData = new Map(context.children['contexts']);
                /**
                 * Angular v11.2.10 introduced a change
                 * where this route context is cleared out when
                 * a router-outlet is deactivated, However,
                 * we need this route information in order to
                 * return a user back to the correct tab when
                 * leaving and then going back to the tab context.
                 */
                const primaryOutlet = this.activatedView.savedData.get('primary');
                if (primaryOutlet && context.route) {
                    primaryOutlet.route = { ...context.route };
                }
                /**
                 * Ensure we are saving the NavigationExtras
                 * data otherwise it will be lost
                 */
                this.activatedView.savedExtras = {};
                if (context.route) {
                    const contextSnapshot = context.route.snapshot;
                    this.activatedView.savedExtras.queryParams = contextSnapshot.queryParams;
                    this.activatedView.savedExtras.fragment = contextSnapshot.fragment;
                }
            }
            const c = this.component;
            this.activatedView = null;
            this.activated = null;
            this._activatedRoute = null;
            this.deactivateEvents.emit(c);
        }
    }
    activateWith(activatedRoute, environmentInjector) {
        if (this.isActivated) {
            throw new Error('Cannot activate an already activated outlet');
        }
        this._activatedRoute = activatedRoute;
        let cmpRef;
        let enteringView = this.stackCtrl.getExistingView(activatedRoute);
        if (enteringView) {
            cmpRef = this.activated = enteringView.ref;
            const saved = enteringView.savedData;
            if (saved) {
                // self-restore
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                const context = this.getContext();
                context.children['contexts'] = saved;
            }
            // Updated activated route proxy for this component
            this.updateActivatedRouteProxy(cmpRef.instance, activatedRoute);
        }
        else {
            const snapshot = activatedRoute._futureSnapshot;
            /**
             * Angular 14 introduces a new `loadComponent` property to the route config.
             * This function will assign a `component` property to the route snapshot.
             * We check for the presence of this property to determine if the route is
             * using standalone components.
             */
            const childContexts = this.parentContexts.getOrCreateContext(this.name).children;
            // We create an activated route proxy object that will maintain future updates for this component
            // over its lifecycle in the stack.
            const component$ = new BehaviorSubject(null);
            const activatedRouteProxy = this.createActivatedRouteProxy(component$, activatedRoute);
            const injector = new OutletInjector(activatedRouteProxy, childContexts, this.location.injector);
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            const component = snapshot.routeConfig.component ?? snapshot.component;
            /**
             * View components need to be added as a child of ion-router-outlet
             * for page transitions and swipe to go back.
             * However, createComponent mounts components as siblings of the
             * ViewContainerRef. As a result, outletContent must reference
             * an ng-container inside of ion-router-outlet and not
             * ion-router-outlet itself.
             */
            cmpRef = this.activated = this.outletContent.createComponent(component, {
                index: this.outletContent.length,
                injector,
                environmentInjector: environmentInjector ?? this.environmentInjector,
            });
            // Once the component is created we can push it to our local subject supplied to the proxy
            component$.next(cmpRef.instance);
            // Calling `markForCheck` to make sure we will run the change detection when the
            // `RouterOutlet` is inside a `ChangeDetectionStrategy.OnPush` component.
            /**
             * At this point this.activated has been set earlier
             * in this function, so it is guaranteed to be non-null.
             */
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            enteringView = this.stackCtrl.createView(this.activated, activatedRoute);
            // Store references to the proxy by component
            this.proxyMap.set(cmpRef.instance, activatedRouteProxy);
            this.currentActivatedRoute$.next({ component: cmpRef.instance, activatedRoute });
        }
        this.inputBinder?.bindActivatedRouteToOutletComponent(this);
        this.activatedView = enteringView;
        /**
         * The top outlet is set prior to the entering view's transition completing,
         * so that when we have nested outlets (e.g. ion-tabs inside an ion-router-outlet),
         * the tabs outlet will be assigned as the top outlet when a view inside tabs is
         * activated.
         *
         * In this scenario, activeWith is called for both the tabs and the root router outlet.
         * To avoid a race condition, we assign the top outlet synchronously.
         */
        this.navCtrl.setTopOutlet(this);
        const leavingView = this.stackCtrl.getActiveView();
        this.stackWillChange.emit({
            enteringView,
            tabSwitch: isTabSwitch(enteringView, leavingView),
        });
        this.stackCtrl.setActive(enteringView).then((data) => {
            this.activateEvents.emit(cmpRef.instance);
            this.stackDidChange.emit(data);
        });
    }
    /**
     * Returns `true` if there are pages in the stack to go back.
     */
    canGoBack(deep = 1, stackId) {
        return this.stackCtrl.canGoBack(deep, stackId);
    }
    /**
     * Resolves to `true` if it the outlet was able to sucessfully pop the last N pages.
     */
    pop(deep = 1, stackId) {
        return this.stackCtrl.pop(deep, stackId);
    }
    /**
     * Returns the URL of the active page of each stack.
     */
    getLastUrl(stackId) {
        const active = this.stackCtrl.getLastUrl(stackId);
        return active ? active.url : undefined;
    }
    /**
     * Returns the RouteView of the active page of each stack.
     * @internal
     */
    getLastRouteView(stackId) {
        return this.stackCtrl.getLastUrl(stackId);
    }
    /**
     * Returns the root view in the tab stack.
     * @internal
     */
    getRootView(stackId) {
        return this.stackCtrl.getRootUrl(stackId);
    }
    /**
     * Returns the active stack ID. In the context of ion-tabs, it means the active tab.
     */
    getActiveStackId() {
        return this.stackCtrl.getActiveStackId();
    }
    /**
     * Since the activated route can change over the life time of a component in an ion router outlet, we create
     * a proxy so that we can update the values over time as a user navigates back to components already in the stack.
     */
    createActivatedRouteProxy(component$, activatedRoute) {
        const proxy = new ActivatedRoute();
        proxy._futureSnapshot = activatedRoute._futureSnapshot;
        proxy._routerState = activatedRoute._routerState;
        proxy.snapshot = activatedRoute.snapshot;
        proxy.outlet = activatedRoute.outlet;
        proxy.component = activatedRoute.component;
        // Setup wrappers for the observables so consumers don't have to worry about switching to new observables as the state updates
        proxy._paramMap = this.proxyObservable(component$, 'paramMap');
        proxy._queryParamMap = this.proxyObservable(component$, 'queryParamMap');
        proxy.url = this.proxyObservable(component$, 'url');
        proxy.params = this.proxyObservable(component$, 'params');
        proxy.queryParams = this.proxyObservable(component$, 'queryParams');
        proxy.fragment = this.proxyObservable(component$, 'fragment');
        proxy.data = this.proxyObservable(component$, 'data');
        return proxy;
    }
    /**
     * Create a wrapped observable that will switch to the latest activated route matched by the given component
     */
    proxyObservable(component$, path) {
        return component$.pipe(
        // First wait until the component instance is pushed
        filter((component) => !!component), switchMap((component) => this.currentActivatedRoute$.pipe(filter((current) => current !== null && current.component === component), switchMap((current) => current && current.activatedRoute[path]), distinctUntilChanged())));
    }
    /**
     * Updates the activated route proxy for the given component to the new incoming router state
     */
    updateActivatedRouteProxy(component, activatedRoute) {
        const proxy = this.proxyMap.get(component);
        if (!proxy) {
            throw new Error(`Could not find activated route proxy for view`);
        }
        proxy._futureSnapshot = activatedRoute._futureSnapshot;
        proxy._routerState = activatedRoute._routerState;
        proxy.snapshot = activatedRoute.snapshot;
        proxy.outlet = activatedRoute.outlet;
        proxy.component = activatedRoute.component;
        this.currentActivatedRoute$.next({ component, activatedRoute });
    }
    /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.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 });
    /** @nocollapse */ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.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 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: IonRouterOutlet, decorators: [{
            type: Directive,
            args: [{
                    selector: 'ion-router-outlet',
                    exportAs: 'outlet',
                    // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
                    inputs: ['animated', 'animation', 'mode', 'swipeGesture'],
                }]
        }], ctorParameters: function () { return [{ type: undefined, decorators: [{
                    type: Attribute,
                    args: ['name']
                }] }, { type: undefined, decorators: [{
                    type: Optional
                }, {
                    type: Attribute,
                    args: ['tabs']
                }] }, { type: i1.Location }, { type: i0.ElementRef }, { type: i3.Router }, { type: i0.NgZone }, { type: i3.ActivatedRoute }, { type: IonRouterOutlet, decorators: [{
                    type: SkipSelf
                }, {
                    type: Optional
                }] }]; }, propDecorators: { name: [{
                type: Input
            }], stackWillChange: [{
                type: Output
            }], stackDidChange: [{
                type: Output
            }], activateEvents: [{
                type: Output,
                args: ['activate']
            }], deactivateEvents: [{
                type: Output,
                args: ['deactivate']
            }] } });
class OutletInjector {
    route;
    childContexts;
    parent;
    constructor(route, childContexts, parent) {
        this.route = route;
        this.childContexts = childContexts;
        this.parent = parent;
    }
    get(token, notFoundValue) {
        if (token === ActivatedRoute) {
            return this.route;
        }
        if (token === ChildrenOutletContexts) {
            return this.childContexts;
        }
        return this.parent.get(token, notFoundValue);
    }
}
// TODO: FW-4785 - Remove this once Angular 15 support is dropped
const INPUT_BINDER = new InjectionToken('');
/**
 * Injectable used as a tree-shakable provider for opting in to binding router data to component
 * inputs.
 *
 * The RouterOutlet registers itself with this service when an `ActivatedRoute` is attached or
 * activated. When this happens, the service subscribes to the `ActivatedRoute` observables (params,
 * queryParams, data) and sets the inputs of the component using `ComponentRef.setInput`.
 * Importantly, when an input does not have an item in the route data with a matching key, this
 * input is set to `undefined`. If it were not done this way, the previous information would be
 * retained if the data got removed from the route (i.e. if a query parameter is removed).
 *
 * The `RouterOutlet` should unregister itself when destroyed via `unsubscribeFromRouteData` so that
 * the subscriptions are cleaned up.
 */
class RoutedComponentInputBinder {
    outletDataSubscriptions = new Map();
    bindActivatedRouteToOutletComponent(outlet) {
        this.unsubscribeFromRouteData(outlet);
        this.subscribeToRouteData(outlet);
    }
    unsubscribeFromRouteData(outlet) {
        this.outletDataSubscriptions.get(outlet)?.unsubscribe();
        this.outletDataSubscriptions.delete(outlet);
    }
    subscribeToRouteData(outlet) {
        const { activatedRoute } = outlet;
        const dataSubscription = combineLatest([activatedRoute.queryParams, activatedRoute.params, activatedRoute.data])
            .pipe(switchMap(([queryParams, params, data], index) => {
            data = { ...queryParams, ...params, ...data };
            // Get the first result from the data subscription synchronously so it's available to
            // the component as soon as possible (and doesn't require a second change detection).
            if (index === 0) {
                return of(data);
            }
            // Promise.resolve is used to avoid synchronously writing the wrong data when
            // two of the Observables in the `combineLatest` stream emit one after
            // another.
            return Promise.resolve(data);
        }))
            .subscribe((data) => {
            // Outlet may have been deactivated or changed names to be associated with a different
            // route
            if (!outlet.isActivated ||
                !outlet.activatedComponentRef ||
                outlet.activatedRoute !== activatedRoute ||
                activatedRoute.component === null) {
                this.unsubscribeFromRouteData(outlet);
                return;
            }
            const mirror = reflectComponentType(activatedRoute.component);
            if (!mirror) {
                this.unsubscribeFromRouteData(outlet);
                return;
            }
            for (const { templateName } of mirror.inputs) {
                outlet.activatedComponentRef.setInput(templateName, data[templateName]);
            }
        });
        this.outletDataSubscriptions.set(outlet, dataSubscription);
    }
    /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: RoutedComponentInputBinder, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
    /** @nocollapse */ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: RoutedComponentInputBinder });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: RoutedComponentInputBinder, decorators: [{
            type: Injectable
        }] });
const provideComponentInputBinding = () => {
    return {
        provide: INPUT_BINDER,
        useFactory: componentInputBindingFactory,
        deps: [Router],
    };
};
function componentInputBindingFactory(router) {
    /**
     * We cast the router to any here, since the componentInputBindingEnabled
     * property is not available until Angular v16.
     */
    if (router?.componentInputBindingEnabled) {
        return new RoutedComponentInputBinder();
    }
    return null;
}

const raf = (h) => {
    if (typeof __zone_symbol__requestAnimationFrame === 'function') {
        return __zone_symbol__requestAnimationFrame(h);
    }
    if (typeof requestAnimationFrame === 'function') {
        return requestAnimationFrame(h);
    }
    return setTimeout(h);
};

// TODO(FW-2827): types
class ValueAccessor {
    injector;
    elementRef;
    onChange = () => {
        /**/
    };
    onTouched = () => {
        /**/
    };
    lastValue;
    statusChanges;
    constructor(injector, elementRef) {
        this.injector = injector;
        this.elementRef = elementRef;
    }
    writeValue(value) {
        this.elementRef.nativeElement.value = this.lastValue = value;
        setIonicClasses(this.elementRef);
    }
    /**
     * Notifies the ControlValueAccessor of a change in the value of the control.
     *
     * This is called by each of the ValueAccessor directives when we want to update
     * the status and validity of the form control. For example with text components this
     * is called when the ionInput event is fired. For select components this is called
     * when the ionChange event is fired.
     *
     * This also updates the Ionic form status classes on the element.
     *
     * @param el The component element.
     * @param value The new value of the control.
     */
    handleValueChange(el, value) {
        if (el === this.elementRef.nativeElement) {
            if (value !== this.lastValue) {
                this.lastValue = value;
                this.onChange(value);
            }
            setIonicClasses(this.elementRef);
        }
    }
    _handleBlurEvent(el) {
        if (el === this.elementRef.nativeElement) {
            this.onTouched();
            setIonicClasses(this.elementRef);
            // When ion-radio is blurred, el and this.elementRef.nativeElement are
            // different so we need to check if the closest ion-radio-group is the same
            // as this.elementRef.nativeElement and if so, we need to mark the radio group
            // as touched
        }
        else if (el.closest('ion-radio-group') === this.elementRef.nativeElement) {
            this.onTouched();
        }
    }
    registerOnChange(fn) {
        this.onChange = fn;
    }
    registerOnTouched(fn) {
        this.onTouched = fn;
    }
    setDisabledState(isDisabled) {
        this.elementRef.nativeElement.disabled = isDisabled;
    }
    ngOnDestroy() {
        if (this.statusChanges) {
            this.statusChanges.unsubscribe();
        }
    }
    ngAfterViewInit() {
        let ngControl;
        try {
            ngControl = this.injector.get(NgControl);
        }
        catch {
            /* No FormControl or ngModel binding */
        }
        if (!ngControl) {
            return;
        }
        // Listen for changes in validity, disabled, or pending states
        if (ngControl.statusChanges) {
            this.statusChanges = ngControl.statusChanges.subscribe(() => setIonicClasses(this.elementRef));
        }
        /**
         * TODO FW-2787: Remove this in favor of https://github.com/angular/angular/issues/10887
         * whenever it is implemented.
         */
        const formControl = ngControl.control;
        if (formControl) {
            const methodsToPatch = ['markAsTouched', 'markAllAsTouched', 'markAsUntouched', 'markAsDirty', 'markAsPristine'];
            methodsToPatch.forEach((method) => {
                if (typeof formControl[method] !== 'undefined') {
                    const oldFn = formControl[method].bind(formControl);
                    formControl[method] = (...params) => {
                        oldFn(...params);
                        setIonicClasses(this.elementRef);
                    };
                }
            });
        }
    }
    /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ValueAccessor, deps: [{ token: i0.Injector }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
    /** @nocollapse */ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: ValueAccessor, host: { listeners: { "ionBlur": "_handleBlurEvent($event.target)" } }, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ValueAccessor, decorators: [{
            type: Directive
        }], ctorParameters: function () { return [{ type: i0.Injector }, { type: i0.ElementRef }]; }, propDecorators: { _handleBlurEvent: [{
                type: HostListener,
                args: ['ionBlur', ['$event.target']]
            }] } });
const setIonicClasses = (element) => {
    raf(() => {
        const input = element.nativeElement;
        const hasValue = input.value != null && input.value.toString().length > 0;
        const classes = getClasses(input);
        setClasses(input, classes);
        const item = input.closest('ion-item');
        if (item) {
            if (hasValue) {
                setClasses(item, [...classes, 'item-has-value']);
            }
            else {
                setClasses(item, classes);
            }
        }
    });
};
const getClasses = (element) => {
    const classList = element.classList;
    const classes = [];
    for (let i = 0; i < classList.length; i++) {
        const item = classList.item(i);
        if (item !== null && startsWith(item, 'ng-')) {
            classes.push(`ion-${item.substring(3)}`);
        }
    }
    return classes;
};
const setClasses = (element, classes) => {
    const classList = element.classList;
    classList.remove('ion-valid', 'ion-invalid', 'ion-touched', 'ion-untouched', 'ion-dirty', 'ion-pristine');
    classList.add(...classes);
};
const startsWith = (input, search) => {
    return input.substring(0, search.length) === search;
};

const BACK_BUTTON_INPUTS = ['color', 'defaultHref', 'disabled', 'icon', 'mode', 'routerAnimation', 'text', 'type'];
let IonBackButton = class IonBackButton {
    routerOutlet;
    navCtrl;
    config;
    r;
    z;
    el;
    constructor(routerOutlet, navCtrl, config, r, z, c) {
        this.routerOutlet = routerOutlet;
        this.navCtrl = navCtrl;
        this.config = config;
        this.r = r;
        this.z = z;
        c.detach();
        this.el = this.r.nativeElement;
    }
    /**
     * @internal
     */
    onClick(ev) {
        const defaultHref = this.defaultHref || this.config.get('backButtonDefaultHref');
        if (this.routerOutlet?.canGoBack()) {
            this.navCtrl.setDirection('back', undefined, undefined, this.routerAnimation);
            this.routerOutlet.pop();
            ev.preventDefault();
        }
        else if (defaultHref != null) {
            this.navCtrl.navigateBack(defaultHref, { animation: this.routerAnimation });
            ev.preventDefault();
        }
    }
    /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.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 });
    /** @nocollapse */ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.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 });
};
IonBackButton = __decorate([
    ProxyCmp({
        inputs: BACK_BUTTON_INPUTS,
    })
], IonBackButton);
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: IonBackButton, decorators: [{
            type: Directive,
            args: [{
                    // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
                    inputs: BACK_BUTTON_INPUTS,
                }]
        }], ctorParameters: function () { return [{ type: IonRouterOutlet, decorators: [{
                    type: Optional
                }] }, { type: NavController }, { type: Config }, { type: i0.ElementRef }, { type: i0.NgZone }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { onClick: [{
                type: HostListener,
                args: ['click', ['$event']]
            }] } });

const NAV_INPUTS = ['animated', 'animation', 'root', 'rootParams', 'swipeGesture'];
const NAV_METHODS = [
    'push',
    'insert',
    'insertPages',
    'pop',
    'popTo',
    'popToRoot',
    'removeIndex',
    'setRoot',
    'setPages',
    'getActive',
    'getByIndex',
    'canGoBack',
    'getPrevious',
];
let IonNav = class IonNav {
    z;
    el;
    constructor(ref, environmentInjector, injector, angularDelegate, z, c) {
        this.z = z;
        c.detach();
        this.el = ref.nativeElement;
        ref.nativeElement.delegate = angularDelegate.create(environmentInjector, injector);
        proxyOutputs(this, this.el, ['ionNavDidChange', 'ionNavWillChange']);
    }
    /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.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 });
    /** @nocollapse */ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: IonNav, inputs: { animated: "animated", animation: "animation", root: "root", rootParams: "rootParams", swipeGesture: "swipeGesture" }, ngImport: i0 });
};
IonNav = __decorate([
    ProxyCmp({
        inputs: NAV_INPUTS,
        methods: NAV_METHODS,
    })
], IonNav);
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: IonNav, decorators: [{
            type: Directive,
            args: [{
                    // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
                    inputs: NAV_INPUTS,
                }]
        }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.EnvironmentInjector }, { type: i0.Injector }, { type: AngularDelegate }, { type: i0.NgZone }, { type: i0.ChangeDetectorRef }]; } });

/**
 * Adds support for Ionic routing directions and animations to the base Angular router link directive.
 *
 * When the router link is clicked, the directive will assign the direction and
 * animation so that the routing integration will transition correctly.
 */
class RouterLinkDelegateDirective {
    locationStrategy;
    navCtrl;
    elementRef;
    router;
    routerLink;
    routerDirection = 'forward';
    routerAnimation;
    constructor(locationStrategy, navCtrl, elementRef, router, routerLink) {
        this.locationStrategy = locationStrategy;
        this.navCtrl = navCtrl;
        this.elementRef = elementRef;
        this.router = router;
        this.routerLink = routerLink;
    }
    ngOnInit() {
        this.updateTargetUrlAndHref();
        this.updateTabindex();
        /**
         * Ionic components like `ion-item` render a native anchor in their shadow DOM,
         * so a modifier click (ctrl/meta/shift/alt) or a non-`_self` target should let
         * the browser handle the navigation natively (new tab, new window, download)
         * instead of navigating in-app.
         *
         * We listen in the capture phase so this runs before Angular's `RouterLink`
         * handler and our own bubble-phase `onClick`. On a native-navigation intent it
         * stops propagation to cancel the in-app navigation, but leaves `preventDefault`
         * alone so the native anchor can still act.
         */
        this.elementRef.nativeElement.addEventListener('click', this.onCaptureClick, { capture: true });
    }
    ngOnChanges() {
        this.updateTargetUrlAndHref();
    }
    ngOnDestroy() {
        this.elementRef.nativeElement.removeEventListener('click', this.onCaptureClick, { capture: true });
    }
    onCaptureClick = (ev) => {
        if (this.opensNatively(ev)) {
            ev.stopImmediatePropagation();
        }
    };
    /**
     * True when the browser should handle the click natively instead of routing
     * in-app: a modifier was held (ctrl/meta/shift/alt), or the host targets
     * something other than `_self`. This mirrors the modifier set Angular's own
     * `RouterLink` guards on, so an Ionic `routerLink` behaves like a plain anchor
     * for new-tab, new-window, and download intents.
     */
    opensNatively(ev) {
        if (ev instanceof MouseEvent && (ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey)) {
            return true;
        }
        const target = this.elementRef.nativeElement.target;
        return target != null && target !== '' && target !== '_self';
    }
    /**
     * The `tabindex` is set to `0` by default on the host element when
     * the `routerLink` directive is used. This causes issues with Ionic
     * components that wrap an `a` or `button` element, such as `ion-item`.
     * See issue https://github.com/angular/angular/issues/28345
     *
     * This method removes the `tabindex` attribute from the host element
     * to allow the Ionic component to manage the focus state correctly.
     */
    updateTabindex() {
        // Ionic components that render a native anchor or button element
        const ionicComponents = [
            'ION-BACK-BUTTON',
            'ION-BREADCRUMB',
            'ION-BUTTON',
            'ION-CARD',
            'ION-FAB-BUTTON',
            'ION-ITEM',
            'ION-ITEM-OPTION',
            'ION-MENU-BUTTON',
            'ION-SEGMENT-BUTTON',
            'ION-TAB-BUTTON',
        ];
        const hostElement = this.elementRef.nativeElement;
        if (ionicComponents.includes(hostElement.tagName)) {
            if (hostElement.getAttribute('tabindex') === '0') {
                hostElement.removeAttribute('tabindex');
            }
        }
    }
    updateTargetUrlAndHref() {
        if (this.routerLink?.urlTree) {
            const href = this.locationStrategy.prepareExternalUrl(this.router.serializeUrl(this.routerLink.urlTree));
            this.elementRef.nativeElement.href = href;
        }
    }
    /**
     * @internal
     */
    onClick(ev) {
        this.navCtrl.setDirection(this.routerDirection, undefined, undefined, this.routerAnimation);
        /**
         * This prevents the browser from
         * performing a page reload when pressing
         * an Ionic component with routerLink.
         * The page reload interferes with routing
         * and causes ion-back-button to disappear
         * since the local history is wiped on reload.
         */
        ev.preventDefault();
    }
    /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.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 });
    /** @nocollapse */ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: RouterLinkDelegateDirective, selector: ":not(a):not(area)[routerLink]", inputs: { routerDirection: "routerDirection", routerAnimation: "routerAnimation" }, host: { listeners: { "click": "onClick($event)" } }, usesOnChanges: true, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: RouterLinkDelegateDirective, decorators: [{
            type: Directive,
            args: [{
                    selector: ':not(a):not(area)[routerLink]',
                }]
        }], ctorParameters: function () { return [{ type: i1.LocationStrategy }, { type: NavController }, { type: i0.ElementRef }, { type: i3.Router }, { type: i3.RouterLink, decorators: [{
                    type: Optional
                }] }]; }, propDecorators: { routerDirection: [{
                type: Input
            }], routerAnimation: [{
                type: Input
            }], onClick: [{
                type: HostListener,
                args: ['click', ['$event']]
            }] } });
class RouterLinkWithHrefDelegateDirective {
    locationStrategy;
    navCtrl;
    elementRef;
    router;
    routerLink;
    routerDirection = 'forward';
    routerAnimation;
    constructor(locationStrategy, navCtrl, elementRef, router, routerLink) {
        this.locationStrategy = locationStrategy;
        this.navCtrl = navCtrl;
        this.elementRef = elementRef;
        this.router = router;
        this.routerLink = routerLink;
    }
    ngOnInit() {
        this.updateTargetUrlAndHref();
    }
    ngOnChanges() {
        this.updateTargetUrlAndHref();
    }
    updateTargetUrlAndHref() {
        if (this.routerLink?.urlTree) {
            const href = this.locationStrategy.prepareExternalUrl(this.router.serializeUrl(this.routerLink.urlTree));
            this.elementRef.nativeElement.href = href;
        }
    }
    /**
     * @internal
     */
    onClick() {
        this.navCtrl.setDirection(this.routerDirection, undefined, undefined, this.routerAnimation);
    }
    /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.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 });
    /** @nocollapse */ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: RouterLinkWithHrefDelegateDirective, selector: "a[routerLink],area[routerLink]", inputs: { routerDirection: "routerDirection", routerAnimation: "routerAnimation" }, host: { listeners: { "click": "onClick()" } }, usesOnChanges: true, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: RouterLinkWithHrefDelegateDirective, decorators: [{
            type: Directive,
            args: [{
                    selector: 'a[routerLink],area[routerLink]',
                }]
        }], ctorParameters: function () { return [{ type: i1.LocationStrategy }, { type: NavController }, { type: i0.ElementRef }, { type: i3.Router }, { type: i3.RouterLink, decorators: [{
                    type: Optional
                }] }]; }, propDecorators: { routerDirection: [{
                type: Input
            }], routerAnimation: [{
                type: Input
            }], onClick: [{
                type: HostListener,
                args: ['click']
            }] } });

/**
 * Extracts `queryParams` and `fragment` from a tab button's href for use
 * as Angular `NavigationExtras`. Returns `undefined` when neither is present.
 */
const parseHrefExtras = (href) => {
    if (!href) {
        return undefined;
    }
    const hashIndex = href.indexOf('#');
    // Treat a bare `#` (no fragment text) as no fragment.
    const fragment = hashIndex >= 0 && hashIndex < href.length - 1 ? href.slice(hashIndex + 1) : undefined;
    const beforeHash = hashIndex >= 0 ? href.slice(0, hashIndex) : href;
    const queryIndex = beforeHash.indexOf('?');
    const search = queryIndex >= 0 ? beforeHash.slice(queryIndex + 1) : '';
    let queryParams;
    if (search) {
        const params = new URLSearchParams(search);
        queryParams = {};
        for (const key of new Set(params.keys())) {
            const all = params.getAll(key);
            queryParams[key] = all.length > 1 ? all : all[0];
        }
    }
    if (!queryParams && fragment === undefined) {
        return undefined;
    }
    /**
     * Build the result with only the populated keys so that a spread of the
     * returned object does not overwrite saved `queryParams`/`fragment` with
     * `undefined` (which `Object.assign`/spread would copy as a real key).
     */
    const extras = {};
    if (queryParams)
        extras.queryParams = queryParams;
    if (fragment !== undefined)
        extras.fragment = fragment;
    return extras;
};
class IonTabs {
    navCtrl;
    tabsInner;
    /**
     * Emitted before the tab view is changed.
     */
    ionTabsWillChange = new EventEmitter();
    /**
     * Emitted after the tab view is changed.
     */
    ionTabsDidChange = new EventEmitter();
    tabBarSlot = 'bottom';
    hasTab = false;
    selectedTab;
    leavingTab;
    constructor(navCtrl) {
        this.navCtrl = navCtrl;
    }
    ngAfterViewInit() {
        /**
         * Developers must pass at least one ion-tab
         * inside of ion-tabs if they want to use a
         * basic tab-based navigation without the
         * history stack or URL updates associated
         * with the router.
         */
        const firstTab = this.tabs.length > 0 ? this.tabs.first : undefined;
        if (firstTab) {
            this.hasTab = true;
            this.setActiveTab(firstTab.tab);
            this.tabSwitch();
        }
    }
    ngAfterContentInit() {
        this.detectSlotChanges();
    }
    ngAfterContentChecked() {
        this.detectSlotChanges();
    }
    /**
     * @internal
     */
    onStackWillChange({ enteringView, tabSwitch }) {
        const stackId = enteringView.stackId;
        if (tabSwitch && stackId !== undefined) {
            this.ionTabsWillChange.emit({ tab: stackId });
        }
    }
    /**
     * @internal
     */
    onStackDidChange({ enteringView, tabSwitch }) {
        const stackId = enteringView.stackId;
        if (tabSwitch && stackId !== undefined) {
            if (this.tabBar) {
                this.tabBar.selectedTab = stackId;
            }
            this.ionTabsDidChange.emit({ tab: stackId });
        }
    }
    /**
     * When a tab button is clicked, there are several scenarios:
     * 1. If the selected tab is currently active (the tab button has been clicked
     *    again), then it should go to the root view for that tab.
     *
     *   a. Get the saved root view from the router outlet. If the saved root view
     *      matches the tabRootUrl, set the route view to this view including the
     *      navigation extras. Any `queryParams` or `fragment` declared on the tab
     *      button's `href` are also forwarded.
     *   b. If the saved root view from the router outlet does not match, navigate
     *      to the tabRootUrl, forwarding any `queryParams`/`fragment` declared on
     *      the tab button's `href`.
     *
     * 2. If the current tab tab is not currently selected, get the last route
     *    view from the router outlet.
     *
     *   a. If the last route view exists, navigate to that view including any
     *      navigation extras.
     *   b. If the last route view doesn't exist, then navigate to the default
     *      tabRootUrl, forwarding any `queryParams`/`fragment` declared on the
     *      tab button's `href`.
     */
    select(tabOrEvent) {
        const isTabString = typeof tabOrEvent === 'string';
        const tab = isTabString ? tabOrEvent : tabOrEvent.detail.tab;
        const href = isTabString ? undefined : tabOrEvent.detail.href;
        /**
         * If the tabs are not using the router, then
         * the tab switch logic is handled by the tabs
         * component itself.
         */
        if (this.hasTab) {
            this.setActiveTab(tab);
            this.tabSwitch();
            return;
        }
        const alreadySelected = this.outlet.getActiveStackId() === tab;
        const tabRootUrl = `${this.outlet.tabsPrefix}/${tab}`;
        /**
         * The href pathname is ignored here; tab routing is driven by `tabsPrefix/tab`.
         * Only the query and fragment are forwarded as navigation extras.
         */
        const hrefExtras = parseHrefExtras(href);
        /**
         * If this is a nested tab, prevent the event
         * from bubbling otherwise the outer tabs
         * will respond to this event too, causing
         * the app to get directed to the wrong place.
         */
        if (!isTabString) {
            tabOrEvent.stopPropagation();
        }
        if (alreadySelected) {
            const activeStackId = this.outlet.getActiveStackId();
            const activeView = this.outlet.getLastRouteView(activeStackId);
            // If on root tab, do not navigate to root tab again
            if (activeView?.url === tabRootUrl) {
                return;
            }
            const rootView = this.outlet.getRootView(tab);
            const navigationExtras = rootView && tabRootUrl === rootView.url && rootView.savedExtras;
            return this.navCtrl.navigateRoot(tabRootUrl, {
                ...navigationExtras,
                ...hrefExtras,
                animated: true,
                animationDirection: 'back',
            });
        }
        else {
            const lastRoute = this.outlet.getLastRouteView(tab);
            /**
             * If there is a lastRoute, goto that, otherwise goto the fallback url of the
             * selected tab. When falling back to the tab root, honor query params and
             * fragment declared on the tab button's href.
             */
            const url = lastRoute?.url || tabRootUrl;
            const navigationExtras = lastRoute?.savedExtras ?? (url === tabRootUrl ? hrefExtras : undefined);
            return this.navCtrl.navigateRoot(url, {
                ...navigationExtras,
                animated: true,
                animationDirection: 'back',
            });
        }
    }
    setActiveTab(tab) {
        const tabs = this.tabs;
        const selectedTab = tabs.find((t) => t.tab === tab);
        if (!selectedTab) {
            console.error(`[Ionic Error]: Tab with id: "${tab}" does not exist`);
            return;
        }
        this.leavingTab = this.selectedTab;
        this.selectedTab = selectedTab;
        this.ionTabsWillChange.emit({ tab });
        selectedTab.el.active = true;
    }
    tabSwitch() {
        const { selectedTab, leavingTab } = this;
        if (this.tabBar && selectedTab) {
            this.tabBar.selectedTab = selectedTab.tab;
        }
        if (leavingTab?.tab !== selectedTab?.tab) {
            if (leavingTab?.el) {
                leavingTab.el.active = false;
            }
        }
        if (selectedTab) {
            this.ionTabsDidChange.emit({ tab: selectedTab.tab });
        }
    }
    getSelected() {
        if (this.hasTab) {
            return this.selectedTab?.tab;
        }
        return this.outlet.getActiveStackId();
    }
    /**
     * Detects changes to the slot attribute of the tab bar.
     *
     * If the slot attribute has changed, then the tab bar
     * should be relocated to the new slot position.
     */
    detectSlotChanges() {
        this.tabBars.forEach((tabBar) => {
            // el is a protected attribute from the generated component wrapper
            const currentSlot = tabBar.el.getAttribute('slot');
            if (currentSlot !== this.tabBarSlot) {
                this.tabBarSlot = currentSlot;
                this.relocateTabBar();
            }
        });
    }
    /**
     * Relocates the tab bar to the new slot position.
     */
    relocateTabBar() {
        /**
         * `el` is a protected attribute from the generated component wrapper.
         * To avoid having to manually create the wrapper for tab bar, we
         * cast the tab bar to any and access the protected attribute.
         */
        const tabBar = this.tabBar.el;
        if (this.tabBarSlot === 'top') {
            /**
             * A tab bar with a slot of "top" should be inserted
             * at the top of the container.
             */
            this.tabsInner.nativeElement.before(tabBar);
        }
        else {
            /**
             * A tab bar with a slot of "bottom" or without a slot
             * should be inserted at the end of the container.
             */
            this.tabsInner.nativeElement.after(tabBar);
        }
    }
    /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: IonTabs, deps: [{ token: NavController }], target: i0.ɵɵFactoryTarget.Directive });
    /** @nocollapse */ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.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 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: IonTabs, decorators: [{
            type: Directive,
            args: [{
                    selector: 'ion-tabs',
                }]
        }], ctorParameters: function () { return [{ type: NavController }]; }, propDecorators: { tabsInner: [{
                type: ViewChild,
                args: ['tabsInner', { read: ElementRef, static: true }]
            }], ionTabsWillChange: [{
                type: Output
            }], ionTabsDidChange: [{
                type: Output
            }], select: [{
                type: HostListener,
                args: ['ionTabButtonClick', ['$event']]
            }] } });

// TODO(FW-2827): types
class OverlayBaseController {
    ctrl;
    constructor(ctrl) {
        this.ctrl = ctrl;
    }
    /**
     * Creates a new overlay
     */
    create(opts) {
        return this.ctrl.create((opts || {}));
    }
    /**
     * When `id` is not provided, it dismisses the top overlay.
     */
    dismiss(data, role, id) {
        return this.ctrl.dismiss(data, role, id);
    }
    /**
     * Returns the top overlay.
     */
    getTop() {
        return this.ctrl.getTop();
    }
}

/**
 * Provides a way to customize when activated routes get reused.
 */
class IonicRouteStrategy {
    /**
     * Whether the given route should detach for later reuse.
     */
    shouldDetach(_route) {
        return false;
    }
    /**
     * Returns `false`, meaning the route (and its subtree) is never reattached
     */
    shouldAttach(_route) {
        return false;
    }
    /**
     * A no-op; the route is never stored since this strategy never detaches routes for later re-use.
     */
    store(_route, _detachedTree) {
        return;
    }
    /**
     * Returns `null` because this strategy does not store routes for later re-use.
     */
    retrieve(_route) {
        return null;
    }
    /**
     * Determines if a route should be reused.
     * This strategy returns `true` when the future route config and
     * current route config are identical and all route parameters are identical.
     */
    shouldReuseRoute(future, curr) {
        if (future.routeConfig !== curr.routeConfig) {
            return false;
        }
        // checking router params
        const futureParams = future.params;
        const currentParams = curr.params;
        const keysA = Object.keys(futureParams);
        const keysB = Object.keys(currentParams);
        if (keysA.length !== keysB.length) {
            return false;
        }
        // Test for A's keys different from B.
        for (const key of keysA) {
            if (currentParams[key] !== futureParams[key]) {
                return false;
            }
        }
        return true;
    }
}

/**
 * Generated bundle index. Do not edit.
 */

export { AngularDelegate, Config, ConfigToken, DomController, IonBackButton, IonModal, IonModalToken, IonNav, IonPopover, IonRouterOutlet, IonTabs, IonicRouteStrategy, MenuController, NavController, NavParams, OverlayBaseController, Platform, ProxyCmp, RouterLinkDelegateDirective, RouterLinkWithHrefDelegateDirective, ValueAccessor, bindLifecycleEvents, provideComponentInputBinding, raf, setIonicClasses };
//# sourceMappingURL=ionic-angular-common.mjs.map