UNPKG

15.4 kBJavaScriptView Raw
1"use strict";
2
3var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
4
5var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
6
7exports.__esModule = true;
8exports["default"] = void 0;
9
10var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
11
12var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
13
14var _activeElement = _interopRequireDefault(require("dom-helpers/activeElement"));
15
16var _contains = _interopRequireDefault(require("dom-helpers/contains"));
17
18var _canUseDOM = _interopRequireDefault(require("dom-helpers/canUseDOM"));
19
20var _listen = _interopRequireDefault(require("dom-helpers/listen"));
21
22var _propTypes = _interopRequireDefault(require("prop-types"));
23
24var _react = _interopRequireWildcard(require("react"));
25
26var _reactDom = _interopRequireDefault(require("react-dom"));
27
28var _useMounted = _interopRequireDefault(require("@restart/hooks/useMounted"));
29
30var _useWillUnmount = _interopRequireDefault(require("@restart/hooks/useWillUnmount"));
31
32var _usePrevious = _interopRequireDefault(require("@restart/hooks/usePrevious"));
33
34var _useEventCallback = _interopRequireDefault(require("@restart/hooks/useEventCallback"));
35
36var _ModalManager = _interopRequireDefault(require("./ModalManager"));
37
38var _useWaitForDOMRef = _interopRequireDefault(require("./useWaitForDOMRef"));
39
40/* eslint-disable @typescript-eslint/no-use-before-define, react/prop-types */
41var manager;
42
43function getManager() {
44 if (!manager) manager = new _ModalManager["default"]();
45 return manager;
46}
47
48function useModalManager(provided) {
49 var modalManager = provided || getManager();
50 var modal = (0, _react.useRef)({
51 dialog: null,
52 backdrop: null
53 });
54 return Object.assign(modal.current, {
55 add: function add(container, className) {
56 return modalManager.add(modal.current, container, className);
57 },
58 remove: function remove() {
59 return modalManager.remove(modal.current);
60 },
61 isTopModal: function isTopModal() {
62 return modalManager.isTopModal(modal.current);
63 },
64 setDialogRef: (0, _react.useCallback)(function (ref) {
65 modal.current.dialog = ref;
66 }, []),
67 setBackdropRef: (0, _react.useCallback)(function (ref) {
68 modal.current.backdrop = ref;
69 }, [])
70 });
71}
72
73var Modal = /*#__PURE__*/(0, _react.forwardRef)(function (_ref, ref) {
74 var _ref$show = _ref.show,
75 show = _ref$show === void 0 ? false : _ref$show,
76 _ref$role = _ref.role,
77 role = _ref$role === void 0 ? 'dialog' : _ref$role,
78 className = _ref.className,
79 style = _ref.style,
80 children = _ref.children,
81 _ref$backdrop = _ref.backdrop,
82 backdrop = _ref$backdrop === void 0 ? true : _ref$backdrop,
83 _ref$keyboard = _ref.keyboard,
84 keyboard = _ref$keyboard === void 0 ? true : _ref$keyboard,
85 onBackdropClick = _ref.onBackdropClick,
86 onEscapeKeyDown = _ref.onEscapeKeyDown,
87 transition = _ref.transition,
88 backdropTransition = _ref.backdropTransition,
89 _ref$autoFocus = _ref.autoFocus,
90 autoFocus = _ref$autoFocus === void 0 ? true : _ref$autoFocus,
91 _ref$enforceFocus = _ref.enforceFocus,
92 enforceFocus = _ref$enforceFocus === void 0 ? true : _ref$enforceFocus,
93 _ref$restoreFocus = _ref.restoreFocus,
94 restoreFocus = _ref$restoreFocus === void 0 ? true : _ref$restoreFocus,
95 restoreFocusOptions = _ref.restoreFocusOptions,
96 renderDialog = _ref.renderDialog,
97 _ref$renderBackdrop = _ref.renderBackdrop,
98 renderBackdrop = _ref$renderBackdrop === void 0 ? function (props) {
99 return /*#__PURE__*/_react["default"].createElement("div", props);
100 } : _ref$renderBackdrop,
101 providedManager = _ref.manager,
102 containerRef = _ref.container,
103 containerClassName = _ref.containerClassName,
104 onShow = _ref.onShow,
105 _ref$onHide = _ref.onHide,
106 onHide = _ref$onHide === void 0 ? function () {} : _ref$onHide,
107 onExit = _ref.onExit,
108 onExited = _ref.onExited,
109 onExiting = _ref.onExiting,
110 onEnter = _ref.onEnter,
111 onEntering = _ref.onEntering,
112 onEntered = _ref.onEntered,
113 rest = (0, _objectWithoutPropertiesLoose2["default"])(_ref, ["show", "role", "className", "style", "children", "backdrop", "keyboard", "onBackdropClick", "onEscapeKeyDown", "transition", "backdropTransition", "autoFocus", "enforceFocus", "restoreFocus", "restoreFocusOptions", "renderDialog", "renderBackdrop", "manager", "container", "containerClassName", "onShow", "onHide", "onExit", "onExited", "onExiting", "onEnter", "onEntering", "onEntered"]);
114 var container = (0, _useWaitForDOMRef["default"])(containerRef);
115 var modal = useModalManager(providedManager);
116 var isMounted = (0, _useMounted["default"])();
117 var prevShow = (0, _usePrevious["default"])(show);
118
119 var _useState = (0, _react.useState)(!show),
120 exited = _useState[0],
121 setExited = _useState[1];
122
123 var lastFocusRef = (0, _react.useRef)(null);
124 (0, _react.useImperativeHandle)(ref, function () {
125 return modal;
126 }, [modal]);
127
128 if (_canUseDOM["default"] && !prevShow && show) {
129 lastFocusRef.current = (0, _activeElement["default"])();
130 }
131
132 if (!transition && !show && !exited) {
133 setExited(true);
134 } else if (show && exited) {
135 setExited(false);
136 }
137
138 var handleShow = (0, _useEventCallback["default"])(function () {
139 modal.add(container, containerClassName);
140 removeKeydownListenerRef.current = (0, _listen["default"])(document, 'keydown', handleDocumentKeyDown);
141 removeFocusListenerRef.current = (0, _listen["default"])(document, 'focus', // the timeout is necessary b/c this will run before the new modal is mounted
142 // and so steals focus from it
143 function () {
144 return setTimeout(handleEnforceFocus);
145 }, true);
146
147 if (onShow) {
148 onShow();
149 } // autofocus after onShow to not trigger a focus event for previous
150 // modals before this one is shown.
151
152
153 if (autoFocus) {
154 var currentActiveElement = (0, _activeElement["default"])(document);
155
156 if (modal.dialog && currentActiveElement && !(0, _contains["default"])(modal.dialog, currentActiveElement)) {
157 lastFocusRef.current = currentActiveElement;
158 modal.dialog.focus();
159 }
160 }
161 });
162 var handleHide = (0, _useEventCallback["default"])(function () {
163 modal.remove();
164 removeKeydownListenerRef.current == null ? void 0 : removeKeydownListenerRef.current();
165 removeFocusListenerRef.current == null ? void 0 : removeFocusListenerRef.current();
166
167 if (restoreFocus) {
168 var _lastFocusRef$current;
169
170 // Support: <=IE11 doesn't support `focus()` on svg elements (RB: #917)
171 (_lastFocusRef$current = lastFocusRef.current) == null ? void 0 : _lastFocusRef$current.focus == null ? void 0 : _lastFocusRef$current.focus(restoreFocusOptions);
172 lastFocusRef.current = null;
173 }
174 }); // TODO: try and combine these effects: https://github.com/react-bootstrap/react-overlays/pull/794#discussion_r409954120
175 // Show logic when:
176 // - show is `true` _and_ `container` has resolved
177
178 (0, _react.useEffect)(function () {
179 if (!show || !container) return;
180 handleShow();
181 }, [show, container,
182 /* should never change: */
183 handleShow]); // Hide cleanup logic when:
184 // - `exited` switches to true
185 // - component unmounts;
186
187 (0, _react.useEffect)(function () {
188 if (!exited) return;
189 handleHide();
190 }, [exited, handleHide]);
191 (0, _useWillUnmount["default"])(function () {
192 handleHide();
193 }); // --------------------------------
194
195 var handleEnforceFocus = (0, _useEventCallback["default"])(function () {
196 if (!enforceFocus || !isMounted() || !modal.isTopModal()) {
197 return;
198 }
199
200 var currentActiveElement = (0, _activeElement["default"])();
201
202 if (modal.dialog && currentActiveElement && !(0, _contains["default"])(modal.dialog, currentActiveElement)) {
203 modal.dialog.focus();
204 }
205 });
206 var handleBackdropClick = (0, _useEventCallback["default"])(function (e) {
207 if (e.target !== e.currentTarget) {
208 return;
209 }
210
211 onBackdropClick == null ? void 0 : onBackdropClick(e);
212
213 if (backdrop === true) {
214 onHide();
215 }
216 });
217 var handleDocumentKeyDown = (0, _useEventCallback["default"])(function (e) {
218 if (keyboard && e.keyCode === 27 && modal.isTopModal()) {
219 onEscapeKeyDown == null ? void 0 : onEscapeKeyDown(e);
220
221 if (!e.defaultPrevented) {
222 onHide();
223 }
224 }
225 });
226 var removeFocusListenerRef = (0, _react.useRef)();
227 var removeKeydownListenerRef = (0, _react.useRef)();
228
229 var handleHidden = function handleHidden() {
230 setExited(true);
231
232 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
233 args[_key] = arguments[_key];
234 }
235
236 onExited == null ? void 0 : onExited.apply(void 0, args);
237 };
238
239 var Transition = transition;
240
241 if (!container || !(show || Transition && !exited)) {
242 return null;
243 }
244
245 var dialogProps = (0, _extends2["default"])({
246 role: role,
247 ref: modal.setDialogRef,
248 // apparently only works on the dialog role element
249 'aria-modal': role === 'dialog' ? true : undefined
250 }, rest, {
251 style: style,
252 className: className,
253 tabIndex: -1
254 });
255 var dialog = renderDialog ? renderDialog(dialogProps) : /*#__PURE__*/_react["default"].createElement("div", dialogProps, /*#__PURE__*/_react["default"].cloneElement(children, {
256 role: 'document'
257 }));
258
259 if (Transition) {
260 dialog = /*#__PURE__*/_react["default"].createElement(Transition, {
261 appear: true,
262 unmountOnExit: true,
263 "in": !!show,
264 onExit: onExit,
265 onExiting: onExiting,
266 onExited: handleHidden,
267 onEnter: onEnter,
268 onEntering: onEntering,
269 onEntered: onEntered
270 }, dialog);
271 }
272
273 var backdropElement = null;
274
275 if (backdrop) {
276 var BackdropTransition = backdropTransition;
277 backdropElement = renderBackdrop({
278 ref: modal.setBackdropRef,
279 onClick: handleBackdropClick
280 });
281
282 if (BackdropTransition) {
283 backdropElement = /*#__PURE__*/_react["default"].createElement(BackdropTransition, {
284 appear: true,
285 "in": !!show
286 }, backdropElement);
287 }
288 }
289
290 return /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_reactDom["default"].createPortal( /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, backdropElement, dialog), container));
291});
292var propTypes = {
293 /**
294 * Set the visibility of the Modal
295 */
296 show: _propTypes["default"].bool,
297
298 /**
299 * A DOM element, a `ref` to an element, or function that returns either. The Modal is appended to it's `container` element.
300 *
301 * For the sake of assistive technologies, the container should usually be the document body, so that the rest of the
302 * page content can be placed behind a virtual backdrop as well as a visual one.
303 */
304 container: _propTypes["default"].any,
305
306 /**
307 * A callback fired when the Modal is opening.
308 */
309 onShow: _propTypes["default"].func,
310
311 /**
312 * A callback fired when either the backdrop is clicked, or the escape key is pressed.
313 *
314 * The `onHide` callback only signals intent from the Modal,
315 * you must actually set the `show` prop to `false` for the Modal to close.
316 */
317 onHide: _propTypes["default"].func,
318
319 /**
320 * Include a backdrop component.
321 */
322 backdrop: _propTypes["default"].oneOfType([_propTypes["default"].bool, _propTypes["default"].oneOf(['static'])]),
323
324 /**
325 * A function that returns the dialog component. Useful for custom
326 * rendering. **Note:** the component should make sure to apply the provided ref.
327 *
328 * ```js static
329 * renderDialog={props => <MyDialog {...props} />}
330 * ```
331 */
332 renderDialog: _propTypes["default"].func,
333
334 /**
335 * A function that returns a backdrop component. Useful for custom
336 * backdrop rendering.
337 *
338 * ```js
339 * renderBackdrop={props => <MyBackdrop {...props} />}
340 * ```
341 */
342 renderBackdrop: _propTypes["default"].func,
343
344 /**
345 * A callback fired when the escape key, if specified in `keyboard`, is pressed.
346 *
347 * If preventDefault() is called on the keyboard event, closing the modal will be cancelled.
348 */
349 onEscapeKeyDown: _propTypes["default"].func,
350
351 /**
352 * A callback fired when the backdrop, if specified, is clicked.
353 */
354 onBackdropClick: _propTypes["default"].func,
355
356 /**
357 * A css class or set of classes applied to the modal container when the modal is open,
358 * and removed when it is closed.
359 */
360 containerClassName: _propTypes["default"].string,
361
362 /**
363 * Close the modal when escape key is pressed
364 */
365 keyboard: _propTypes["default"].bool,
366
367 /**
368 * A `react-transition-group@2.0.0` `<Transition/>` component used
369 * to control animations for the dialog component.
370 */
371 transition: _propTypes["default"].elementType,
372
373 /**
374 * A `react-transition-group@2.0.0` `<Transition/>` component used
375 * to control animations for the backdrop components.
376 */
377 backdropTransition: _propTypes["default"].elementType,
378
379 /**
380 * When `true` The modal will automatically shift focus to itself when it opens, and
381 * replace it to the last focused element when it closes. This also
382 * works correctly with any Modal children that have the `autoFocus` prop.
383 *
384 * Generally this should never be set to `false` as it makes the Modal less
385 * accessible to assistive technologies, like screen readers.
386 */
387 autoFocus: _propTypes["default"].bool,
388
389 /**
390 * When `true` The modal will prevent focus from leaving the Modal while open.
391 *
392 * Generally this should never be set to `false` as it makes the Modal less
393 * accessible to assistive technologies, like screen readers.
394 */
395 enforceFocus: _propTypes["default"].bool,
396
397 /**
398 * When `true` The modal will restore focus to previously focused element once
399 * modal is hidden
400 */
401 restoreFocus: _propTypes["default"].bool,
402
403 /**
404 * Options passed to focus function when `restoreFocus` is set to `true`
405 *
406 * @link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#Parameters
407 */
408 restoreFocusOptions: _propTypes["default"].shape({
409 preventScroll: _propTypes["default"].bool
410 }),
411
412 /**
413 * Callback fired before the Modal transitions in
414 */
415 onEnter: _propTypes["default"].func,
416
417 /**
418 * Callback fired as the Modal begins to transition in
419 */
420 onEntering: _propTypes["default"].func,
421
422 /**
423 * Callback fired after the Modal finishes transitioning in
424 */
425 onEntered: _propTypes["default"].func,
426
427 /**
428 * Callback fired right before the Modal transitions out
429 */
430 onExit: _propTypes["default"].func,
431
432 /**
433 * Callback fired as the Modal begins to transition out
434 */
435 onExiting: _propTypes["default"].func,
436
437 /**
438 * Callback fired after the Modal finishes transitioning out
439 */
440 onExited: _propTypes["default"].func,
441
442 /**
443 * A ModalManager instance used to track and manage the state of open
444 * Modals. Useful when customizing how modals interact within a container
445 */
446 manager: _propTypes["default"].instanceOf(_ModalManager["default"])
447};
448Modal.displayName = 'Modal';
449Modal.propTypes = propTypes;
450
451var _default = Object.assign(Modal, {
452 Manager: _ModalManager["default"]
453});
454
455exports["default"] = _default;
456module.exports = exports.default;
\No newline at end of file