/*!
 * (C) Ionic http://ionicframework.com - MIT License
 */
'use strict';

var index = require('./index-DjlJwy91.js');

const win = typeof window !== 'undefined' ? window : undefined;
const doc = typeof document !== 'undefined' ? document : undefined;

/**
 * CloseWatcher is a newer API that lets
 * use detect the hardware back button event
 * in a web browser: https://caniuse.com/?search=closewatcher
 * However, not every browser supports it yet.
 *
 * This needs to be a function so that we can
 * check the config once it has been set.
 * Otherwise, this code would be evaluated the
 * moment this file is evaluated which could be
 * before the config is set.
 */
const shouldUseCloseWatcher = () => index.config.get('experimentalCloseWatcher', false) && win !== undefined && 'CloseWatcher' in win;
/**
 * When hardwareBackButton: false in config,
 * we need to make sure we also block the default
 * webview behavior. If we don't then it will be
 * possible for users to navigate backward while
 * an overlay is still open. Additionally, it will
 * give the appearance that the hardwareBackButton
 * config is not working as the page transition
 * will still happen.
 */
const blockHardwareBackButton = () => {
    document.addEventListener('backbutton', () => { }); // eslint-disable-line
};
const startHardwareBackButton = () => {
    const doc = document;
    let busy = false;
    const backButtonCallback = () => {
        if (busy) {
            return;
        }
        let index$1 = 0;
        let handlers = [];
        const ev = new CustomEvent('ionBackButton', {
            bubbles: false,
            detail: {
                register(priority, handler) {
                    handlers.push({ priority, handler, id: index$1++ });
                },
            },
        });
        doc.dispatchEvent(ev);
        /**
         * If no handlers have been registered, fall back to the default
         * behavior of navigating back in history. This ensures the hardware
         * back button works even when no router or custom handler is present.
         */
        if (handlers.length === 0) {
            handlers.push({
                priority: FALLBACK_BACK_BUTTON_PRIORITY,
                handler: () => {
                    win === null || win === void 0 ? void 0 : win.history.back();
                },
                id: index$1++,
            });
        }
        const executeAction = async (handlerRegister) => {
            try {
                if (handlerRegister === null || handlerRegister === void 0 ? void 0 : handlerRegister.handler) {
                    const result = handlerRegister.handler(processHandlers);
                    if (result != null) {
                        await result;
                    }
                }
            }
            catch (e) {
                index.printIonError('[ion-app] - Exception in startHardwareBackButton:', e);
            }
        };
        const processHandlers = () => {
            if (handlers.length > 0) {
                let selectedHandler = {
                    priority: Number.MIN_SAFE_INTEGER,
                    handler: () => undefined,
                    id: -1,
                };
                handlers.forEach((handler) => {
                    if (handler.priority >= selectedHandler.priority) {
                        selectedHandler = handler;
                    }
                });
                busy = true;
                handlers = handlers.filter((handler) => handler.id !== selectedHandler.id);
                executeAction(selectedHandler).then(() => (busy = false));
            }
        };
        processHandlers();
    };
    /**
     * If the CloseWatcher is defined then
     * we don't want to also listen for the native
     * backbutton event otherwise we may get duplicate
     * events firing.
     */
    if (shouldUseCloseWatcher()) {
        let watcher;
        const configureWatcher = () => {
            watcher === null || watcher === void 0 ? void 0 : watcher.destroy();
            watcher = new win.CloseWatcher();
            /**
             * Once a close request happens
             * the watcher gets destroyed.
             * As a result, we need to re-configure
             * the watcher so we can respond to other
             * close requests.
             */
            watcher.onclose = () => {
                backButtonCallback();
                configureWatcher();
            };
        };
        configureWatcher();
    }
    else {
        doc.addEventListener('backbutton', backButtonCallback);
    }
};
const OVERLAY_BACK_BUTTON_PRIORITY = 100;
const MENU_BACK_BUTTON_PRIORITY = 99; // 1 less than overlay priority since menu is displayed behind overlays
const FALLBACK_BACK_BUTTON_PRIORITY = -1; // Fallback when no other handlers are registered

