"use strict"; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports["default"] = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose")); var _activeElement = _interopRequireDefault(require("dom-helpers/activeElement")); var _contains = _interopRequireDefault(require("dom-helpers/contains")); var _canUseDOM = _interopRequireDefault(require("dom-helpers/canUseDOM")); var _listen = _interopRequireDefault(require("dom-helpers/listen")); var _propTypes = _interopRequireDefault(require("prop-types")); var _react = _interopRequireWildcard(require("react")); var _reactDom = _interopRequireDefault(require("react-dom")); var _useMounted = _interopRequireDefault(require("@restart/hooks/useMounted")); var _useWillUnmount = _interopRequireDefault(require("@restart/hooks/useWillUnmount")); var _usePrevious = _interopRequireDefault(require("@restart/hooks/usePrevious")); var _useEventCallback = _interopRequireDefault(require("@restart/hooks/useEventCallback")); var _ModalManager = _interopRequireDefault(require("./ModalManager")); var _useWaitForDOMRef = _interopRequireDefault(require("./useWaitForDOMRef")); /* eslint-disable @typescript-eslint/no-use-before-define, react/prop-types */ var manager; function getManager() { if (!manager) manager = new _ModalManager["default"](); return manager; } function useModalManager(provided) { var modalManager = provided || getManager(); var modal = (0, _react.useRef)({ dialog: null, backdrop: null }); return Object.assign(modal.current, { add: function add(container, className) { return modalManager.add(modal.current, container, className); }, remove: function remove() { return modalManager.remove(modal.current); }, isTopModal: function isTopModal() { return modalManager.isTopModal(modal.current); }, setDialogRef: (0, _react.useCallback)(function (ref) { modal.current.dialog = ref; }, []), setBackdropRef: (0, _react.useCallback)(function (ref) { modal.current.backdrop = ref; }, []) }); } var Modal = /*#__PURE__*/(0, _react.forwardRef)(function (_ref, ref) { var _ref$show = _ref.show, show = _ref$show === void 0 ? false : _ref$show, _ref$role = _ref.role, role = _ref$role === void 0 ? 'dialog' : _ref$role, className = _ref.className, style = _ref.style, children = _ref.children, _ref$backdrop = _ref.backdrop, backdrop = _ref$backdrop === void 0 ? true : _ref$backdrop, _ref$keyboard = _ref.keyboard, keyboard = _ref$keyboard === void 0 ? true : _ref$keyboard, onBackdropClick = _ref.onBackdropClick, onEscapeKeyDown = _ref.onEscapeKeyDown, transition = _ref.transition, backdropTransition = _ref.backdropTransition, _ref$autoFocus = _ref.autoFocus, autoFocus = _ref$autoFocus === void 0 ? true : _ref$autoFocus, _ref$enforceFocus = _ref.enforceFocus, enforceFocus = _ref$enforceFocus === void 0 ? true : _ref$enforceFocus, _ref$restoreFocus = _ref.restoreFocus, restoreFocus = _ref$restoreFocus === void 0 ? true : _ref$restoreFocus, restoreFocusOptions = _ref.restoreFocusOptions, renderDialog = _ref.renderDialog, _ref$renderBackdrop = _ref.renderBackdrop, renderBackdrop = _ref$renderBackdrop === void 0 ? function (props) { return /*#__PURE__*/_react["default"].createElement("div", props); } : _ref$renderBackdrop, providedManager = _ref.manager, containerRef = _ref.container, containerClassName = _ref.containerClassName, onShow = _ref.onShow, _ref$onHide = _ref.onHide, onHide = _ref$onHide === void 0 ? function () {} : _ref$onHide, onExit = _ref.onExit, onExited = _ref.onExited, onExiting = _ref.onExiting, onEnter = _ref.onEnter, onEntering = _ref.onEntering, onEntered = _ref.onEntered, 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"]); var container = (0, _useWaitForDOMRef["default"])(containerRef); var modal = useModalManager(providedManager); var isMounted = (0, _useMounted["default"])(); var prevShow = (0, _usePrevious["default"])(show); var _useState = (0, _react.useState)(!show), exited = _useState[0], setExited = _useState[1]; var lastFocusRef = (0, _react.useRef)(null); (0, _react.useImperativeHandle)(ref, function () { return modal; }, [modal]); if (_canUseDOM["default"] && !prevShow && show) { lastFocusRef.current = (0, _activeElement["default"])(); } if (!transition && !show && !exited) { setExited(true); } else if (show && exited) { setExited(false); } var handleShow = (0, _useEventCallback["default"])(function () { modal.add(container, containerClassName); removeKeydownListenerRef.current = (0, _listen["default"])(document, 'keydown', handleDocumentKeyDown); removeFocusListenerRef.current = (0, _listen["default"])(document, 'focus', // the timeout is necessary b/c this will run before the new modal is mounted // and so steals focus from it function () { return setTimeout(handleEnforceFocus); }, true); if (onShow) { onShow(); } // autofocus after onShow to not trigger a focus event for previous // modals before this one is shown. if (autoFocus) { var currentActiveElement = (0, _activeElement["default"])(document); if (modal.dialog && currentActiveElement && !(0, _contains["default"])(modal.dialog, currentActiveElement)) { lastFocusRef.current = currentActiveElement; modal.dialog.focus(); } } }); var handleHide = (0, _useEventCallback["default"])(function () { modal.remove(); removeKeydownListenerRef.current == null ? void 0 : removeKeydownListenerRef.current(); removeFocusListenerRef.current == null ? void 0 : removeFocusListenerRef.current(); if (restoreFocus) { var _lastFocusRef$current; // Support: <=IE11 doesn't support `focus()` on svg elements (RB: #917) (_lastFocusRef$current = lastFocusRef.current) == null ? void 0 : _lastFocusRef$current.focus == null ? void 0 : _lastFocusRef$current.focus(restoreFocusOptions); lastFocusRef.current = null; } }); // TODO: try and combine these effects: https://github.com/react-bootstrap/react-overlays/pull/794#discussion_r409954120 // Show logic when: // - show is `true` _and_ `container` has resolved (0, _react.useEffect)(function () { if (!show || !container) return; handleShow(); }, [show, container, /* should never change: */ handleShow]); // Hide cleanup logic when: // - `exited` switches to true // - component unmounts; (0, _react.useEffect)(function () { if (!exited) return; handleHide(); }, [exited, handleHide]); (0, _useWillUnmount["default"])(function () { handleHide(); }); // -------------------------------- var handleEnforceFocus = (0, _useEventCallback["default"])(function () { if (!enforceFocus || !isMounted() || !modal.isTopModal()) { return; } var currentActiveElement = (0, _activeElement["default"])(); if (modal.dialog && currentActiveElement && !(0, _contains["default"])(modal.dialog, currentActiveElement)) { modal.dialog.focus(); } }); var handleBackdropClick = (0, _useEventCallback["default"])(function (e) { if (e.target !== e.currentTarget) { return; } onBackdropClick == null ? void 0 : onBackdropClick(e); if (backdrop === true) { onHide(); } }); var handleDocumentKeyDown = (0, _useEventCallback["default"])(function (e) { if (keyboard && e.keyCode === 27 && modal.isTopModal()) { onEscapeKeyDown == null ? void 0 : onEscapeKeyDown(e); if (!e.defaultPrevented) { onHide(); } } }); var removeFocusListenerRef = (0, _react.useRef)(); var removeKeydownListenerRef = (0, _react.useRef)(); var handleHidden = function handleHidden() { setExited(true); for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } onExited == null ? void 0 : onExited.apply(void 0, args); }; var Transition = transition; if (!container || !(show || Transition && !exited)) { return null; } var dialogProps = (0, _extends2["default"])({ role: role, ref: modal.setDialogRef, // apparently only works on the dialog role element 'aria-modal': role === 'dialog' ? true : undefined }, rest, { style: style, className: className, tabIndex: -1 }); var dialog = renderDialog ? renderDialog(dialogProps) : /*#__PURE__*/_react["default"].createElement("div", dialogProps, /*#__PURE__*/_react["default"].cloneElement(children, { role: 'document' })); if (Transition) { dialog = /*#__PURE__*/_react["default"].createElement(Transition, { appear: true, unmountOnExit: true, "in": !!show, onExit: onExit, onExiting: onExiting, onExited: handleHidden, onEnter: onEnter, onEntering: onEntering, onEntered: onEntered }, dialog); } var backdropElement = null; if (backdrop) { var BackdropTransition = backdropTransition; backdropElement = renderBackdrop({ ref: modal.setBackdropRef, onClick: handleBackdropClick }); if (BackdropTransition) { backdropElement = /*#__PURE__*/_react["default"].createElement(BackdropTransition, { appear: true, "in": !!show }, backdropElement); } } return /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_reactDom["default"].createPortal( /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, backdropElement, dialog), container)); }); var propTypes = { /** * Set the visibility of the Modal */ show: _propTypes["default"].bool, /** * A DOM element, a `ref` to an element, or function that returns either. The Modal is appended to it's `container` element. * * For the sake of assistive technologies, the container should usually be the document body, so that the rest of the * page content can be placed behind a virtual backdrop as well as a visual one. */ container: _propTypes["default"].any, /** * A callback fired when the Modal is opening. */ onShow: _propTypes["default"].func, /** * A callback fired when either the backdrop is clicked, or the escape key is pressed. * * The `onHide` callback only signals intent from the Modal, * you must actually set the `show` prop to `false` for the Modal to close. */ onHide: _propTypes["default"].func, /** * Include a backdrop component. */ backdrop: _propTypes["default"].oneOfType([_propTypes["default"].bool, _propTypes["default"].oneOf(['static'])]), /** * A function that returns the dialog component. Useful for custom * rendering. **Note:** the component should make sure to apply the provided ref. * * ```js static * renderDialog={props => } * ``` */ renderDialog: _propTypes["default"].func, /** * A function that returns a backdrop component. Useful for custom * backdrop rendering. * * ```js * renderBackdrop={props => } * ``` */ renderBackdrop: _propTypes["default"].func, /** * A callback fired when the escape key, if specified in `keyboard`, is pressed. * * If preventDefault() is called on the keyboard event, closing the modal will be cancelled. */ onEscapeKeyDown: _propTypes["default"].func, /** * A callback fired when the backdrop, if specified, is clicked. */ onBackdropClick: _propTypes["default"].func, /** * A css class or set of classes applied to the modal container when the modal is open, * and removed when it is closed. */ containerClassName: _propTypes["default"].string, /** * Close the modal when escape key is pressed */ keyboard: _propTypes["default"].bool, /** * A `react-transition-group@2.0.0` `` component used * to control animations for the dialog component. */ transition: _propTypes["default"].elementType, /** * A `react-transition-group@2.0.0` `` component used * to control animations for the backdrop components. */ backdropTransition: _propTypes["default"].elementType, /** * When `true` The modal will automatically shift focus to itself when it opens, and * replace it to the last focused element when it closes. This also * works correctly with any Modal children that have the `autoFocus` prop. * * Generally this should never be set to `false` as it makes the Modal less * accessible to assistive technologies, like screen readers. */ autoFocus: _propTypes["default"].bool, /** * When `true` The modal will prevent focus from leaving the Modal while open. * * Generally this should never be set to `false` as it makes the Modal less * accessible to assistive technologies, like screen readers. */ enforceFocus: _propTypes["default"].bool, /** * When `true` The modal will restore focus to previously focused element once * modal is hidden */ restoreFocus: _propTypes["default"].bool, /** * Options passed to focus function when `restoreFocus` is set to `true` * * @link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#Parameters */ restoreFocusOptions: _propTypes["default"].shape({ preventScroll: _propTypes["default"].bool }), /** * Callback fired before the Modal transitions in */ onEnter: _propTypes["default"].func, /** * Callback fired as the Modal begins to transition in */ onEntering: _propTypes["default"].func, /** * Callback fired after the Modal finishes transitioning in */ onEntered: _propTypes["default"].func, /** * Callback fired right before the Modal transitions out */ onExit: _propTypes["default"].func, /** * Callback fired as the Modal begins to transition out */ onExiting: _propTypes["default"].func, /** * Callback fired after the Modal finishes transitioning out */ onExited: _propTypes["default"].func, /** * A ModalManager instance used to track and manage the state of open * Modals. Useful when customizing how modals interact within a container */ manager: _propTypes["default"].instanceOf(_ModalManager["default"]) }; Modal.displayName = 'Modal'; Modal.propTypes = propTypes; var _default = Object.assign(Modal, { Manager: _ModalManager["default"] }); exports["default"] = _default; module.exports = exports.default;