/* @oracle/oraclejet-preact: undefined */
import { jsx, jsxs } from 'preact/jsx-runtime';
import { forwardRef, useRef, useEffect } from 'preact/compat';
import { u as useModal } from './useModal-953082f2.js';
import { L as Layer } from './Layer-446bf21e.js';
import './LayerHost-e6402730.js';
import { multiVariantStyles, baseStyle } from './UNSAFE_Modal/themes/ModalStyles.css.js';
import { a as allTabbableElements, f as focusWithin } from './tabbableUtils-6ceb767f.js';
import { i as isLogicalAncestor } from './popupUtils-58411a98.js';

const ModalBackdrop = forwardRef(({ variant = 'scrim' }, ref) => {
    const variantClasses = multiVariantStyles({
        backdrop: variant === 'transparent' ? 'transparent' : 'scrim'
    });
    return jsx("div", { ref: ref, class: variantClasses });
});

class ModalManager {
    constructor() {
        /**
         * A flag indicating that the ModalManager's focus handler is installed
         */
        this.isFocusHandlerActive = false;
        /**
         * A focus handler to keep focus in the top-most modal.
         */
        this.focusHandler = (event) => {
            const topModal = this.getTopModal();
            const target = event.target;
            // if there is an open modal and valid focus event target...
            if (topModal && target) {
                const modalElement = topModal.modalRef;
                if (modalElement &&
                    !modalElement.contains(target) &&
                    !isLogicalAncestor(modalElement, target) &&
                    !this._parentLayerContainsNode(modalElement, target)) {
                    // Focus is outside of the top modal container, let's try to fix it
                    const tabbableElements = allTabbableElements(modalElement);
                    if (tabbableElements.length > 0) {
                        // re-directing focus to the element inside top modal
                        focusWithin(modalElement);
                        event.preventDefault();
                        event.stopPropagation();
                    }
                }
            }
        };
        this.getTopModal = () => {
            return this.modals.length > 0 ? this.modals[this.modals.length - 1] : null;
        };
        // TODO: replace with a proper API
        /**
         * Checks if the legacy popup service is available
         */
        this.islegacyLayerSupportAvailable = () => {
            return document.getElementById('__oj_zorder_container') !== null;
        };
        this.modals = [];
        this.styleBackup = [];
    }
    /**
     * Registers a modal with ModalManager
     *
     * @param modal
     * @returns
     */
    push(modal) {
        const modalIndex = this._findModalIndex(modal);
        if (modalIndex !== -1) {
            // Do not try to register the same modal multiple times
            return;
        }
        this.modals.push(modal);
        // if this is the first modal in the container, block container scrolling
        if (this.modals.length === 1) {
            const ownerDocument = this._getOwnerDocument(modal.modalRef);
            const scrollContainer = ownerDocument.body;
            const scrollBarWidth = window.innerWidth - document.documentElement.clientWidth;
            if (scrollBarWidth > 1) {
                // in RTL, scrollbar is on the left
                const scrollBarLeft = Math.round(document.documentElement.getBoundingClientRect().left) +
                    document.documentElement.scrollLeft;
                if (scrollBarLeft) {
                    this.styleBackup.push({
                        property: 'padding-left',
                        value: scrollContainer.style.paddingLeft
                    });
                    scrollContainer.style.paddingLeft = `${scrollBarWidth}px`;
                }
                else {
                    this.styleBackup.push({
                        property: 'padding-right',
                        value: scrollContainer.style.paddingRight
                    });
                    scrollContainer.style.paddingRight = `${scrollBarWidth}px`;
                }
            }
            this.styleBackup.push({ property: 'overflow', value: scrollContainer.style.overflow });
            this.styleBackup.push({ property: 'overflow-x', value: scrollContainer.style.overflowX });
            this.styleBackup.push({ property: 'overflow-y', value: scrollContainer.style.overflowY });
            scrollContainer.style.overflow = 'hidden';
        }
        // also install the focus handler when the first modal becomes active
        if (this.modals.length === 1 && !this.islegacyLayerSupportAvailable()) {
            document.addEventListener('focus', this.focusHandler, true);
            this.isFocusHandlerActive = true;
        }
    }
    /**
     * Unegisters a modal from ModalManager
     *
     * @param modal
     * @returns
     */
    pop(modal) {
        const modalIndex = this._findModalIndex(modal);
        if (modalIndex === -1) {
            // modal to be removed not found!'
            return;
        }
        if (modalIndex !== this.modals.length - 1) {
            // trying to remove modal out of order!
            return;
        }
        // remove the focus handler when the last modal is about to be removed
        if (this.isFocusHandlerActive && this.modals.length === 1) {
            document.removeEventListener('focus', this.focusHandler, true);
        }
        this.modals.pop();
        // if this was the last modal in the container, restore its overflow props
        if (this.modals.length === 0) {
            const ownerDocument = this._getOwnerDocument(modal.modalRef);
            const scrollContainer = ownerDocument.body;
            this.styleBackup.forEach(({ property, value }) => {
                scrollContainer.style.setProperty(property, value);
            });
        }
    }
    isTopModal(modal) {
        return this.modals.length > 0 && this.modals[this.modals.length - 1] === modal;
    }
    _findModalIndex(modal) {
        let index = -1;
        this.modals.forEach((elem, idx) => {
            if (elem === modal) {
                index = idx;
                return;
            }
        });
        return index;
    }
    _getOwnerDocument(node) {
        return (node && node.ownerDocument) || document;
    }
    /**
     * Checks of the modal's layer contains the target node
     * @param modalElement modal element
     * @param node target node
     * @returns true, if the modal layer contains the node
     */
    _parentLayerContainsNode(modalElement, node) {
        const parentLayer = modalElement.closest('[data-oj-layer]');
        if (parentLayer && parentLayer.contains(node)) {
            return true;
        }
        return false;
    }
}