var hardwareBackButton = /*#__PURE__*/Object.freeze({
    __proto__: null,
    MENU_BACK_BUTTON_PRIORITY: MENU_BACK_BUTTON_PRIORITY,
    OVERLAY_BACK_BUTTON_PRIORITY: OVERLAY_BACK_BUTTON_PRIORITY,
    blockHardwareBackButton: blockHardwareBackButton,
    shouldUseCloseWatcher: shouldUseCloseWatcher,
    startHardwareBackButton: startHardwareBackButton
});

const getPlatforms = (win) => setupPlatforms(win);
const isPlatform = (winOrPlatform, platform) => {
    if (typeof winOrPlatform === 'string') {
        platform = winOrPlatform;
        winOrPlatform = undefined;
    }
    return getPlatforms(winOrPlatform).includes(platform);
};
const setupPlatforms = (win = window) => {
    if (typeof win === 'undefined') {
        return [];
    }
    win.Ionic = win.Ionic || {};
    let platforms = win.Ionic.platforms;
    if (platforms == null) {
        platforms = win.Ionic.platforms = detectPlatforms(win);
        platforms.forEach((p) => win.document.documentElement.classList.add(`plt-${p}`));
    }
    return platforms;
};
const detectPlatforms = (win) => {
    const customPlatformMethods = index.config.get('platform');
    return Object.keys(PLATFORMS_MAP).filter((p) => {
        const customMethod = customPlatformMethods === null || customPlatformMethods === void 0 ? void 0 : customPlatformMethods[p];
        return typeof customMethod === 'function' ? customMethod(win) : PLATFORMS_MAP[p](win);
    });
};
const isMobileWeb = (win) => isMobile(win) && !isHybrid(win);
const isIpad = (win) => {
    // iOS 12 and below
    if (testUserAgent(win, /iPad/i)) {
        return true;
    }
    // iOS 13+
    if (testUserAgent(win, /Macintosh/i) && isMobile(win)) {
        return true;
    }
    return false;
};
const isIphone = (win) => testUserAgent(win, /iPhone/i);
const isIOS = (win) => testUserAgent(win, /iPhone|iPod/i) || isIpad(win);
const isAndroid = (win) => testUserAgent(win, /android|sink/i);
const isAndroidTablet = (win) => {
    return isAndroid(win) && !testUserAgent(win, /mobile/i);
};
const isPhablet = (win) => {
    const width = win.innerWidth;
    const height = win.innerHeight;
    const smallest = Math.min(width, height);
    const largest = Math.max(width, height);
    return smallest > 390 && smallest < 520 && largest > 620 && largest < 800;
};
const isTablet = (win) => {
    const width = win.innerWidth;
    const height = win.innerHeight;
    const smallest = Math.min(width, height);
    const largest = Math.max(width, height);
    return isIpad(win) || isAndroidTablet(win) || (smallest > 460 && smallest < 820 && largest > 780 && largest < 1400);
};
const isMobile = (win) => matchMedia(win, '(any-pointer:coarse)');
const isDesktop = (win) => !isMobile(win);
const isHybrid = (win) => isCordova(win) || isCapacitorNative(win);
const isCordova = (win) => !!(win['cordova'] || win['phonegap'] || win['PhoneGap']);
const isCapacitorNative = (win) => {
    const capacitor = win['Capacitor'];
    // TODO(ROU-11693): Remove when we no longer support Capacitor 2, which does not have isNativePlatform
    return !!((capacitor === null || capacitor === void 0 ? void 0 : capacitor.isNative) || ((capacitor === null || capacitor === void 0 ? void 0 : capacitor.isNativePlatform) && !!capacitor.isNativePlatform()));
};
const isElectron = (win) => testUserAgent(win, /electron/i);
const isPWA = (win) => { var _a; return !!(((_a = win.matchMedia) === null || _a === void 0 ? void 0 : _a.call(win, '(display-mode: standalone)').matches) || win.navigator.standalone); };
const testUserAgent = (win, expr) => expr.test(win.navigator.userAgent);
const matchMedia = (win, query) => { var _a; return (_a = win.matchMedia) === null || _a === void 0 ? void 0 : _a.call(win, query).matches; };
const PLATFORMS_MAP = {
    ipad: isIpad,
    iphone: isIphone,
    ios: isIOS,
    android: isAndroid,
    phablet: isPhablet,
    tablet: isTablet,
    cordova: isCordova,
    capacitor: isCapacitorNative,
    electron: isElectron,
    pwa: isPWA,
    mobile: isMobile,
    mobileweb: isMobileWeb,
    desktop: isDesktop,
    hybrid: isHybrid,
};

