'use client'; import _extends from "@babel/runtime/helpers/esm/extends"; import * as React from 'react'; import { unstable_ownerDocument as ownerDocument, unstable_useForkRef as useForkRef, unstable_useEventCallback as useEventCallback, unstable_createChainedFunction as createChainedFunction } from '@mui/utils'; import { extractEventHandlers } from '../utils'; import { ModalManager, ariaHidden } from './ModalManager'; function getContainer(container) { return typeof container === 'function' ? container() : container; } function getHasTransition(children) { return children ? children.props.hasOwnProperty('in') : false; } // A modal manager used to track and manage the state of open Modals. // Modals don't open on the server so this won't conflict with concurrent requests. const defaultManager = new ModalManager(); /** * * Demos: * * - [Modal](https://mui.com/base-ui/react-modal/#hook) * * API: * * - [useModal API](https://mui.com/base-ui/react-modal/hooks-api/#use-modal) */ export function useModal(parameters) { const { container, disableEscapeKeyDown = false, disableScrollLock = false, // @ts-ignore internal logic - Base UI supports the manager as a prop too manager = defaultManager, closeAfterTransition = false, onTransitionEnter, onTransitionExited, children, onClose, open, rootRef } = parameters; // @ts-ignore internal logic const modal = React.useRef({}); const mountNodeRef = React.useRef(null); const modalRef = React.useRef(null); const handleRef = useForkRef(modalRef, rootRef); const [exited, setExited] = React.useState(!open); const hasTransition = getHasTransition(children); let ariaHiddenProp = true; if (parameters['aria-hidden'] === 'false' || parameters['aria-hidden'] === false) { ariaHiddenProp = false; } const getDoc = () => ownerDocument(mountNodeRef.current); const getModal = () => { modal.current.modalRef = modalRef.current; modal.current.mount = mountNodeRef.current; return modal.current; }; const handleMounted = () => { manager.mount(getModal(), { disableScrollLock }); // Fix a bug on Chrome where the scroll isn't initially 0. if (modalRef.current) { modalRef.current.scrollTop = 0; } }; const handleOpen = useEventCallback(() => { const resolvedContainer = getContainer(container) || getDoc().body; manager.add(getModal(), resolvedContainer); // The element was already mounted. if (modalRef.current) { handleMounted(); } }); const isTopModal = React.useCallback(() => manager.isTopModal(getModal()), [manager]); const handlePortalRef = useEventCallback(node => { mountNodeRef.current = node; if (!node) { return; } if (open && isTopModal()) { handleMounted(); } else if (modalRef.current) { ariaHidden(modalRef.current, ariaHiddenProp); } }); const handleClose = React.useCallback(() => { manager.remove(getModal(), ariaHiddenProp); }, [ariaHiddenProp, manager]); React.useEffect(() => { return () => { handleClose(); }; }, [handleClose]); React.useEffect(() => { if (open) { handleOpen(); } else if (!hasTransition || !closeAfterTransition) { handleClose(); } }, [open, handleClose, hasTransition, closeAfterTransition, handleOpen]); const createHandleKeyDown = otherHandlers => event => { otherHandlers.onKeyDown?.(event); // The handler doesn't take event.defaultPrevented into account: // // event.preventDefault() is meant to stop default behaviors like // clicking a checkbox to check it, hitting a button to submit a form, // and hitting left arrow to move the cursor in a text input etc. // Only special HTML elements have these default behaviors. if (event.key !== 'Escape' || event.which === 229 || // Wait until IME is settled. !isTopModal()) { return; } if (!disableEscapeKeyDown) { // Swallow the event, in case someone is listening for the escape key on the body. event.stopPropagation(); if (onClose) { onClose(event, 'escapeKeyDown'); } } }; const createHandleBackdropClick = otherHandlers => event => { otherHandlers.onClick?.(event); if (event.target !== event.currentTarget) { return; } if (onClose) { onClose(event, 'backdropClick'); } }; const getRootProps = (otherHandlers = {}) => { const propsEventHandlers = extractEventHandlers(parameters); // The custom event handlers shouldn't be spread on the root element delete propsEventHandlers.onTransitionEnter; delete propsEventHandlers.onTransitionExited; const externalEventHandlers = _extends({}, propsEventHandlers, otherHandlers); return _extends({ role: 'presentation' }, externalEventHandlers, { onKeyDown: createHandleKeyDown(externalEventHandlers), ref: handleRef }); }; const getBackdropProps = (otherHandlers = {}) => { const externalEventHandlers = otherHandlers; return _extends({ 'aria-hidden': true }, externalEventHandlers, { onClick: createHandleBackdropClick(externalEventHandlers), open }); }; const getTransitionProps = () => { const handleEnter = () => { setExited(false); if (onTransitionEnter) { onTransitionEnter(); } }; const handleExited = () => { setExited(true); if (onTransitionExited) { onTransitionExited(); } if (closeAfterTransition) { handleClose(); } }; return { onEnter: createChainedFunction(handleEnter, children?.props.onEnter), onExited: createChainedFunction(handleExited, children?.props.onExited) }; }; return { getRootProps, getBackdropProps, getTransitionProps, rootRef: handleRef, portalRef: handlePortalRef, isTopModal, exited, hasTransition }; }