/* @oracle/oraclejet-preact: undefined */
import { jsx } from 'preact/jsx-runtime';
import { useState, useRef, useMemo, useCallback, useEffect } from 'preact/hooks';
import { forwardRef } from 'preact/compat';
import { m as mergeRefs } from './refUtils-b9d8d91a.js';
import { L as Layer } from './Layer-446bf21e.js';
import './LayerHost-e6402730.js';
import { M as Modal } from './Modal-24a0bf4b.js';
import { F as FocusTrap } from './FocusTrap-9e80c124.js';
import { g as getActiveElement, e as focusOnElementOrNearestAncestor, f as focusWithin, b as focusOn, d as getAutofocusFocusables } from './tabbableUtils-6ceb767f.js';
import { n as normalizePosition } from './positionUtils-f26caad9.js';
import { g as getClientHints } from './clientHints-f850225e.js';
import { u as useOutsideClick } from './useOutsideClick-fd9ad806.js';
import { u as useAnimation } from './useAnimation-a5eb7604.js';
import { u as useUser } from './useUser-1cd223a9.js';
import { u as useTestId } from './useTestId-9b504481.js';
import { u as useSwipe } from './useSwipe-b5c1b9ad.js';
import { u as useComponentTheme } from './useComponentTheme-d2f9e47f.js';
import { c as colorInterpolations } from './colors-170180bf.js';
import { m as mergeInterpolations } from './mergeInterpolations-e2db2a66.js';
import { DrawerPopupRedwoodTheme } from './UNSAFE_DrawerPopup/themes/redwood/DrawerPopupTheme.js';

// Constants
const displayModefullWidthThreshold = 600;
const zero = '0px';
const duration = 250;
const getElementHeight = (element) => {
    return Math.round(element.offsetHeight);
};
const getElementWidth = (element) => {
    return Math.round(element.getBoundingClientRect().width);
};
/**
 * A drawer popup adds a single slide-in side content alongside some primary content to an application window.
 *
 * This drawer always overlays the page and can be placed at the 'start', 'end' or 'bottom' edge.
 */