let defaultMode;
let defaultTheme = 'md';
/**
 * Prints a warning message to the developer to inform them of
 * an invalid configuration of mode and theme.
 * @param mode The invalid mode configuration.
 * @param theme The invalid theme configuration.
 */
const printInvalidModeWarning = (mode, theme, ref) => {
    index.printIonWarning(`Invalid mode and theme combination provided: mode: ${mode}, theme: ${theme}. Fallback mode ${getDefaultModeForTheme(theme)} will be used.`, ref);
};
/**
 * Validates if a mode is accepted for a theme configuration.
 * @param mode The mode to validate.
 * @param theme The theme the mode is being used with.
 * @returns `true` if the mode is valid for the theme, `false` if invalid.
 */
const isModeValidForTheme = (mode, theme) => {
    if (mode === 'md') {
        return theme === 'md' || theme === 'ionic';
    }
    else if (mode === 'ios') {
        return theme === 'ios' || theme === 'ionic';
    }
    return false;
};
/**
 * Returns the default mode for a specified theme.
 * @param theme The theme to return a default mode for.
 * @returns The default mode, either `ios` or `md`.
 */
const getDefaultModeForTheme = (theme) => {
    if (theme === 'ios') {
        return 'ios';
    }
    return 'md';
};
/**
 * Returns the default theme for a specified mode.
 * @param mode The mode to return a default theme for.
 * @returns The default theme.
 */
const getDefaultThemeForMode = (mode) => {
    if (mode === 'ios') {
        return 'ios';
    }
    return 'md';
};
const isModeSupported = (elmMode) => ['ios', 'md'].includes(elmMode);
const isThemeSupported = (theme) => ['ios', 'md', 'ionic'].includes(theme);
const isIonicElement = (elm) => { var _a; return (_a = elm.tagName) === null || _a === void 0 ? void 0 : _a.startsWith('ION-'); };
/**
 * Returns the mode value of the element reference or the closest
 * parent with a valid mode.
 * @param ref The element reference to look up the mode for.
 * @param theme Optionally can provide the theme to avoid an additional look-up.
 * @returns The mode value for the element reference.
 */
const getIonMode = (ref, theme = getIonTheme(ref)) => {
    var _a;
    if ((ref === null || ref === void 0 ? void 0 : ref.mode) && isModeValidForTheme(ref === null || ref === void 0 ? void 0 : ref.mode, theme)) {
        /**
         * If the reference already has a mode configuration,
         * use it instead of performing a look-up.
         */
        return ref.mode;
    }
    else {
        const el = index.getElement(ref);
        const mode = ((_a = el.closest('[mode]')) === null || _a === void 0 ? void 0 : _a.getAttribute('mode')) || defaultMode;
        if (isModeValidForTheme(mode, theme)) {
            /**
             * The mode configuration is supported for the configured theme.
             */
            return mode;
        }
        else {
            printInvalidModeWarning(mode, theme, ref);
        }
    }
    return getDefaultModeForTheme(theme);
};
/**
 * Returns the theme value of the element reference or the closest
 * parent with a valid theme.
 *
 * @param ref The element reference to look up the theme for.
 * @returns The theme value for the element reference, defaults to
 * the default theme if it cannot be determined.
 */
