@ionic/angular
Version:
Angular specific wrappers for @ionic/core
1 lines • 170 kB
Source Map (JSON)
{"version":3,"file":"ionic-angular-common.mjs","sources":["../../common/src/providers/menu-controller.ts","../../common/src/providers/dom-controller.ts","../../common/src/providers/platform.ts","../../common/src/providers/nav-controller.ts","../../common/src/providers/config.ts","../../common/src/directives/navigation/nav-params.ts","../../common/src/providers/angular-delegate.ts","../../common/src/utils/proxy.ts","../../common/src/overlays/popover.ts","../../common/src/overlays/modal.ts","../../common/src/directives/navigation/stack-utils.ts","../../common/src/directives/navigation/stack-controller.ts","../../common/src/directives/navigation/router-outlet.ts","../../common/src/directives/navigation/back-button.ts","../../common/src/directives/navigation/router-link-delegate.ts","../../common/src/directives/navigation/nav.ts","../../common/src/directives/navigation/tabs.ts","../../common/src/utils/util.ts","../../common/src/directives/control-value-accessors/value-accessor.ts","../../common/src/utils/routing.ts","../../common/src/utils/overlay.ts","../../common/src/ionic-angular-common.ts"],"sourcesContent":["import type { MenuControllerI, AnimationBuilder, MenuI, Animation } from '@ionic/core/components';\n\nexport class MenuController implements MenuControllerI {\n constructor(private menuController: MenuControllerI) {}\n\n /**\n * Programmatically open the Menu.\n * @param [menuId] Optionally get the menu by its id, or side.\n * @return returns a promise when the menu is fully opened\n */\n open(menuId?: string): Promise<boolean> {\n return this.menuController.open(menuId);\n }\n\n /**\n * Programmatically close the Menu. If no `menuId` is given as the first\n * argument then it'll close any menu which is open. If a `menuId`\n * is given then it'll close that exact menu.\n * @param [menuId] Optionally get the menu by its id, or side.\n * @return returns a promise when the menu is fully closed\n */\n close(menuId?: string): Promise<boolean> {\n return this.menuController.close(menuId);\n }\n\n /**\n * Toggle the menu. If it's closed, it will open, and if opened, it\n * will close.\n * @param [menuId] Optionally get the menu by its id, or side.\n * @return returns a promise when the menu has been toggled\n */\n toggle(menuId?: string): Promise<boolean> {\n return this.menuController.toggle(menuId);\n }\n\n /**\n * Used to enable or disable a menu. For example, there could be multiple\n * left menus, but only one of them should be able to be opened at the same\n * time. If there are multiple menus on the same side, then enabling one menu\n * will also automatically disable all the others that are on the same side.\n * @param [menuId] Optionally get the menu by its id, or side.\n * @return Returns the instance of the menu, which is useful for chaining.\n */\n enable(shouldEnable: boolean, menuId?: string): Promise<HTMLIonMenuElement | undefined> {\n return this.menuController.enable(shouldEnable, menuId);\n }\n\n /**\n * Used to enable or disable the ability to swipe open the menu.\n * @param shouldEnable True if it should be swipe-able, false if not.\n * @param [menuId] Optionally get the menu by its id, or side.\n * @return Returns the instance of the menu, which is useful for chaining.\n */\n swipeGesture(shouldEnable: boolean, menuId?: string): Promise<HTMLIonMenuElement | undefined> {\n return this.menuController.swipeGesture(shouldEnable, menuId);\n }\n\n /**\n * @param [menuId] Optionally get the menu by its id, or side.\n * @return Returns true if the specified menu is currently open, otherwise false.\n * If the menuId is not specified, it returns true if ANY menu is currenly open.\n */\n isOpen(menuId?: string): Promise<boolean> {\n return this.menuController.isOpen(menuId);\n }\n\n /**\n * @param [menuId] Optionally get the menu by its id, or side.\n * @return Returns true if the menu is currently enabled, otherwise false.\n */\n isEnabled(menuId?: string): Promise<boolean> {\n return this.menuController.isEnabled(menuId);\n }\n\n /**\n * Used to get a menu instance. If a `menuId` is not provided then it'll\n * return the first menu found. If a `menuId` is `left` or `right`, then\n * it'll return the enabled menu on that side. Otherwise, if a `menuId` is\n * provided, then it'll try to find the menu using the menu's `id`\n * property. If a menu is not found then it'll return `null`.\n * @param [menuId] Optionally get the menu by its id, or side.\n * @return Returns the instance of the menu if found, otherwise `null`.\n */\n get(menuId?: string): Promise<HTMLIonMenuElement | undefined> {\n return this.menuController.get(menuId);\n }\n\n /**\n * @return Returns the instance of the menu already opened, otherwise `null`.\n */\n getOpen(): Promise<HTMLIonMenuElement | undefined> {\n return this.menuController.getOpen();\n }\n\n /**\n * @return Returns an array of all menu instances.\n */\n getMenus(): Promise<HTMLIonMenuElement[]> {\n return this.menuController.getMenus();\n }\n\n registerAnimation(name: string, animation: AnimationBuilder): void {\n return this.menuController.registerAnimation(name, animation);\n }\n\n isAnimating(): Promise<boolean> {\n return this.menuController.isAnimating();\n }\n\n _getOpenSync(): HTMLIonMenuElement | undefined {\n return this.menuController._getOpenSync();\n }\n\n _createAnimation(type: string, menuCmp: MenuI): Promise<Animation> {\n return this.menuController._createAnimation(type, menuCmp);\n }\n\n _register(menu: MenuI): void {\n return this.menuController._register(menu);\n }\n\n _unregister(menu: MenuI): void {\n return this.menuController._unregister(menu);\n }\n\n _setOpen(menu: MenuI, shouldOpen: boolean, animated: boolean): Promise<boolean> {\n return this.menuController._setOpen(menu, shouldOpen, animated);\n }\n}\n","import { Injectable } from '@angular/core';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class DomController {\n /**\n * Schedules a task to run during the READ phase of the next frame.\n * This task should only read the DOM, but never modify it.\n */\n read(cb: RafCallback): void {\n getQueue().read(cb);\n }\n\n /**\n * Schedules a task to run during the WRITE phase of the next frame.\n * This task should write the DOM, but never READ it.\n */\n write(cb: RafCallback): void {\n getQueue().write(cb);\n }\n}\n\nconst getQueue = () => {\n const win = typeof (window as any) !== 'undefined' ? window : (null as any);\n\n if (win != null) {\n const Ionic = win.Ionic;\n if (Ionic?.queue) {\n return Ionic.queue;\n }\n\n return {\n read: (cb: any) => win.requestAnimationFrame(cb),\n write: (cb: any) => win.requestAnimationFrame(cb),\n };\n }\n\n return {\n read: (cb: any) => cb(),\n write: (cb: any) => cb(),\n };\n};\n\nexport type RafCallback = (timeStamp?: number) => void;\n","import { DOCUMENT } from '@angular/common';\nimport { NgZone, Inject, Injectable } from '@angular/core';\nimport { getPlatforms, isPlatform } from '@ionic/core/components';\nimport type { BackButtonEventDetail, KeyboardEventDetail, Platforms } from '@ionic/core/components';\nimport { Subscription, Subject } from 'rxjs';\n\n// TODO(FW-2827): types\n\nexport interface BackButtonEmitter extends Subject<BackButtonEventDetail> {\n subscribeWithPriority(\n priority: number,\n callback: (processNextHandler: () => void) => Promise<any> | void\n ): Subscription;\n}\n\n@Injectable({\n providedIn: 'root',\n})\nexport class Platform {\n private _readyPromise: Promise<string>;\n private win: any;\n\n /**\n * @hidden\n */\n backButton = new Subject<BackButtonEventDetail>() as BackButtonEmitter;\n\n /**\n * The keyboardDidShow event emits when the\n * on-screen keyboard is presented.\n */\n keyboardDidShow = new Subject<KeyboardEventDetail>();\n\n /**\n * The keyboardDidHide event emits when the\n * on-screen keyboard is hidden.\n */\n keyboardDidHide = new Subject<void>();\n\n /**\n * The pause event emits when the native platform puts the application\n * into the background, typically when the user switches to a different\n * application. This event would emit when a Cordova app is put into\n * the background, however, it would not fire on a standard web browser.\n */\n pause = new Subject<void>();\n\n /**\n * The resume event emits when the native platform pulls the application\n * out from the background. This event would emit when a Cordova app comes\n * out from the background, however, it would not fire on a standard web browser.\n */\n resume = new Subject<void>();\n\n /**\n * The resize event emits when the browser window has changed dimensions. This\n * could be from a browser window being physically resized, or from a device\n * changing orientation.\n */\n resize = new Subject<void>();\n\n constructor(@Inject(DOCUMENT) private doc: any, zone: NgZone) {\n zone.run(() => {\n this.win = doc.defaultView;\n this.backButton.subscribeWithPriority = function (priority, callback) {\n return this.subscribe((ev) => {\n return ev.register(priority, (processNextHandler) => zone.run(() => callback(processNextHandler)));\n });\n };\n\n proxyEvent(this.pause, doc, 'pause', zone);\n proxyEvent(this.resume, doc, 'resume', zone);\n proxyEvent(this.backButton, doc, 'ionBackButton', zone);\n proxyEvent(this.resize, this.win, 'resize', zone);\n proxyEvent(this.keyboardDidShow, this.win, 'ionKeyboardDidShow', zone);\n proxyEvent(this.keyboardDidHide, this.win, 'ionKeyboardDidHide', zone);\n\n let readyResolve: (value: string) => void;\n this._readyPromise = new Promise((res) => {\n readyResolve = res;\n });\n if (this.win?.['cordova']) {\n doc.addEventListener(\n 'deviceready',\n () => {\n readyResolve('cordova');\n },\n { once: true }\n );\n } else {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n readyResolve!('dom');\n }\n });\n }\n\n /**\n * @returns returns true/false based on platform.\n * @description\n * Depending on the platform the user is on, `is(platformName)` will\n * return `true` or `false`. Note that the same app can return `true`\n * for more than one platform name. For example, an app running from\n * an iPad would return `true` for the platform names: `mobile`,\n * `ios`, `ipad`, and `tablet`. Additionally, if the app was running\n * from Cordova then `cordova` would be true, and if it was running\n * from a web browser on the iPad then `mobileweb` would be `true`.\n *\n * ```\n * import { Platform } from 'ionic-angular';\n *\n * @Component({...})\n * export MyPage {\n * constructor(public platform: Platform) {\n * if (this.platform.is('ios')) {\n * // This will only print when on iOS\n * console.log('I am an iOS device!');\n * }\n * }\n * }\n * ```\n *\n * | Platform Name | Description |\n * |-----------------|------------------------------------|\n * | android | on a device running Android. |\n * | capacitor | on a device running Capacitor. |\n * | cordova | on a device running Cordova. |\n * | ios | on a device running iOS. |\n * | ipad | on an iPad device. |\n * | iphone | on an iPhone device. |\n * | phablet | on a phablet device. |\n * | tablet | on a tablet device. |\n * | electron | in Electron on a desktop device. |\n * | pwa | as a PWA app. |\n * | mobile | on a mobile device. |\n * | mobileweb | on a mobile device in a browser. |\n * | desktop | on a desktop device. |\n * | hybrid | is a cordova or capacitor app. |\n *\n */\n is(platformName: Platforms): boolean {\n return isPlatform(this.win, platformName);\n }\n\n /**\n * @returns the array of platforms\n * @description\n * Depending on what device you are on, `platforms` can return multiple values.\n * Each possible value is a hierarchy of platforms. For example, on an iPhone,\n * it would return `mobile`, `ios`, and `iphone`.\n *\n * ```\n * import { Platform } from 'ionic-angular';\n *\n * @Component({...})\n * export MyPage {\n * constructor(public platform: Platform) {\n * // This will print an array of the current platforms\n * console.log(this.platform.platforms());\n * }\n * }\n * ```\n */\n platforms(): string[] {\n return getPlatforms(this.win);\n }\n\n /**\n * Returns a promise when the platform is ready and native functionality\n * can be called. If the app is running from within a web browser, then\n * the promise will resolve when the DOM is ready. When the app is running\n * from an application engine such as Cordova, then the promise will\n * resolve when Cordova triggers the `deviceready` event.\n *\n * The resolved value is the `readySource`, which states which platform\n * ready was used. For example, when Cordova is ready, the resolved ready\n * source is `cordova`. The default ready source value will be `dom`. The\n * `readySource` is useful if different logic should run depending on the\n * platform the app is running from. For example, only Cordova can execute\n * the status bar plugin, so the web should not run status bar plugin logic.\n *\n * ```\n * import { Component } from '@angular/core';\n * import { Platform } from 'ionic-angular';\n *\n * @Component({...})\n * export MyApp {\n * constructor(public platform: Platform) {\n * this.platform.ready().then((readySource) => {\n * console.log('Platform ready from', readySource);\n * // Platform now ready, execute any required native code\n * });\n * }\n * }\n * ```\n */\n ready(): Promise<string> {\n return this._readyPromise;\n }\n\n /**\n * Returns if this app is using right-to-left language direction or not.\n * We recommend the app's `index.html` file already has the correct `dir`\n * attribute value set, such as `<html dir=\"ltr\">` or `<html dir=\"rtl\">`.\n * [W3C: Structural markup and right-to-left text in HTML](http://www.w3.org/International/questions/qa-html-dir)\n */\n get isRTL(): boolean {\n return this.doc.dir === 'rtl';\n }\n\n /**\n * Get the query string parameter\n */\n getQueryParam(key: string): string | null {\n return readQueryParam(this.win.location.href, key);\n }\n\n /**\n * Returns `true` if the app is in landscape mode.\n */\n isLandscape(): boolean {\n return !this.isPortrait();\n }\n\n /**\n * Returns `true` if the app is in portrait mode.\n */\n isPortrait(): boolean {\n return this.win.matchMedia?.('(orientation: portrait)').matches;\n }\n\n testUserAgent(expression: string): boolean {\n const nav = this.win.navigator;\n return !!(nav?.userAgent && nav.userAgent.indexOf(expression) >= 0);\n }\n\n /**\n * Get the current url.\n */\n url(): string {\n return this.win.location.href;\n }\n\n /**\n * Gets the width of the platform's viewport using `window.innerWidth`.\n */\n width(): number {\n return this.win.innerWidth;\n }\n\n /**\n * Gets the height of the platform's viewport using `window.innerHeight`.\n */\n height(): number {\n return this.win.innerHeight;\n }\n}\n\nconst readQueryParam = (url: string, key: string) => {\n key = key.replace(/[[\\]\\\\]/g, '\\\\$&');\n const regex = new RegExp('[\\\\?&]' + key + '=([^&#]*)');\n const results = regex.exec(url);\n return results ? decodeURIComponent(results[1].replace(/\\+/g, ' ')) : null;\n};\n\nconst proxyEvent = <T>(emitter: Subject<T>, el: EventTarget, eventName: string, zone: NgZone) => {\n if (el) {\n el.addEventListener(eventName, (ev) => {\n /**\n * `zone.run` is required to make sure that we are running inside the Angular zone\n * at all times. This is necessary since an app that has Capacitor will\n * override the `document.addEventListener` with its own implementation.\n * The override causes the event to no longer be in the Angular zone.\n */\n zone.run(() => {\n // ?? cordova might emit \"null\" events\n const value = ev != null ? (ev as any).detail : undefined;\n emitter.next(value);\n });\n });\n }\n};\n","import { Location } from '@angular/common';\nimport { Injectable, Optional } from '@angular/core';\nimport { NavigationExtras, Router, UrlSerializer, UrlTree, NavigationStart } from '@angular/router';\nimport type { AnimationBuilder, NavDirection, RouterDirection } from '@ionic/core/components';\n\nimport { IonRouterOutlet } from '../directives/navigation/router-outlet';\n\nimport { Platform } from './platform';\n\nexport interface AnimationOptions {\n animated?: boolean;\n animation?: AnimationBuilder;\n animationDirection?: 'forward' | 'back';\n}\n\nexport interface NavigationOptions extends NavigationExtras, AnimationOptions {}\n\n@Injectable({\n providedIn: 'root',\n})\nexport class NavController {\n private topOutlet?: IonRouterOutlet;\n private direction: 'forward' | 'back' | 'root' | 'auto' = DEFAULT_DIRECTION;\n private animated?: NavDirection = DEFAULT_ANIMATED;\n private animationBuilder?: AnimationBuilder;\n private guessDirection: RouterDirection = 'forward';\n private guessAnimation?: NavDirection;\n private lastNavId = -1;\n\n constructor(\n platform: Platform,\n private location: Location,\n private serializer: UrlSerializer,\n @Optional() private router?: Router\n ) {\n // Subscribe to router events to detect direction\n if (router) {\n router.events.subscribe((ev) => {\n if (ev instanceof NavigationStart) {\n // restoredState is set if the browser back/forward button is used\n const id = ev.restoredState ? ev.restoredState.navigationId : ev.id;\n this.guessDirection = this.guessAnimation = id < this.lastNavId ? 'back' : 'forward';\n this.lastNavId = this.guessDirection === 'forward' ? ev.id : id;\n }\n });\n }\n\n // Subscribe to backButton events\n platform.backButton.subscribeWithPriority(0, (processNextHandler) => {\n this.pop();\n processNextHandler();\n });\n }\n\n /**\n * This method uses Angular's [Router](https://angular.io/api/router/Router) under the hood,\n * it's equivalent to calling `this.router.navigateByUrl()`, but it's explicit about the **direction** of the transition.\n *\n * Going **forward** means that a new page is going to be pushed to the stack of the outlet (ion-router-outlet),\n * and that it will show a \"forward\" animation by default.\n *\n * Navigating forward can also be triggered in a declarative manner by using the `[routerDirection]` directive:\n *\n * ```html\n * <a routerLink=\"/path/to/page\" routerDirection=\"forward\">Link</a>\n * ```\n */\n navigateForward(url: string | UrlTree | any[], options: NavigationOptions = {}): Promise<boolean> {\n this.setDirection('forward', options.animated, options.animationDirection, options.animation);\n return this.navigate(url, options);\n }\n\n /**\n * This method uses Angular's [Router](https://angular.io/api/router/Router) under the hood,\n * it's equivalent to calling:\n *\n * ```ts\n * this.navController.setDirection('back');\n * this.router.navigateByUrl(path);\n * ```\n *\n * Going **back** means that all the pages in the stack until the navigated page is found will be popped,\n * and that it will show a \"back\" animation by default.\n *\n * Navigating back can also be triggered in a declarative manner by using the `[routerDirection]` directive:\n *\n * ```html\n * <a routerLink=\"/path/to/page\" routerDirection=\"back\">Link</a>\n * ```\n */\n navigateBack(url: string | UrlTree | any[], options: NavigationOptions = {}): Promise<boolean> {\n this.setDirection('back', options.animated, options.animationDirection, options.animation);\n return this.navigate(url, options);\n }\n\n /**\n * This method uses Angular's [Router](https://angular.io/api/router/Router) under the hood,\n * it's equivalent to calling:\n *\n * ```ts\n * this.navController.setDirection('root');\n * this.router.navigateByUrl(path);\n * ```\n *\n * Going **root** means that all existing pages in the stack will be removed,\n * and the navigated page will become the single page in the stack.\n *\n * Navigating root can also be triggered in a declarative manner by using the `[routerDirection]` directive:\n *\n * ```html\n * <a routerLink=\"/path/to/page\" routerDirection=\"root\">Link</a>\n * ```\n */\n navigateRoot(url: string | UrlTree | any[], options: NavigationOptions = {}): Promise<boolean> {\n this.setDirection('root', options.animated, options.animationDirection, options.animation);\n return this.navigate(url, options);\n }\n\n /**\n * Same as [Location](https://angular.io/api/common/Location)'s back() method.\n * It will use the standard `window.history.back()` under the hood, but featuring a `back` animation\n * by default.\n */\n back(options: AnimationOptions = { animated: true, animationDirection: 'back' }): void {\n this.setDirection('back', options.animated, options.animationDirection, options.animation);\n return this.location.back();\n }\n\n /**\n * This methods goes back in the context of Ionic's stack navigation.\n *\n * It recursively finds the top active `ion-router-outlet` and calls `pop()`.\n * This is the recommended way to go back when you are using `ion-router-outlet`.\n *\n * Resolves to `true` if it was able to pop.\n */\n async pop(): Promise<boolean> {\n let outlet = this.topOutlet;\n\n while (outlet) {\n if (await outlet.pop()) {\n return true;\n } else {\n outlet = outlet.parentOutlet;\n }\n }\n\n return false;\n }\n\n /**\n * This methods specifies the direction of the next navigation performed by the Angular router.\n *\n * `setDirection()` does not trigger any transition, it just sets some flags to be consumed by `ion-router-outlet`.\n *\n * It's recommended to use `navigateForward()`, `navigateBack()` and `navigateRoot()` instead of `setDirection()`.\n */\n setDirection(\n direction: RouterDirection,\n animated?: boolean,\n animationDirection?: 'forward' | 'back',\n animationBuilder?: AnimationBuilder\n ): void {\n this.direction = direction;\n this.animated = getAnimation(direction, animated, animationDirection);\n this.animationBuilder = animationBuilder;\n }\n\n /**\n * @internal\n */\n setTopOutlet(outlet: IonRouterOutlet): void {\n this.topOutlet = outlet;\n }\n\n /**\n * @internal\n */\n consumeTransition(): {\n direction: RouterDirection;\n animation: NavDirection | undefined;\n animationBuilder: AnimationBuilder | undefined;\n } {\n let direction: RouterDirection = 'root';\n let animation: NavDirection | undefined;\n const animationBuilder = this.animationBuilder;\n\n if (this.direction === 'auto') {\n direction = this.guessDirection;\n animation = this.guessAnimation;\n } else {\n animation = this.animated;\n direction = this.direction;\n }\n this.direction = DEFAULT_DIRECTION;\n this.animated = DEFAULT_ANIMATED;\n this.animationBuilder = undefined;\n\n return {\n direction,\n animation,\n animationBuilder,\n };\n }\n\n private navigate(url: string | UrlTree | any[], options: NavigationOptions) {\n if (Array.isArray(url)) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return this.router!.navigate(url, options);\n } else {\n /**\n * navigateByUrl ignores any properties that\n * would change the url, so things like queryParams\n * would be ignored unless we create a url tree\n * More Info: https://github.com/angular/angular/issues/18798\n */\n const urlTree = this.serializer.parse(url.toString());\n\n if (options.queryParams !== undefined) {\n urlTree.queryParams = { ...options.queryParams };\n }\n\n if (options.fragment !== undefined) {\n urlTree.fragment = options.fragment;\n }\n\n /**\n * `navigateByUrl` will still apply `NavigationExtras` properties\n * that do not modify the url, such as `replaceUrl` which is why\n * `options` is passed in here.\n */\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return this.router!.navigateByUrl(urlTree, options);\n }\n }\n}\n\nconst getAnimation = (\n direction: RouterDirection,\n animated: boolean | undefined,\n animationDirection: 'forward' | 'back' | undefined\n): NavDirection | undefined => {\n if (animated === false) {\n return undefined;\n }\n if (animationDirection !== undefined) {\n return animationDirection;\n }\n if (direction === 'forward' || direction === 'back') {\n return direction;\n } else if (direction === 'root' && animated === true) {\n return 'forward';\n }\n return undefined;\n};\n\nconst DEFAULT_DIRECTION = 'auto';\nconst DEFAULT_ANIMATED = undefined;\n","import { Injectable, InjectionToken } from '@angular/core';\nimport type { Config as CoreConfig, IonicConfig } from '@ionic/core/components';\n\nimport { IonicWindow } from '../types/interfaces';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class Config {\n get(key: keyof IonicConfig, fallback?: any): any {\n const c = getConfig();\n if (c) {\n return c.get(key, fallback);\n }\n return null;\n }\n\n getBoolean(key: keyof IonicConfig, fallback?: boolean): boolean {\n const c = getConfig();\n if (c) {\n return c.getBoolean(key, fallback);\n }\n return false;\n }\n\n getNumber(key: keyof IonicConfig, fallback?: number): number {\n const c = getConfig();\n if (c) {\n return c.getNumber(key, fallback);\n }\n return 0;\n }\n}\n\nexport const ConfigToken = new InjectionToken<any>('USERCONFIG');\n\nconst getConfig = (): CoreConfig | null => {\n if (typeof (window as any) !== 'undefined') {\n const Ionic = (window as any as IonicWindow).Ionic;\n if (Ionic?.config) {\n return Ionic.config;\n }\n }\n return null;\n};\n","/**\n * @description\n * NavParams are an object that exists on a page and can contain data for that particular view.\n * Similar to how data was pass to a view in V1 with `$stateParams`, NavParams offer a much more flexible\n * option with a simple `get` method.\n *\n * @usage\n * ```ts\n * import { NavParams } from '@ionic/angular';\n *\n * export class MyClass{\n *\n * constructor(navParams: NavParams){\n * // userParams is an object we have in our nav-parameters\n * navParams.get('userParams');\n * }\n *\n * }\n * ```\n */\nexport class NavParams {\n constructor(public data: { [key: string]: any } = {}) {\n console.warn(\n `[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.`\n );\n }\n\n /**\n * Get the value of a nav-parameter for the current view\n *\n * ```ts\n * import { NavParams } from 'ionic-angular';\n *\n * export class MyClass{\n * constructor(public navParams: NavParams){\n * // userParams is an object we have in our nav-parameters\n * this.navParams.get('userParams');\n * }\n * }\n * ```\n *\n * @param param Which param you want to look up\n */\n get<T = any>(param: string): T {\n return this.data[param];\n }\n}\n","import {\n ApplicationRef,\n NgZone,\n Injectable,\n Injector,\n EnvironmentInjector,\n inject,\n createComponent,\n InjectionToken,\n ComponentRef,\n} from '@angular/core';\nimport {\n FrameworkDelegate,\n LIFECYCLE_DID_ENTER,\n LIFECYCLE_DID_LEAVE,\n LIFECYCLE_WILL_ENTER,\n LIFECYCLE_WILL_LEAVE,\n LIFECYCLE_WILL_UNLOAD,\n} from '@ionic/core/components';\n\nimport { NavParams } from '../directives/navigation/nav-params';\n\nimport { ConfigToken } from './config';\n\n// TODO(FW-2827): types\n\n@Injectable()\nexport class AngularDelegate {\n private zone = inject(NgZone);\n private applicationRef = inject(ApplicationRef);\n private config = inject(ConfigToken);\n\n create(\n environmentInjector: EnvironmentInjector,\n injector: Injector,\n elementReferenceKey?: string\n ): AngularFrameworkDelegate {\n return new AngularFrameworkDelegate(\n environmentInjector,\n injector,\n this.applicationRef,\n this.zone,\n elementReferenceKey,\n this.config.useSetInputAPI ?? false\n );\n }\n}\n\nexport class AngularFrameworkDelegate implements FrameworkDelegate {\n private elRefMap = new WeakMap<HTMLElement, ComponentRef<any>>();\n private elEventsMap = new WeakMap<HTMLElement, () => void>();\n\n constructor(\n private environmentInjector: EnvironmentInjector,\n private injector: Injector,\n private applicationRef: ApplicationRef,\n private zone: NgZone,\n private elementReferenceKey?: string,\n private enableSignalsSupport?: boolean\n ) {}\n\n attachViewToDom(container: any, component: any, params?: any, cssClasses?: string[]): Promise<any> {\n return this.zone.run(() => {\n return new Promise((resolve) => {\n const componentProps = {\n ...params,\n };\n\n /**\n * Ionic Angular passes a reference to a modal\n * or popover that can be accessed using a\n * variable in the overlay component. If\n * elementReferenceKey is defined, then we should\n * pass a reference to the component using\n * elementReferenceKey as the key.\n */\n if (this.elementReferenceKey !== undefined) {\n componentProps[this.elementReferenceKey] = container;\n }\n\n const el = attachView(\n this.zone,\n this.environmentInjector,\n this.injector,\n this.applicationRef,\n this.elRefMap,\n this.elEventsMap,\n container,\n component,\n componentProps,\n cssClasses,\n this.elementReferenceKey,\n this.enableSignalsSupport\n );\n resolve(el);\n });\n });\n }\n\n removeViewFromDom(_container: any, component: any): Promise<void> {\n return this.zone.run(() => {\n return new Promise((resolve) => {\n const componentRef = this.elRefMap.get(component);\n if (componentRef) {\n componentRef.destroy();\n this.elRefMap.delete(component);\n const unbindEvents = this.elEventsMap.get(component);\n if (unbindEvents) {\n unbindEvents();\n this.elEventsMap.delete(component);\n }\n }\n resolve();\n });\n });\n }\n}\n\nexport const attachView = (\n zone: NgZone,\n environmentInjector: EnvironmentInjector,\n injector: Injector,\n applicationRef: ApplicationRef,\n elRefMap: WeakMap<HTMLElement, ComponentRef<any>>,\n elEventsMap: WeakMap<HTMLElement, () => void>,\n container: any,\n component: any,\n params: any,\n cssClasses: string[] | undefined,\n elementReferenceKey: string | undefined,\n enableSignalsSupport: boolean | undefined\n): any => {\n /**\n * Wraps the injector with a custom injector that\n * provides NavParams to the component.\n *\n * NavParams is a legacy feature from Ionic v3 that allows\n * Angular developers to provide data to a component\n * and access it by providing NavParams as a dependency\n * in the constructor.\n *\n * The modern approach is to access the data directly\n * from the component's class instance.\n */\n const childInjector = Injector.create({\n providers: getProviders(params),\n parent: injector,\n });\n\n const componentRef = createComponent<any>(component, {\n environmentInjector,\n elementInjector: childInjector,\n });\n\n const instance = componentRef.instance;\n const hostElement = componentRef.location.nativeElement;\n\n if (params) {\n /**\n * For modals and popovers, a reference to the component is\n * added to `params` during the call to attachViewToDom. If\n * a reference using this name is already set, this means\n * the app is trying to use the name as a component prop,\n * which will cause collisions.\n */\n if (elementReferenceKey && instance[elementReferenceKey] !== undefined) {\n console.error(\n `[Ionic Error]: ${elementReferenceKey} is a reserved property when using ${container.tagName.toLowerCase()}. Rename or remove the \"${elementReferenceKey}\" property from ${\n component.name\n }.`\n );\n }\n\n /**\n * Angular 14.1 added support for setInput\n * so we need to fall back to Object.assign\n * for Angular 14.0.\n */\n if (enableSignalsSupport === true && componentRef.setInput !== undefined) {\n const { modal, popover, ...otherParams } = params;\n /**\n * Any key/value pairs set in componentProps\n * must be set as inputs on the component instance.\n */\n for (const key in otherParams) {\n componentRef.setInput(key, otherParams[key]);\n }\n\n /**\n * Using setInput will cause an error when\n * setting modal/popover on a component that\n * does not define them as an input. For backwards\n * compatibility purposes we fall back to using\n * Object.assign for these properties.\n */\n if (modal !== undefined) {\n Object.assign(instance, { modal });\n }\n\n if (popover !== undefined) {\n Object.assign(instance, { popover });\n }\n } else {\n Object.assign(instance, params);\n }\n }\n if (cssClasses) {\n for (const cssClass of cssClasses) {\n hostElement.classList.add(cssClass);\n }\n }\n const unbindEvents = bindLifecycleEvents(zone, instance, hostElement);\n container.appendChild(hostElement);\n\n applicationRef.attachView(componentRef.hostView);\n\n elRefMap.set(hostElement, componentRef);\n elEventsMap.set(hostElement, unbindEvents);\n return hostElement;\n};\n\nconst LIFECYCLES = [\n LIFECYCLE_WILL_ENTER,\n LIFECYCLE_DID_ENTER,\n LIFECYCLE_WILL_LEAVE,\n LIFECYCLE_DID_LEAVE,\n LIFECYCLE_WILL_UNLOAD,\n];\n\nexport const bindLifecycleEvents = (zone: NgZone, instance: any, element: HTMLElement): (() => void) => {\n return zone.run(() => {\n const unregisters = LIFECYCLES.filter((eventName) => typeof instance[eventName] === 'function').map((eventName) => {\n const handler = (ev: any) => instance[eventName](ev.detail);\n element.addEventListener(eventName, handler);\n return () => element.removeEventListener(eventName, handler);\n });\n return () => unregisters.forEach((fn) => fn());\n });\n};\n\nconst NavParamsToken = new InjectionToken<any>('NavParamsToken');\n\nconst getProviders = (params: { [key: string]: any }) => {\n return [\n {\n provide: NavParamsToken,\n useValue: params,\n },\n {\n provide: NavParams,\n useFactory: provideNavParamsInjectable,\n deps: [NavParamsToken],\n },\n ];\n};\n\nconst provideNavParamsInjectable = (params: { [key: string]: any }) => {\n return new NavParams(params);\n};\n","// TODO: Is there a way we can grab this from angular-component-lib instead?\n\n/* eslint-disable */\n/* tslint:disable */\nimport { fromEvent } from 'rxjs';\n\nexport const proxyInputs = (Cmp: any, inputs: string[]) => {\n const Prototype = Cmp.prototype;\n inputs.forEach((item) => {\n Object.defineProperty(Prototype, item, {\n get() {\n return this.el[item];\n },\n set(val: any) {\n this.z.runOutsideAngular(() => (this.el[item] = val));\n },\n });\n });\n};\n\nexport const proxyMethods = (Cmp: any, methods: string[]) => {\n const Prototype = Cmp.prototype;\n methods.forEach((methodName) => {\n Prototype[methodName] = function () {\n const args = arguments;\n return this.z.runOutsideAngular(() => this.el[methodName].apply(this.el, args));\n };\n });\n};\n\nexport const proxyOutputs = (instance: any, el: any, events: string[]) => {\n events.forEach((eventName) => (instance[eventName] = fromEvent(el, eventName)));\n};\n\n// tslint:disable-next-line: only-arrow-functions\nexport function ProxyCmp(opts: { defineCustomElementFn?: () => void; inputs?: any; methods?: any }) {\n const decorator = function (cls: any) {\n const { defineCustomElementFn, inputs, methods } = opts;\n\n if (defineCustomElementFn !== undefined) {\n defineCustomElementFn();\n }\n\n if (inputs) {\n proxyInputs(cls, inputs);\n }\n if (methods) {\n proxyMethods(cls, methods);\n }\n return cls;\n };\n return decorator;\n}\n","import {\n ChangeDetectorRef,\n ContentChild,\n Directive,\n ElementRef,\n EventEmitter,\n NgZone,\n TemplateRef,\n} from '@angular/core';\nimport type { Components } from '@ionic/core/components';\n\nimport { ProxyCmp, proxyOutputs } from '../utils/proxy';\n\nexport declare interface IonPopover extends Components.IonPopover {\n /**\n * Emitted after the popover has presented.\n */\n ionPopoverDidPresent: EventEmitter<CustomEvent>;\n /**\n * Emitted before the popover has presented.\n */\n ionPopoverWillPresent: EventEmitter<CustomEvent>;\n /**\n * Emitted after the popover has dismissed.\n */\n ionPopoverWillDismiss: EventEmitter<CustomEvent>;\n /**\n * Emitted after the popover has dismissed.\n */\n ionPopoverDidDismiss: EventEmitter<CustomEvent>;\n /**\n * Emitted after the popover has presented. Shorthand for ionPopoverDidPresent.\n */\n didPresent: EventEmitter<CustomEvent>;\n /**\n * Emitted before the popover has presented. Shorthand for ionPopoverWillPresent.\n */\n willPresent: EventEmitter<CustomEvent>;\n /**\n * Emitted after the popover has presented. Shorthand for ionPopoverWillDismiss.\n */\n willDismiss: EventEmitter<CustomEvent>;\n /**\n * Emitted after the popover has dismissed. Shorthand for ionPopoverDidDismiss.\n */\n didDismiss: EventEmitter<CustomEvent>;\n}\n\nconst POPOVER_INPUTS = [\n 'alignment',\n 'animated',\n 'arrow',\n 'keepContentsMounted',\n 'backdropDismiss',\n 'cssClass',\n 'dismissOnSelect',\n 'enterAnimation',\n 'event',\n 'focusTrap',\n 'isOpen',\n 'keyboardClose',\n 'leaveAnimation',\n 'mode',\n 'showBackdrop',\n 'translucent',\n 'trigger',\n 'triggerAction',\n 'reference',\n 'size',\n 'side',\n];\n\nconst POPOVER_METHODS = ['present', 'dismiss', 'onDidDismiss', 'onWillDismiss'];\n\n@ProxyCmp({\n inputs: POPOVER_INPUTS,\n methods: POPOVER_METHODS,\n})\n/**\n * @Component extends from @Directive\n * so by defining the inputs here we\n * do not need to re-define them for the\n * lazy loaded popover.\n */\n@Directive({\n selector: 'ion-popover',\n // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property\n inputs: POPOVER_INPUTS,\n})\n\n// eslint-disable-next-line @angular-eslint/directive-class-suffix\nexport class IonPopover {\n // TODO(FW-2827): type\n @ContentChild(TemplateRef, { static: false }) template: TemplateRef<any>;\n\n isCmpOpen = false;\n\n protected el: HTMLElement;\n\n constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {\n this.el = r.nativeElement;\n\n this.el.addEventListener('ionMount', () => {\n this.isCmpOpen = true;\n c.detectChanges();\n });\n this.el.addEventListener('didDismiss', () => {\n this.isCmpOpen = false;\n c.detectChanges();\n });\n proxyOutputs(this, this.el, [\n 'ionPopoverDidPresent',\n 'ionPopoverWillPresent',\n 'ionPopoverWillDismiss',\n 'ionPopoverDidDismiss',\n 'didPresent',\n 'willPresent',\n 'willDismiss',\n 'didDismiss',\n ]);\n }\n}\n","import {\n ChangeDetectorRef,\n ContentChild,\n Directive,\n ElementRef,\n EventEmitter,\n NgZone,\n TemplateRef,\n} from '@angular/core';\nimport type { Components, ModalBreakpointChangeEventDetail } from '@ionic/core/components';\n\nimport { ProxyCmp, proxyOutputs } from '../utils/proxy';\n\nexport declare interface IonModal extends Components.IonModal {\n /**\n * Emitted after the modal has presented.\n **/\n ionModalDidPresent: EventEmitter<CustomEvent>;\n /**\n * Emitted before the modal has presented.\n */\n ionModalWillPresent: EventEmitter<CustomEvent>;\n /**\n * Emitted before the modal has dismissed.\n */\n ionModalWillDismiss: EventEmitter<CustomEvent>;\n /**\n * Emitted after the modal has dismissed.\n */\n ionModalDidDismiss: EventEmitter<CustomEvent>;\n /**\n * Emitted after the modal breakpoint has changed.\n */\n ionBreakpointDidChange: EventEmitter<CustomEvent<ModalBreakpointChangeEventDetail>>;\n /**\n * Emitted after the modal has presented. Shorthand for ionModalDidPresent.\n */\n didPresent: EventEmitter<CustomEvent>;\n /**\n * Emitted before the modal has presented. Shorthand for ionModalWillPresent.\n */\n willPresent: EventEmitter<CustomEvent>;\n /**\n * Emitted before the modal has dismissed. Shorthand for ionModalWillDismiss.\n */\n willDismiss: EventEmitter<CustomEvent>;\n /**\n * Emitted after the modal has dismissed. Shorthand for ionModalDidDismiss.\n */\n didDismiss: EventEmitter<CustomEvent>;\n}\n\nconst MODAL_INPUTS = [\n 'animated',\n 'keepContentsMounted',\n 'backdropBreakpoint',\n 'backdropDismiss',\n 'breakpoints',\n 'canDismiss',\n 'cssClass',\n 'enterAnimation',\n 'expandToScroll',\n 'event',\n 'focusTrap',\n 'handle',\n 'handleBehavior',\n 'initialBreakpoint',\n 'isOpen',\n 'keyboardClose',\n 'leaveAnimation',\n 'mode',\n 'presentingElement',\n 'showBackdrop',\n 'translucent',\n 'trigger',\n];\n\nconst MODAL_METHODS = [\n 'present',\n 'dismiss',\n 'onDidDismiss',\n 'onWillDismiss',\n 'setCurrentBreakpoint',\n 'getCurrentBreakpoint',\n];\n\n@ProxyCmp({\n inputs: MODAL_INPUTS,\n methods: MODAL_METHODS,\n})\n/**\n * @Component extends from @Directive\n * so by defining the inputs here we\n * do not need to re-define them for the\n * lazy loaded popover.\n */\n@Directive({\n selector: 'ion-modal',\n // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property\n inputs: MODAL_INPUTS,\n})\n\n// eslint-disable-next-line @angular-eslint/directive-class-suffix\nexport class IonModal {\n // TODO(FW-2827): type\n @ContentChild(TemplateRef, { static: false }) template: TemplateRef<any>;\n\n isCmpOpen = false;\n\n protected el: HTMLElement;\n\n constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {\n this.el = r.nativeElement;\n\n this.el.addEventListener('ionMount', () => {\n this.isCmpOpen = true;\n c.detectChanges();\n });\n this.el.addEventListener('didDismiss', () => {\n this.isCmpOpen = false;\n c.detectChanges();\n });\n proxyOutputs(this, this.el, [\n 'ionModalDidPresent',\n 'ionModalWillPresent',\n 'ionModalWillDismiss',\n 'ionModalDidDismiss',\n 'ionBreakpointDidChange',\n 'didPresent',\n 'willPresent',\n 'willDismiss',\n 'didDismiss',\n ]);\n }\n}\n","import { ComponentRef } from '@angular/core';\nimport { ActivatedRoute, NavigationExtras, Router } from '@angular/router';\nimport type { AnimationBuilder, NavDirection, RouterDirection } from '@ionic/core/components';\n\nexport const insertView = (views: RouteView[], view: RouteView, direction: RouterDirection): RouteView[] => {\n if (direction === 'root') {\n return setRoot(views, view);\n } else if (direction === 'forward') {\n return setForward(views, view);\n } else {\n return setBack(views, view);\n }\n};\n\nconst setRoot = (views: RouteView[], view: RouteView) => {\n views = views.filter((v) => v.stackId !== view.stackId);\n views.push(view);\n return views;\n};\n\nconst setForward = (views: RouteView[], view: RouteView) => {\n const index = views.indexOf(view);\n if (index >= 0) {\n views = views.filter((v) => v.stackId !== view.stackId || v.id <= view.id);\n } else {\n views.push(view);\n }\n return views;\n};\n\nconst setBack = (views: RouteView[], view: RouteView) => {\n const index = views.indexOf(view);\n if (index >= 0) {\n return views.filter((v) => v.stackId !== view.stackId || v.id <= view.id);\n } else {\n return setRoot(views, view);\n }\n};\n\nexport const getUrl = (router: Router, activatedRoute: ActivatedRoute): string => {\n const urlTree = router.createUrlTree(['.'], { relativeTo: activatedRoute });\n return router.serializeUrl(urlTree);\n};\n\nexport const isTabSwitch = (enteringView: RouteView, leavingView: RouteView | undefined): boolean => {\n if (!leavingView) {\n return true;\n }\n return enteringView.stackId !== leavingView.stackId;\n};\n\nexport const computeStackId = (prefixUrl: string[] | undefined, url: string): string | undefined => {\n if (!prefixUrl) {\n return undefined;\n }\n const segments = toSegments(url);\n for (let i = 0; i < segments.length; i++) {\n if (i >= prefixUrl.length) {\n return segments[i];\n }\n if (segments[i] !== prefixUrl[i]) {\n return undefined;\n }\n }\n return undefined;\n};\n\nexport const toSegments = (path: string): string[] => {\n return path\n .split('/')\n .map((s) => s.trim())\n .filter((s) => s !== '');\n};\n\nexport const destroyView = (view: RouteView | undefined): void => {\n if (view) {\n view.ref.destroy();\n view.unlistenEvents();\n }\n};\n\nexport interface StackWillChangeEvent {\n enteringView: RouteView;\n /**\n * `true` if the event is trigged as a result of a switch\n * between tab navigation stacks.\n */\n tabSwitch: boolean;\n}\n\nexport interface StackDidChangeEvent {\n enteringView: RouteView;\n direction: RouterDirection;\n animation: NavDirection | undefined;\n /**\n * `true` if the event is trigged as a result of a switch\n * between tab navigation stacks.\n */\n tabSwitch: boolean;\n}\n\n// TODO(FW-2827): types\nexport interface RouteView {\n id: number;\n url: string;\n stackId: string | undefined;\n element: HTMLElement;\n ref: ComponentRef<any>;\n savedData?: any;\n savedExtras?: NavigationExtras;\n unlistenEvents: () => void;\n animationBuilder?: AnimationBuilder;\n}\n","import { Location } from '@angular/common';\nimport { ComponentRef, NgZone } from '@angular/core';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport type { AnimationBuilder, RouterDirection } from '@ionic/core/components';\n\nimport { bindLifecycleEvents } from '../../providers/angular-delegate';\nimport { NavController } from '../../providers/nav-controller';\n\nimport {\n RouteView,\n StackDidChangeEvent,\n computeStackId,\n destroyView,\n getUrl,\n insertView,\n isTabSwitch,\n toSegments,\n} from './stack-utils';\n\n// TODO(FW-2827): types\n\nexport class StackController {\n private views: RouteView[] = [];\n private runningTask?: Promise<any>;\n private skipTransition = false;\n private tabsPrefix: string[] | undefined;\n private activeView: RouteView | undefined;\n private nextId = 0;\n\n constructor(\n tabsPrefix: string | undefined,\n private containerEl: HTMLIonRouterOutletElement,\n private router: Router,\n private navCtrl: NavController,\n private zone: NgZone,\n private location: Location\n ) {\n this.tabsPrefix = tabsPrefix !== undefined ? toSegments(tabsPrefix) : undefined;\n }\n\n createView(ref: ComponentRef<any>, activatedRoute: ActivatedRoute): RouteView {\n const url = getUrl(this.router, activatedRoute);\n const element = ref?.location?.nativeElement as HTMLElement;\n const unlistenEvents = bindLifecycleEvents(this.zone, ref.instance, element);\n return {\n id: this.nextId++,\n stackId: computeStackId(this.tabsPrefix, url),\n unlistenEvents,\n element,\n ref,\n url,\n };\n }\n\n getExistingView(activatedRoute: ActivatedRoute): RouteView | undefined {\n const activatedUrlKey = getUrl(this.router, activatedRoute);\n const view = this.views.find((vw) => vw.url === activatedUrlKey);\n if (view) {\n view.ref.changeDetectorRef.reattach();\n }\n return view;\n }\n\n setActive(enteringView: RouteView): Promise<StackDidChangeEvent> {\n const consumeResult = this.navCtrl.consumeTransition();\n let { direction, animation, animationBuilder } = consumeResult;\n const leavingView = this.activeView;\n const tabSwitch = isTabSwitch(enteringView, leavingView);\n if (tabSwitch) {\n direction = 'back';\n animation = undefined;\n }\n\n const viewsSnapshot = this.views.slice();\n\n let currentNavigation;\n\n const router = this.router as any;\n\n // Angular >= 7.2.0\n if (router.getCurrentNavigation) {\n currentNavigation = router.getCurrentNavigation();\n\n // Angular < 7.2.0\n } else if (router.navigations?.value) {\n currentNavigation = router.navigations.value;\n }\n\n /**\n * If the navigation action\n * sets `replaceUrl: true`\n * then we need to make sure\n * we remove the last item\n * from our views stack\n */\n if (currentNavigation?.extras?.replaceUrl) {\n if (this.views.length > 0) {\n this.view