import { doc } from '@utils/browser'; import { focusFirstDescendant, focusLastDescendant, focusableQueryString } from '@utils/focus-trap'; import type { BackButtonEvent } from '@utils/hardware-back-button'; import { shouldUseCloseWatcher } from '@utils/hardware-back-button'; import { config } from '../global/config'; import { getIonMode } from '../global/ionic-global'; import type { ActionSheetOptions, AlertOptions, Animation, AnimationBuilder, FrameworkDelegate, HTMLIonOverlayElement, IonicConfig, LoadingOptions, ModalOptions, OverlayInterface, PickerOptions, PopoverOptions, ToastOptions, } from '../interface'; import { CoreDelegate } from './framework-delegate'; import { BACKDROP_NO_SCROLL } from './gesture/gesture-controller'; import { OVERLAY_BACK_BUTTON_PRIORITY } from './hardware-back-button'; import { addEventListener, componentOnReady, focusVisibleElement, getElementRoot, removeEventListener, } from './helpers'; import { printIonWarning } from './logging'; let lastOverlayIndex = 0; let lastId = 0; export const activeAnimations = new WeakMap(); const createController = (tagName: string) => { return { create(options: Opts): Promise { return createOverlay(tagName, options) as any; }, dismiss(data?: any, role?: string, id?: string) { return dismissOverlay(document, data, role, tagName, id); }, async getTop(): Promise { return getPresentedOverlay(document, tagName) as any; }, }; }; export const alertController = /*@__PURE__*/ createController('ion-alert'); export const actionSheetController = /*@__PURE__*/ createController( 'ion-action-sheet' ); export const loadingController = /*@__PURE__*/ createController('ion-loading'); export const modalController = /*@__PURE__*/ createController('ion-modal'); /** * @deprecated Use the inline ion-picker component instead. */ export const pickerController = /*@__PURE__*/ createController( 'ion-picker-legacy' ); export const popoverController = /*@__PURE__*/ createController('ion-popover'); export const toastController = /*@__PURE__*/ createController('ion-toast'); /** * Prepares the overlay element to be presented. */ export const prepareOverlay = (el: T) => { if (typeof document !== 'undefined') { /** * Adds a single instance of event listeners for application behaviors: * * - Escape Key behavior to dismiss an overlay * - Trapping focus within an overlay * - Back button behavior to dismiss an overlay * * This only occurs when the first overlay is created. */ connectListeners(document); } const overlayIndex = lastOverlayIndex++; /** * overlayIndex is used in the overlay components to set a zIndex. * This ensures that the most recently presented overlay will be * on top. */ el.overlayIndex = overlayIndex; }; /** * Assigns an incrementing id to an overlay element, that does not * already have an id assigned to it. * * Used to track unique instances of an overlay element. */ export const setOverlayId = (el: T) => { if (!el.hasAttribute('id')) { el.id = `ion-overlay-${++lastId}`; } return el.id; }; export const createOverlay = ( tagName: string, opts: object | undefined ): Promise => { // eslint-disable-next-line @typescript-eslint/prefer-optional-chain if (typeof window !== 'undefined' && typeof window.customElements !== 'undefined') { return window.customElements.whenDefined(tagName).then(() => { const element = document.createElement(tagName) as HTMLIonOverlayElement; element.classList.add('overlay-hidden'); /** * Convert the passed in overlay options into props * that get passed down into the new overlay. */ Object.assign(element, { ...opts, hasController: true }); // append the overlay element to the document body getAppRoot(document).appendChild(element); return new Promise((resolve) => componentOnReady(element, resolve)); }); } return Promise.resolve() as any; }; const isOverlayHidden = (overlay: Element) => overlay.classList.contains('overlay-hidden'); /** * Focuses a particular element in an overlay. If the element * doesn't have anything focusable associated with it then * the overlay itself will be focused. * This should be used instead of the focus() method * on most elements because the focusable element * may not be the host element. * * For example, if an ion-button should be focused * then we should actually focus the native