UNPKG

8.47 kBJavaScriptView Raw
1import classNames from 'classnames';
2import addEventListener from 'dom-helpers/addEventListener';
3import canUseDOM from 'dom-helpers/canUseDOM';
4import ownerDocument from 'dom-helpers/ownerDocument';
5import removeEventListener from 'dom-helpers/removeEventListener';
6import getScrollbarSize from 'dom-helpers/scrollbarSize';
7import useCallbackRef from '@restart/hooks/useCallbackRef';
8import useEventCallback from '@restart/hooks/useEventCallback';
9import useMergedRefs from '@restart/hooks/useMergedRefs';
10import useWillUnmount from '@restart/hooks/useWillUnmount';
11import transitionEnd from 'dom-helpers/transitionEnd';
12import * as React from 'react';
13import { useCallback, useMemo, useRef, useState } from 'react';
14import BaseModal from '@restart/ui/Modal';
15import { getSharedManager } from './BootstrapModalManager';
16import Fade from './Fade';
17import ModalBody from './ModalBody';
18import ModalContext from './ModalContext';
19import ModalDialog from './ModalDialog';
20import ModalFooter from './ModalFooter';
21import ModalHeader from './ModalHeader';
22import ModalTitle from './ModalTitle';
23import { useBootstrapPrefix, useIsRTL } from './ThemeProvider';
24import { jsx as _jsx } from "react/jsx-runtime";
25const defaultProps = {
26 show: false,
27 backdrop: true,
28 keyboard: true,
29 autoFocus: true,
30 enforceFocus: true,
31 restoreFocus: true,
32 animation: true,
33 dialogAs: ModalDialog
34};
35/* eslint-disable no-use-before-define, react/no-multi-comp */
36
37function DialogTransition(props) {
38 return /*#__PURE__*/_jsx(Fade, { ...props,
39 timeout: null
40 });
41}
42
43function BackdropTransition(props) {
44 return /*#__PURE__*/_jsx(Fade, { ...props,
45 timeout: null
46 });
47}
48/* eslint-enable no-use-before-define */
49
50
51const Modal = /*#__PURE__*/React.forwardRef(({
52 bsPrefix,
53 className,
54 style,
55 dialogClassName,
56 contentClassName,
57 children,
58 dialogAs: Dialog,
59 'aria-labelledby': ariaLabelledby,
60 'aria-describedby': ariaDescribedby,
61 'aria-label': ariaLabel,
62
63 /* BaseModal props */
64 show,
65 animation,
66 backdrop,
67 keyboard,
68 onEscapeKeyDown,
69 onShow,
70 onHide,
71 container,
72 autoFocus,
73 enforceFocus,
74 restoreFocus,
75 restoreFocusOptions,
76 onEntered,
77 onExit,
78 onExiting,
79 onEnter,
80 onEntering,
81 onExited,
82 backdropClassName,
83 manager: propsManager,
84 ...props
85}, ref) => {
86 const [modalStyle, setStyle] = useState({});
87 const [animateStaticModal, setAnimateStaticModal] = useState(false);
88 const waitingForMouseUpRef = useRef(false);
89 const ignoreBackdropClickRef = useRef(false);
90 const removeStaticModalAnimationRef = useRef(null);
91 const [modal, setModalRef] = useCallbackRef();
92 const mergedRef = useMergedRefs(ref, setModalRef);
93 const handleHide = useEventCallback(onHide);
94 const isRTL = useIsRTL();
95 bsPrefix = useBootstrapPrefix(bsPrefix, 'modal');
96 const modalContext = useMemo(() => ({
97 onHide: handleHide
98 }), [handleHide]);
99
100 function getModalManager() {
101 if (propsManager) return propsManager;
102 return getSharedManager({
103 isRTL
104 });
105 }
106
107 function updateDialogStyle(node) {
108 if (!canUseDOM) return;
109 const containerIsOverflowing = getModalManager().getScrollbarWidth() > 0;
110 const modalIsOverflowing = node.scrollHeight > ownerDocument(node).documentElement.clientHeight;
111 setStyle({
112 paddingRight: containerIsOverflowing && !modalIsOverflowing ? getScrollbarSize() : undefined,
113 paddingLeft: !containerIsOverflowing && modalIsOverflowing ? getScrollbarSize() : undefined
114 });
115 }
116
117 const handleWindowResize = useEventCallback(() => {
118 if (modal) {
119 updateDialogStyle(modal.dialog);
120 }
121 });
122 useWillUnmount(() => {
123 removeEventListener(window, 'resize', handleWindowResize);
124 removeStaticModalAnimationRef.current == null ? void 0 : removeStaticModalAnimationRef.current();
125 }); // We prevent the modal from closing during a drag by detecting where the
126 // the click originates from. If it starts in the modal and then ends outside
127 // don't close.
128
129 const handleDialogMouseDown = () => {
130 waitingForMouseUpRef.current = true;
131 };
132
133 const handleMouseUp = e => {
134 if (waitingForMouseUpRef.current && modal && e.target === modal.dialog) {
135 ignoreBackdropClickRef.current = true;
136 }
137
138 waitingForMouseUpRef.current = false;
139 };
140
141 const handleStaticModalAnimation = () => {
142 setAnimateStaticModal(true);
143 removeStaticModalAnimationRef.current = transitionEnd(modal.dialog, () => {
144 setAnimateStaticModal(false);
145 });
146 };
147
148 const handleStaticBackdropClick = e => {
149 if (e.target !== e.currentTarget) {
150 return;
151 }
152
153 handleStaticModalAnimation();
154 };
155
156 const handleClick = e => {
157 if (backdrop === 'static') {
158 handleStaticBackdropClick(e);
159 return;
160 }
161
162 if (ignoreBackdropClickRef.current || e.target !== e.currentTarget) {
163 ignoreBackdropClickRef.current = false;
164 return;
165 }
166
167 onHide == null ? void 0 : onHide();
168 };
169
170 const handleEscapeKeyDown = e => {
171 if (!keyboard && backdrop === 'static') {
172 // Call preventDefault to stop modal from closing in restart ui,
173 // then play our animation.
174 e.preventDefault();
175 handleStaticModalAnimation();
176 } else if (keyboard && onEscapeKeyDown) {
177 onEscapeKeyDown(e);
178 }
179 };
180
181 const handleEnter = (node, isAppearing) => {
182 if (node) {
183 updateDialogStyle(node);
184 }
185
186 onEnter == null ? void 0 : onEnter(node, isAppearing);
187 };
188
189 const handleExit = node => {
190 removeStaticModalAnimationRef.current == null ? void 0 : removeStaticModalAnimationRef.current();
191 onExit == null ? void 0 : onExit(node);
192 };
193
194 const handleEntering = (node, isAppearing) => {
195 onEntering == null ? void 0 : onEntering(node, isAppearing); // FIXME: This should work even when animation is disabled.
196
197 addEventListener(window, 'resize', handleWindowResize);
198 };
199
200 const handleExited = node => {
201 if (node) node.style.display = ''; // RHL removes it sometimes
202
203 onExited == null ? void 0 : onExited(node); // FIXME: This should work even when animation is disabled.
204
205 removeEventListener(window, 'resize', handleWindowResize);
206 };
207
208 const renderBackdrop = useCallback(backdropProps => /*#__PURE__*/_jsx("div", { ...backdropProps,
209 className: classNames(`${bsPrefix}-backdrop`, backdropClassName, !animation && 'show')
210 }), [animation, backdropClassName, bsPrefix]);
211 const baseModalStyle = { ...style,
212 ...modalStyle
213 }; // If `display` is not set to block, autoFocus inside the modal fails
214 // https://github.com/react-bootstrap/react-bootstrap/issues/5102
215
216 baseModalStyle.display = 'block';
217
218 const renderDialog = dialogProps => /*#__PURE__*/_jsx("div", {
219 role: "dialog",
220 ...dialogProps,
221 style: baseModalStyle,
222 className: classNames(className, bsPrefix, animateStaticModal && `${bsPrefix}-static`, !animation && 'show'),
223 onClick: backdrop ? handleClick : undefined,
224 onMouseUp: handleMouseUp,
225 "aria-label": ariaLabel,
226 "aria-labelledby": ariaLabelledby,
227 "aria-describedby": ariaDescribedby,
228 children: /*#__PURE__*/_jsx(Dialog, { ...props,
229 onMouseDown: handleDialogMouseDown,
230 className: dialogClassName,
231 contentClassName: contentClassName,
232 children: children
233 })
234 });
235
236 return /*#__PURE__*/_jsx(ModalContext.Provider, {
237 value: modalContext,
238 children: /*#__PURE__*/_jsx(BaseModal, {
239 show: show,
240 ref: mergedRef,
241 backdrop: backdrop,
242 container: container,
243 keyboard: true // Always set true - see handleEscapeKeyDown
244 ,
245 autoFocus: autoFocus,
246 enforceFocus: enforceFocus,
247 restoreFocus: restoreFocus,
248 restoreFocusOptions: restoreFocusOptions,
249 onEscapeKeyDown: handleEscapeKeyDown,
250 onShow: onShow,
251 onHide: onHide,
252 onEnter: handleEnter,
253 onEntering: handleEntering,
254 onEntered: onEntered,
255 onExit: handleExit,
256 onExiting: onExiting,
257 onExited: handleExited,
258 manager: getModalManager(),
259 transition: animation ? DialogTransition : undefined,
260 backdropTransition: animation ? BackdropTransition : undefined,
261 renderBackdrop: renderBackdrop,
262 renderDialog: renderDialog
263 })
264 });
265});
266Modal.displayName = 'Modal';
267Modal.defaultProps = defaultProps;
268export default Object.assign(Modal, {
269 Body: ModalBody,
270 Header: ModalHeader,
271 Title: ModalTitle,
272 Footer: ModalFooter,
273 Dialog: ModalDialog,
274 TRANSITION_DURATION: 300,
275 BACKDROP_TRANSITION_DURATION: 150
276});
\No newline at end of file