UNPKG

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