const getIonTheme = (ref) => {
    var _a, _b;
    const theme = ref && index.getMode(ref);
    if (theme) {
        return theme;
    }
    /**
     * If the theme cannot be detected, then fallback to using
     * the `mode` attribute to determine the style sheets to use.
     */
    const el = index.getElement(ref);
    const mode = (_a = ref === null || ref === void 0 ? void 0 : ref.mode) !== null && _a !== void 0 ? _a : (_b = el.closest('[mode]')) === null || _b === void 0 ? void 0 : _b.getAttribute('mode');
    if (mode) {
        return getDefaultThemeForMode(mode);
    }
    /**
     * If a mode is not detected, then fallback to using the
     * default theme.
     */
    return defaultTheme;
};
const rIC = (callback) => {
    if ('requestIdleCallback' in window) {
        window.requestIdleCallback(callback);
    }
    else {
        setTimeout(callback, 32);
    }
};
const needInputShims = () => {
    /**
     * iOS always needs input shims
     */
    const needsShimsIOS = isPlatform(window, 'ios') && isPlatform(window, 'mobile');
    if (needsShimsIOS) {
        return true;
    }
    /**
     * Android only needs input shims when running
     * in the browser and only if the browser is using the
     * new Chrome 108+ resize behavior: https://developer.chrome.com/blog/viewport-resize-behavior/
     */
    const isAndroidMobileWeb = isPlatform(window, 'android') && isPlatform(window, 'mobileweb');
    if (isAndroidMobileWeb) {
        return true;
    }
    return false;
};
const initialize = (userConfig = {}) => {
    if (typeof window === 'undefined') {
        return;
    }
    const doc = window.document;
    const win = window;
    const Ionic = (win.Ionic = win.Ionic || {});
    // create the Ionic.config from raw config object (if it exists)
    // and convert Ionic.config into a ConfigApi that has a get() fn
    const configObj = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, index.configFromSession(win)), { persistConfig: false }), Ionic.config), index.configFromURL(win)), userConfig);
    index.config.reset(configObj);
    if (index.config.getBoolean('persistConfig')) {
        index.saveConfig(win, configObj);
    }
    // Setup platforms
    setupPlatforms(win);
    Ionic.config = index.config;
    /**
     * Check if the mode was set as an attribute on <html>
     * which could have been set by the user, or by pre-rendering
     * otherwise get the mode via config settings, and fallback to md.
     */
    Ionic.mode = defaultMode = index.config.get('mode', doc.documentElement.getAttribute('mode') || (isPlatform(win, 'ios') ? 'ios' : 'md'));
    /**
     * Check if the theme was set as an attribute on <html>
     * which could have been set by the user, or by pre-rendering
     * otherwise get the theme via config settings, and fallback to md.
     */
    Ionic.theme = defaultTheme = index.config.get('theme', doc.documentElement.getAttribute('theme') || getDefaultThemeForMode(defaultMode));
    if (!isModeValidForTheme(defaultMode, defaultTheme)) {
        printInvalidModeWarning(defaultMode, defaultTheme, configObj);
        defaultMode = getDefaultModeForTheme(defaultTheme);
    }
    index.config.set('mode', defaultMode);
    doc.documentElement.setAttribute('mode', defaultMode);
    doc.documentElement.classList.add(defaultMode);
    index.config.set('theme', defaultTheme);
    doc.documentElement.setAttribute('theme', defaultTheme);
    doc.documentElement.classList.add(defaultTheme);
    if (index.config.getBoolean('_testing')) {
        index.config.set('animated', false);
    }
    index.setMode((elm) => {
        /**
         * Iterate over all the element nodes, to both validate and
         * set the "mode" that is used for determining the styles to
         * apply to the element.
         *
         * setMode refers to Stencil's internal metadata for "mode",
         * which is used to set the correct styleUrl for the component.
         *
         * If the "theme" attribute or property is set, then use it
         * to determine the style sheets to use.
         *
         * If the "mode" attribute or property is set, then use it
         * to determine the style sheets to use. This is fallback
         * behavior for applications that are not setting the "theme".
         */
        while (elm) {
            const theme = elm.getAttribute('theme');
            if (theme) {
                if (isThemeSupported(theme)) {
                    return theme;
                }
                else if (isIonicElement(elm)) {
                    index.printIonWarning(`Invalid theme: "${theme}". Supported themes include: "ios" or "md".`);
                }
            }
            /**
             * If a theme is not detected, then fallback to using the
             * `mode` attribute to determine the style sheets to use.
             */
            const elmMode = elm.getAttribute('mode');
            if (elmMode) {
                if (isModeSupported(elmMode)) {
                    return elmMode;
                }
                else if (isIonicElement(elm)) {
                    index.printIonWarning(`Invalid mode: "${elmMode}". Ionic modes can be only "ios" or "md"`);
                }
            }
            elm = elm.parentElement;
        }
        return defaultTheme;
    });
    // `IonApp` code
    // ----------------------------------------------
    {
        rIC(async () => {
            const isHybrid = isPlatform(window, 'hybrid');
            if (!index.config.getBoolean('_testing')) {
                Promise.resolve().then(function () { return require('./index-BioRZa_4.js'); }).then((module) => module.startTapClick(index.config));
            }
            if (index.config.getBoolean('statusTap', isHybrid)) {
                Promise.resolve().then(function () { return require('./status-tap-BLw3gi_z.js'); }).then((module) => module.startStatusTap());
            }
            if (index.config.getBoolean('inputShims', needInputShims())) {
                /**
                 * needInputShims() ensures that only iOS and Android
                 * platforms proceed into this block.
                 */
                const platform = isPlatform(window, 'ios') ? 'ios' : 'android';
                Promise.resolve().then(function () { return require('./input-shims-BYRSBwuG.js'); }).then((module) => module.startInputShims(index.config, platform));
            }
            const hardwareBackButtonModule = await Promise.resolve().then(function () { return hardwareBackButton; });
            const supportsHardwareBackButtonEvents = isHybrid || shouldUseCloseWatcher();
            if (index.config.getBoolean('hardwareBackButton', supportsHardwareBackButtonEvents)) {
                hardwareBackButtonModule.startHardwareBackButton();
            }
            else {
                /**
                 * If an app sets hardwareBackButton: false and experimentalCloseWatcher: true
                 * then the close watcher will not be used.
                 */
                if (shouldUseCloseWatcher()) {
                    index.printIonWarning('[ion-app] - experimentalCloseWatcher was set to `true`, but hardwareBackButton was set to `false`. Both config options must be `true` for the Close Watcher API to be used.');
                }
                hardwareBackButtonModule.blockHardwareBackButton();
            }
            if (typeof window !== 'undefined') {
                Promise.resolve().then(function () { return require('./keyboard-DYFGJjuN.js'); }).then((module) => module.startKeyboardAssist(window));
            }
            Promise.resolve().then(function () { return require('./focus-visible-BIj-I3-C.js'); }).then((module) => module.getOrInitFocusVisibleUtility());
        });
    }
};

exports.MENU_BACK_BUTTON_PRIORITY = MENU_BACK_BUTTON_PRIORITY;
exports.OVERLAY_BACK_BUTTON_PRIORITY = OVERLAY_BACK_BUTTON_PRIORITY;
exports.doc = doc;
exports.getIonMode = getIonMode;
exports.getIonTheme = getIonTheme;
exports.getPlatforms = getPlatforms;
exports.initialize = initialize;
exports.isPlatform = isPlatform;
exports.shouldUseCloseWatcher = shouldUseCloseWatcher;
exports.win = win;
