"use strict"; 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 _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized")); var _inheritsLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/inheritsLoose")); 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 = _interopRequireDefault(require("react")); var _reactDom = _interopRequireDefault(require("react-dom")); var _ModalManager = _interopRequireDefault(require("./ModalManager")); var _ownerDocument = _interopRequireDefault(require("./utils/ownerDocument")); var _useWaitForDOMRef = _interopRequireDefault(require("./utils/useWaitForDOMRef")); /* eslint-disable react/prop-types */ function omitProps(props, propTypes) { var keys = Object.keys(props); var newProps = {}; keys.forEach(function (prop) { if (!Object.prototype.hasOwnProperty.call(propTypes, prop)) { newProps[prop] = props[prop]; } }); return newProps; } var manager; /** * Love them or hate them, `` provides a solid foundation for creating dialogs, lightboxes, or whatever else. * The Modal component renders its `children` node in front of a backdrop component. * * The Modal offers a few helpful features over using just a `` component and some styles: * * - Manages dialog stacking when one-at-a-time just isn't enough. * - Creates a backdrop, for disabling interaction below the modal. * - It properly manages focus; moving to the modal content, and keeping it there until the modal is closed. * - It disables scrolling of the page content while open. * - Adds the appropriate ARIA roles are automatically. * - Easily pluggable animations via a `` component. * * Note that, in the same way the backdrop element prevents users from clicking or interacting * with the page content underneath the Modal, Screen readers also need to be signaled to not to * interact with page content while the Modal is open. To do this, we use a common technique of applying * the `aria-hidden='true'` attribute to the non-Modal elements in the Modal `container`. This means that for * a Modal to be truly modal, it should have a `container` that is _outside_ your app's * React hierarchy (such as the default: document.body). */ var Modal = /*#__PURE__*/function (_React$Component) { (0, _inheritsLoose2["default"])(Modal, _React$Component); function Modal() { var _this; for (var _len = arguments.length, _args = new Array(_len), _key = 0; _key < _len; _key++) { _args[_key] = arguments[_key]; } _this = _React$Component.call.apply(_React$Component, [this].concat(_args)) || this; _this.state = { exited: !_this.props.show }; _this.onShow = function () { var _this$props = _this.props, container = _this$props.container, containerClassName = _this$props.containerClassName, onShow = _this$props.onShow; _this.getModalManager().add((0, _assertThisInitialized2["default"])(_this), container, containerClassName); _this.removeKeydownListener = (0, _listen["default"])(document, 'keydown', _this.handleDocumentKeyDown); _this.removeFocusListener = (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(_this.enforceFocus); }, true); if (onShow) { onShow(); } // autofocus after onShow, to not trigger a focus event for previous // modals before this one is shown. _this.autoFocus(); }; _this.onHide = function () { _this.getModalManager().remove((0, _assertThisInitialized2["default"])(_this)); _this.removeKeydownListener(); _this.removeFocusListener(); if (_this.props.restoreFocus) { _this.restoreLastFocus(); } }; _this.setDialogRef = function (ref) { _this.dialog = ref; }; _this.setBackdropRef = function (ref) { _this.backdrop = ref && _reactDom["default"].findDOMNode(ref); }; _this.handleHidden = function () { _this.setState({ exited: true }); _this.onHide(); if (_this.props.onExited) { var _this$props2; (_this$props2 = _this.props).onExited.apply(_this$props2, arguments); } }; _this.handleBackdropClick = function (e) { if (e.target !== e.currentTarget) { return; } if (_this.props.onBackdropClick) { _this.props.onBackdropClick(e); } if (_this.props.backdrop === true) { _this.props.onHide(); } }; _this.handleDocumentKeyDown = function (e) { if (_this.props.keyboard && e.keyCode === 27 && _this.isTopModal()) { if (_this.props.onEscapeKeyDown) { _this.props.onEscapeKeyDown(e); } _this.props.onHide(); } }; _this.enforceFocus = function () { if (!_this.props.enforceFocus || !_this._isMounted || !_this.isTopModal()) { return; } var currentActiveElement = (0, _activeElement["default"])((0, _ownerDocument["default"])((0, _assertThisInitialized2["default"])(_this))); if (_this.dialog && !(0, _contains["default"])(_this.dialog, currentActiveElement)) { _this.dialog.focus(); } }; _this.renderBackdrop = function () { var _this$props3 = _this.props, renderBackdrop = _this$props3.renderBackdrop, Transition = _this$props3.backdropTransition; var backdrop = renderBackdrop({ ref: _this.setBackdropRef, onClick: _this.handleBackdropClick }); if (Transition) { backdrop = _react["default"].createElement(Transition, { appear: true, "in": _this.props.show }, backdrop); } return backdrop; }; return _this; } Modal.getDerivedStateFromProps = function getDerivedStateFromProps(nextProps) { if (nextProps.show) { return { exited: false }; } if (!nextProps.transition) { // Otherwise let handleHidden take care of marking exited. return { exited: true }; } return null; }; var _proto = Modal.prototype; _proto.componentDidMount = function componentDidMount() { this._isMounted = true; if (this.props.show) { this.onShow(); } }; _proto.componentDidUpdate = function componentDidUpdate(prevProps) { var transition = this.props.transition; if (prevProps.show && !this.props.show && !transition) { // Otherwise handleHidden will call this. this.onHide(); } else if (!prevProps.show && this.props.show) { this.onShow(); } }; _proto.componentWillUnmount = function componentWillUnmount() { var _this$props4 = this.props, show = _this$props4.show, transition = _this$props4.transition; this._isMounted = false; if (show || transition && !this.state.exited) { this.onHide(); } }; _proto.getSnapshotBeforeUpdate = function getSnapshotBeforeUpdate(prevProps) { if (_canUseDOM["default"] && !prevProps.show && this.props.show) { this.lastFocus = (0, _activeElement["default"])(); } return null; }; _proto.getModalManager = function getModalManager() { if (this.props.manager) { return this.props.manager; } if (!manager) { manager = new _ModalManager["default"](); } return manager; }; _proto.restoreLastFocus = function restoreLastFocus() { // Support: <=IE11 doesn't support `focus()` on svg elements (RB: #917) if (this.lastFocus && this.lastFocus.focus) { this.lastFocus.focus(this.props.restoreFocusOptions); this.lastFocus = null; } }; _proto.autoFocus = function autoFocus() { if (!this.props.autoFocus) return; var currentActiveElement = (0, _activeElement["default"])((0, _ownerDocument["default"])(this)); if (this.dialog && !(0, _contains["default"])(this.dialog, currentActiveElement)) { this.lastFocus = currentActiveElement; this.dialog.focus(); } }; _proto.isTopModal = function isTopModal() { return this.getModalManager().isTopModal(this); }; _proto.render = function render() { var _this$props5 = this.props, show = _this$props5.show, container = _this$props5.container, children = _this$props5.children, renderDialog = _this$props5.renderDialog, _this$props5$role = _this$props5.role, role = _this$props5$role === void 0 ? 'dialog' : _this$props5$role, Transition = _this$props5.transition, backdrop = _this$props5.backdrop, className = _this$props5.className, style = _this$props5.style, onExit = _this$props5.onExit, onExiting = _this$props5.onExiting, onEnter = _this$props5.onEnter, onEntering = _this$props5.onEntering, onEntered = _this$props5.onEntered, props = (0, _objectWithoutPropertiesLoose2["default"])(_this$props5, ["show", "container", "children", "renderDialog", "role", "transition", "backdrop", "className", "style", "onExit", "onExiting", "onEnter", "onEntering", "onEntered"]); if (!(show || Transition && !this.state.exited)) { return null; } var dialogProps = (0, _extends2["default"])({ role: role, ref: this.setDialogRef, // apparently only works on the dialog role element 'aria-modal': role === 'dialog' ? true : undefined }, omitProps(props, Modal.propTypes), { style: style, className: className, tabIndex: '-1' }); var dialog = renderDialog ? renderDialog(dialogProps) : _react["default"].createElement("div", dialogProps, _react["default"].cloneElement(children, { role: 'document' })); if (Transition) { dialog = _react["default"].createElement(Transition, { appear: true, unmountOnExit: true, "in": show, onExit: onExit, onExiting: onExiting, onExited: this.handleHidden, onEnter: onEnter, onEntering: onEntering, onEntered: onEntered }, dialog); } return _reactDom["default"].createPortal(_react["default"].createElement(_react["default"].Fragment, null, backdrop && this.renderBackdrop(), dialog), container); }; return Modal; }(_react["default"].Component); // dumb HOC for the sake react-docgen Modal.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 * 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. */ 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"].object }; Modal.defaultProps = { show: false, role: 'dialog', backdrop: true, keyboard: true, autoFocus: true, enforceFocus: true, restoreFocus: true, onHide: function onHide() {}, renderBackdrop: function renderBackdrop(props) { return _react["default"].createElement("div", props); } }; function forwardRef(Component) { // eslint-disable-next-line react/display-name var ModalWithContainer = _react["default"].forwardRef(function (props, ref) { var resolved = (0, _useWaitForDOMRef["default"])(props.container); return resolved ? _react["default"].createElement(Component, (0, _extends2["default"])({}, props, { ref: ref, container: resolved })) : null; }); ModalWithContainer.Manager = _ModalManager["default"]; ModalWithContainer._Inner = Component; return ModalWithContainer; } var ModalWithContainer = forwardRef(Modal); ModalWithContainer.Manager = _ModalManager["default"]; var _default = ModalWithContainer; exports["default"] = _default; module.exports = exports.default;