const DrawerPopup = forwardRef(({ children, isOpen = false, autoFocusRef, placement = 'start', modality = 'modal', onClose, onTransitionEnd, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, testId, ...props }, ref) => {
    // Initialize states
    const [status, setStatus] = useState(isOpen ? 'initial' : 'unmounted');
    const [visibility, setVisibility] = useState('hidden');
    const [isOutsideClickDisabled, setIsOutsideClickDisabled] = useState(!isOpen);
    const [displayModeHorizontal, setDisplayModeHorizontal] = useState('overlay');
    const prevStatusRef = useRef('unmounted');
    const rootRef = useRef(null);
    const launcherRef = useRef(null);
    const mergedRef = useMemo(() => mergeRefs(rootRef, ref), [rootRef, ref]);
    const isBottomPlacement = placement === 'bottom';
    const shouldReturnFocus = useRef(false);
    // Animation config
    const animationStates = {
        opening: () => {
            if (isBottomPlacement) {
                return {
                    from: {
                        translateY: getTranslateY()
                    },
                    to: {
                        translateY: zero
                    },
                    options: {
                        duration: duration
                    }
                };
            }
            else {
                return {
                    from: {
                        translateX: getTranslateX()
                    },
                    to: {
                        translateX: zero
                    },
                    options: {
                        duration: duration
                    }
                };
            }
        },
        closing: () => {
            if (isBottomPlacement) {
                return {
                    from: {
                        translateY: zero
                    },
                    to: {
                        translateY: getTranslateY()
                    },
                    options: {
                        duration: duration
                    }
                };
            }
            else {
                return {
                    from: {
                        translateX: zero
                    },
                    to: {
                        translateX: getTranslateX()
                    },
                    options: {
                        duration: duration
                    }
                };
            }
        }
    };
    // testId support
    const testIdProps = useTestId(testId);
    // Normalizing position
    const { direction } = useUser();
    const getNormalizedPlacement = () => {
        return normalizePosition(placement, direction);
    };
    const getTranslateX = () => {
        return `${getNormalizedPlacement() === 'right' ? '' : '-'}${getElementWidth(rootRef.current)}px`;
    };
    const getTranslateY = () => {
        return `${getElementHeight(rootRef.current)}px`;
    };
    const setInitialFocus = () => {
        // Set focus to the first match:
        // 1. First element inside the drawer matching [autofocus]
        const autofocusItems = getAutofocusFocusables(rootRef.current);
        if (autofocusItems.length > 0) {
            focusOn(autofocusItems[0]);
            return;
        }
        // 2. Tabbable element inside the content element
        // 3. The drawer itself
        focusWithin(rootRef.current);
    };
    const returnFocusToLauncher = () => {
        if (shouldReturnFocus.current) {
            focusOnElementOrNearestAncestor(launcherRef.current);
            shouldReturnFocus.current = false;
        }
    };
    // Remove style attribute left on the element by the Animation util
    const removeAnimationInlineStyles = useCallback(() => {
        rootRef.current?.style?.removeProperty('transform');
    }, []);
    // Animation
    const { nodeRef } = useAnimation(status, {
        animationStates,
        isAnimatedOnMount: true,
        onAnimationEnd: () => {
            if (status === 'opening') {
                removeAnimationInlineStyles();
                setInitialFocus();
                onTransitionEnd?.(true);
            }
            else if (!isOpen) {
                shouldReturnFocus.current =
                    rootRef.current?.contains(getActiveElement()) || false;
                nodeRef(null);
                setVisibility('hidden');
                setStatus('unmounted');
                onTransitionEnd?.(false);
            }
        }
    });
    useEffect(() => {
        // 1. Ignore status update on initial render
        if (status === 'unmounted' && !isOpen && status !== prevStatusRef.current) {
            returnFocusToLauncher();
        }
        // 2. Mount visually hidden component
        // Component gets mounted, but will be visually hidden to avoid flickering
        // Flickering happens because it gets rendered before animation hook repositions
        // it in next render cycle
        else if (status === 'unmounted' && isOpen) {
            setStatus('initial');
        }
        // 3. Setup animation
        // Component has been mounted.
        // We will kick out animation as node is already present in DOM.
        // To avoid flickering, we still keep it visually hidden until the next render cycle.
        else if (status === 'initial' && isOpen) {
            nodeRef(rootRef.current);
            setIsOutsideClickDisabled(false);
            setStatus('opening');
        }
        // 3. Unhide component. It becomes visible.
        else if (status === 'opening' && isOpen) {
            setVisibility('visible');
        }
        // 4. Closing
        else if (!isOpen && status !== 'unmounted') {
            setIsOutsideClickDisabled(true);
            setStatus('closing');
        }
        return () => {
            // remember the previous status
            prevStatusRef.current = status;
        };
    }, [isOpen, status, nodeRef]);
    // Destroy case handler
    useEffect(() => {
        return () => {
            // eslint-disable-next-line react-hooks/exhaustive-deps
            if (rootRef.current?.contains(getActiveElement()) || false) {
                // In destroy case ModalManager is blocking returning of the focus.
                // We need to reschedule it to workaround that
                setTimeout(() => {
                    focusOnElementOrNearestAncestor(launcherRef.current);
                }, 0);
            }
        };
    }, []);
    // Launcher handler
    useEffect(() => {
        if (status === 'opening') {
            // Remember launcher
            launcherRef.current = getActiveElement();
            // Register F6 key handler. It is modeless only feature.
            if (modality === 'modeless') {
                launcherRef.current?.addEventListener('keydown', launcherKeyDownCallback);
            }
        }
        if (status === 'closing' && modality === 'modeless') {
            return () => {
                // Deregister F6 key handler
                launcherRef.current?.removeEventListener('keydown', launcherKeyDownCallback);
            };
        }
        return;
    }, [modality, status]);
    const launcherKeyDownCallback = (event) => {
        // Moves focus from launcher (must have focus) back to the Drawer
        if (launcherRef.current === getActiveElement() && event.code === 'F6') {
            // Prevent default F6 handlers.
            // F6 is a standard Chrome address bar shortcut on Windows.
            event.preventDefault();
            event.stopPropagation();
            // Focus on first tabbable in a container or container itself if there is none
            focusWithin(rootRef.current);
        }
    };
    const handleKeyDown = useCallback((event) => {
        const launcherEl = launcherRef.current;
        switch (event.code) {
            case 'Escape': {
                onClose?.({ reason: 'escapeKey' });
                break;
            }
            case 'F6':
                if (modality === 'modeless') {
                    // Prevent default F6 handlers.
                    // F6 is a standard Chrome address bar shortcut on Windows.
                    event.preventDefault();
                    event.stopPropagation();
                    // Focus launcher
                    if (launcherEl) {
                        focusOn(launcherEl);
                    }
                    break;
                }
        }
    }, [onClose, modality]);
    // OutsideClick handler
    const handleOutsideClick = useCallback(() => {
        // Trigger onClose event with outside click detail
        onClose?.({ reason: 'outsideClick' });
    }, [onClose]);
    const insideRefs = [rootRef];
    // launcher should be considered an "inside" element
    if (launcherRef.current && launcherRef.current !== document.body) {
        insideRefs.push(launcherRef);
    }
    useOutsideClick({
        isDisabled: isOutsideClickDisabled,
        ref: insideRefs,
        handler: handleOutsideClick
    });
    // Drawer placement
    const getDrawerPlacement = () => {
        if (isBottomPlacement) {
            return placement;
        }
        else {
            if (direction === 'rtl') {
                return placement === 'start' ? 'right' : 'left';
            }
            else {
                return placement === 'start' ? 'left' : 'right';
            }
        }
    };
    const clientHints = getClientHints();
    const getViewportWidth = useCallback(() => {
        if (clientHints.platform === 'ios') {
            // On ios window.innerWidth is not recommended way of measuring the viewport
            return document.documentElement.clientWidth;
        }
        return window.innerWidth;
    }, [clientHints.platform]);
    // Window resize handler
    // Handles display mode: Overlay <> Full overlay (full width side drawers)
    const handleResize = useCallback(() => {
        const viewportWidth = getViewportWidth();
        if (displayModeHorizontal === 'overlay' && viewportWidth < displayModefullWidthThreshold) {
            setDisplayModeHorizontal('fullOverlay');
        }
        if (displayModeHorizontal === 'fullOverlay' &&
            viewportWidth > displayModefullWidthThreshold) {
            setDisplayModeHorizontal('overlay');
        }
    }, [getViewportWidth, displayModeHorizontal]);
    useEffect(() => {
        const root = rootRef.current;
        if (root && status === 'opening') {
            const resizeObserver = new ResizeObserver(() => {
                handleResize();
            });
            resizeObserver.observe(document.body);
            return () => {
                resizeObserver.unobserve(document.body);
            };
        }
        return undefined;
    }, [status, handleResize]);
    const dispatchSwipeOnClose = useCallback(() => {
        onClose?.({ reason: 'swipe' });
    }, [onClose]);
    // OutsideClick handler
    const swipeHandler = useCallback((event) => {
        const swipeDir = event.direction;
        // LTR
        if (direction === 'ltr') {
            if ((placement === 'start' && swipeDir === 'left') ||
                (placement === 'end' && swipeDir === 'right')) {
                dispatchSwipeOnClose();
            }
        }
        // RTL
        if (direction === 'rtl') {
            if ((placement === 'start' && swipeDir === 'right') ||
                (placement === 'end' && swipeDir === 'left')) {
                dispatchSwipeOnClose();
            }
        }
        // Bottom
        if (placement === 'bottom' && swipeDir === 'down') {
            dispatchSwipeOnClose();
        }
    }, [direction, placement, dispatchSwipeOnClose]);
    const swipeOptions = {
        threshold: 50,
        maximumTime: 750,
        tolerance: 40
    };
    const { swipeProps } = useSwipe(swipeHandler, swipeOptions);
    // Styles
    const { classes } = useComponentTheme(DrawerPopupRedwoodTheme, {
        placement: getDrawerPlacement(),
        visibility,
        displayModeHorizontal: ['start', 'end'].indexOf(placement) > -1 ? displayModeHorizontal : undefined
    });
    const interpolations = [...Object.values(colorInterpolations)];
    const styleInterpolations = mergeInterpolations(interpolations);
    const { ...styles } = styleInterpolations(props);
    const renderDrawer = () => {
        return (jsx(FocusTrap, { autoFocusRef: autoFocusRef, restoreFocusRef: false, children: jsx("div", { ref: mergedRef, className: classes, style: styles, tabIndex: -1, role: "dialog", onKeyDown: handleKeyDown, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, ...swipeProps, ...testIdProps, children: children }) }));
    };
    const isMounted = status !== 'unmounted';
    if (isMounted) {
        // Modal vs. modelss upper wrapper
        if (modality === 'modal') {
            return jsx(Modal, { isOpen: isMounted, children: renderDrawer() });
        }
        else {
            return jsx(Layer, { children: renderDrawer() });
        }
    }
    return null;
});

export { DrawerPopup as D };
//# sourceMappingURL=DrawerPopup-9cdb203a.js.map
