import classNames from 'classnames'; import addEventListener from 'dom-helpers/addEventListener'; import canUseDOM from 'dom-helpers/canUseDOM'; import ownerDocument from 'dom-helpers/ownerDocument'; import removeEventListener from 'dom-helpers/removeEventListener'; import getScrollbarSize from 'dom-helpers/scrollbarSize'; import useCallbackRef from '@restart/hooks/useCallbackRef'; import useEventCallback from '@restart/hooks/useEventCallback'; import useMergedRefs from '@restart/hooks/useMergedRefs'; import useWillUnmount from '@restart/hooks/useWillUnmount'; import transitionEnd from 'dom-helpers/transitionEnd'; import * as React from 'react'; import { useCallback, useMemo, useRef, useState } from 'react'; import BaseModal from '@restart/ui/Modal'; import { getSharedManager } from './BootstrapModalManager'; import Fade from './Fade'; import ModalBody from './ModalBody'; import ModalContext from './ModalContext'; import ModalDialog from './ModalDialog'; import ModalFooter from './ModalFooter'; import ModalHeader from './ModalHeader'; import ModalTitle from './ModalTitle'; import { useBootstrapPrefix, useIsRTL } from './ThemeProvider'; import { jsx as _jsx } from "react/jsx-runtime"; const defaultProps = { show: false, backdrop: true, keyboard: true, autoFocus: true, enforceFocus: true, restoreFocus: true, animation: true, dialogAs: ModalDialog }; /* eslint-disable no-use-before-define, react/no-multi-comp */ function DialogTransition(props) { return /*#__PURE__*/_jsx(Fade, { ...props, timeout: null }); } function BackdropTransition(props) { return /*#__PURE__*/_jsx(Fade, { ...props, timeout: null }); } /* eslint-enable no-use-before-define */ const Modal = /*#__PURE__*/React.forwardRef(({ bsPrefix, className, style, dialogClassName, contentClassName, children, dialogAs: Dialog, 'aria-labelledby': ariaLabelledby, 'aria-describedby': ariaDescribedby, 'aria-label': ariaLabel, /* BaseModal props */ show, animation, backdrop, keyboard, onEscapeKeyDown, onShow, onHide, container, autoFocus, enforceFocus, restoreFocus, restoreFocusOptions, onEntered, onExit, onExiting, onEnter, onEntering, onExited, backdropClassName, manager: propsManager, ...props }, ref) => { const [modalStyle, setStyle] = useState({}); const [animateStaticModal, setAnimateStaticModal] = useState(false); const waitingForMouseUpRef = useRef(false); const ignoreBackdropClickRef = useRef(false); const removeStaticModalAnimationRef = useRef(null); const [modal, setModalRef] = useCallbackRef(); const mergedRef = useMergedRefs(ref, setModalRef); const handleHide = useEventCallback(onHide); const isRTL = useIsRTL(); bsPrefix = useBootstrapPrefix(bsPrefix, 'modal'); const modalContext = useMemo(() => ({ onHide: handleHide }), [handleHide]); function getModalManager() { if (propsManager) return propsManager; return getSharedManager({ isRTL }); } function updateDialogStyle(node) { if (!canUseDOM) return; const containerIsOverflowing = getModalManager().getScrollbarWidth() > 0; const modalIsOverflowing = node.scrollHeight > ownerDocument(node).documentElement.clientHeight; setStyle({ paddingRight: containerIsOverflowing && !modalIsOverflowing ? getScrollbarSize() : undefined, paddingLeft: !containerIsOverflowing && modalIsOverflowing ? getScrollbarSize() : undefined }); } const handleWindowResize = useEventCallback(() => { if (modal) { updateDialogStyle(modal.dialog); } }); useWillUnmount(() => { removeEventListener(window, 'resize', handleWindowResize); removeStaticModalAnimationRef.current == null ? void 0 : removeStaticModalAnimationRef.current(); }); // We prevent the modal from closing during a drag by detecting where the // the click originates from. If it starts in the modal and then ends outside // don't close. const handleDialogMouseDown = () => { waitingForMouseUpRef.current = true; }; const handleMouseUp = e => { if (waitingForMouseUpRef.current && modal && e.target === modal.dialog) { ignoreBackdropClickRef.current = true; } waitingForMouseUpRef.current = false; }; const handleStaticModalAnimation = () => { setAnimateStaticModal(true); removeStaticModalAnimationRef.current = transitionEnd(modal.dialog, () => { setAnimateStaticModal(false); }); }; const handleStaticBackdropClick = e => { if (e.target !== e.currentTarget) { return; } handleStaticModalAnimation(); }; const handleClick = e => { if (backdrop === 'static') { handleStaticBackdropClick(e); return; } if (ignoreBackdropClickRef.current || e.target !== e.currentTarget) { ignoreBackdropClickRef.current = false; return; } onHide == null ? void 0 : onHide(); }; const handleEscapeKeyDown = e => { if (!keyboard && backdrop === 'static') { // Call preventDefault to stop modal from closing in restart ui, // then play our animation. e.preventDefault(); handleStaticModalAnimation(); } else if (keyboard && onEscapeKeyDown) { onEscapeKeyDown(e); } }; const handleEnter = (node, isAppearing) => { if (node) { updateDialogStyle(node); } onEnter == null ? void 0 : onEnter(node, isAppearing); }; const handleExit = node => { removeStaticModalAnimationRef.current == null ? void 0 : removeStaticModalAnimationRef.current(); onExit == null ? void 0 : onExit(node); }; const handleEntering = (node, isAppearing) => { onEntering == null ? void 0 : onEntering(node, isAppearing); // FIXME: This should work even when animation is disabled. addEventListener(window, 'resize', handleWindowResize); }; const handleExited = node => { if (node) node.style.display = ''; // RHL removes it sometimes onExited == null ? void 0 : onExited(node); // FIXME: This should work even when animation is disabled. removeEventListener(window, 'resize', handleWindowResize); }; const renderBackdrop = useCallback(backdropProps => /*#__PURE__*/_jsx("div", { ...backdropProps, className: classNames(`${bsPrefix}-backdrop`, backdropClassName, !animation && 'show') }), [animation, backdropClassName, bsPrefix]); const baseModalStyle = { ...style, ...modalStyle }; // If `display` is not set to block, autoFocus inside the modal fails // https://github.com/react-bootstrap/react-bootstrap/issues/5102 baseModalStyle.display = 'block'; const renderDialog = dialogProps => /*#__PURE__*/_jsx("div", { role: "dialog", ...dialogProps, style: baseModalStyle, className: classNames(className, bsPrefix, animateStaticModal && `${bsPrefix}-static`), onClick: backdrop ? handleClick : undefined, onMouseUp: handleMouseUp, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby, children: /*#__PURE__*/_jsx(Dialog, { ...props, onMouseDown: handleDialogMouseDown, className: dialogClassName, contentClassName: contentClassName, children: children }) }); return /*#__PURE__*/_jsx(ModalContext.Provider, { value: modalContext, children: /*#__PURE__*/_jsx(BaseModal, { show: show, ref: mergedRef, backdrop: backdrop, container: container, keyboard: true // Always set true - see handleEscapeKeyDown , autoFocus: autoFocus, enforceFocus: enforceFocus, restoreFocus: restoreFocus, restoreFocusOptions: restoreFocusOptions, onEscapeKeyDown: handleEscapeKeyDown, onShow: onShow, onHide: onHide, onEnter: handleEnter, onEntering: handleEntering, onEntered: onEntered, onExit: handleExit, onExiting: onExiting, onExited: handleExited, manager: getModalManager(), transition: animation ? DialogTransition : undefined, backdropTransition: animation ? BackdropTransition : undefined, renderBackdrop: renderBackdrop, renderDialog: renderDialog }) }); }); Modal.displayName = 'Modal'; Modal.defaultProps = defaultProps; export default Object.assign(Modal, { Body: ModalBody, Header: ModalHeader, Title: ModalTitle, Footer: ModalFooter, Dialog: ModalDialog, TRANSITION_DURATION: 300, BACKDROP_TRANSITION_DURATION: 150 });