// Global ModalManager singleton instance
const globalModalManager = new ModalManager();
/**
 * Returns an array of element's siblings.
 *
 * @param elem
 * @returns
 */
function getSiblings(elem) {
    const siblings = [];
    let sibling = elem.previousSibling;
    while (sibling != null) {
        if (sibling.nodeType === 1) {
            siblings.push(sibling);
        }
        sibling = sibling.previousSibling;
    }
    sibling = elem.nextSibling;
    while (sibling != null) {
        if (sibling.nodeType === 1) {
            siblings.push(sibling);
        }
        sibling = sibling.nextSibling;
    }
    return siblings;
}
/**
 * Sets aria-hidden=true on Modal ancestors and siblings.
 *
 * @param modal
 * @returns An array of modified elements.
 */
function setAriaHiddenOnBackgroundElements(modal) {
    if (!modal) {
        return [];
    }
    const ariaHiddenElements = [];
    let node = modal.parentElement;
    do {
        if (!node) {
            break;
        }
        const siblings = getSiblings(node);
        siblings.forEach((elem) => {
            if (elem.tagName.toLowerCase() !== 'script' && elem.getAttribute('aria-hidden') !== 'true') {
                ariaHiddenElements.push(elem);
                elem.setAttribute('aria-hidden', 'true');
            }
        });
        node = node.parentElement;
    } while (node && node.tagName.toLowerCase() !== 'body');
    return ariaHiddenElements;
}
/**
 * Resets the aria-hidden state on previously aria-hidden elements.
 *
 * @param elements
 */
function resetAriaHiddenOnBackgroundElements(elements) {
    elements.forEach((elem) => {
        elem.removeAttribute('aria-hidden');
    });
}
/**
 * Modal is a low-level component that provides the 'modality' feature with built-in overlay (scrim).
 * It is typically used for building higher-level components (such as 'Dialog' or 'Popup') that need
 * to support modal behavior preventing interaction with the rest of the page while the modal is open.
 */
const Modal = ({ children, isOpen, onBackdropClick, backdropVariant }) => {
    // get the global ModalContext instance
    const modalManager = globalModalManager;
    const { backdropRef } = useModal({ isOpen, onBackdropClick });
    // main modal element ref
    const modalRef = useRef(null);
    const ariaHiddenCache = useRef(null);
    // modal ref cache, required to lookup the item in modal manager when the actual ref is unmounted
    const modal = useRef({});
    // populates and returns the modal ref cache
    const getModal = () => {
        modal.current.modalRef = modalRef.current;
        return modal.current;
    };
    useEffect(() => {
        if (isOpen && modalRef.current) {
            modalManager?.push(getModal());
        }
        else {
            if (modal.current.modalRef) {
                if (ariaHiddenCache.current !== null) {
                    resetAriaHiddenOnBackgroundElements(ariaHiddenCache.current);
                    ariaHiddenCache.current = null;
                }
                modalManager?.pop(getModal());
            }
        }
    }, [isOpen, modalManager]);
    useEffect(() => {
        const modalRef = modal.current.modalRef;
        return () => {
            if (ariaHiddenCache.current !== null) {
                resetAriaHiddenOnBackgroundElements(ariaHiddenCache.current);
                ariaHiddenCache.current = null;
            }
            if (modalRef) {
                modalManager?.pop(getModal());
            }
        };
    }, [modalManager]);
    const handleFocusIn = () => {
        // on first focusin, set aria-hidden on modal ancestor's siblings and remember the modified elements
        if (modalRef.current && ariaHiddenCache.current === null) {
            ariaHiddenCache.current = setAriaHiddenOnBackgroundElements(modalRef.current);
        }
    };
    return isOpen ? (jsx(Layer, { isModal: true, children: jsxs("div", { ref: modalRef, class: baseStyle, onFocus: handleFocusIn, children: [jsx(ModalBackdrop, { ref: backdropRef, variant: backdropVariant }), children] }) })) : null;
};

export { Modal as M };
//# sourceMappingURL=Modal-24a0bf4b.js.map
