Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

539 строки
16 KiB

  1. "use strict";
  2. var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
  3. exports.__esModule = true;
  4. exports["default"] = void 0;
  5. var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
  6. var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
  7. var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized"));
  8. var _inheritsLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/inheritsLoose"));
  9. var _activeElement = _interopRequireDefault(require("dom-helpers/activeElement"));
  10. var _contains = _interopRequireDefault(require("dom-helpers/contains"));
  11. var _canUseDOM = _interopRequireDefault(require("dom-helpers/canUseDOM"));
  12. var _listen = _interopRequireDefault(require("dom-helpers/listen"));
  13. var _propTypes = _interopRequireDefault(require("prop-types"));
  14. var _react = _interopRequireDefault(require("react"));
  15. var _reactDom = _interopRequireDefault(require("react-dom"));
  16. var _ModalManager = _interopRequireDefault(require("./ModalManager"));
  17. var _ownerDocument = _interopRequireDefault(require("./utils/ownerDocument"));
  18. var _useWaitForDOMRef = _interopRequireDefault(require("./utils/useWaitForDOMRef"));
  19. /* eslint-disable react/prop-types */
  20. function omitProps(props, propTypes) {
  21. var keys = Object.keys(props);
  22. var newProps = {};
  23. keys.forEach(function (prop) {
  24. if (!Object.prototype.hasOwnProperty.call(propTypes, prop)) {
  25. newProps[prop] = props[prop];
  26. }
  27. });
  28. return newProps;
  29. }
  30. var manager;
  31. /**
  32. * Love them or hate them, `<Modal />` provides a solid foundation for creating dialogs, lightboxes, or whatever else.
  33. * The Modal component renders its `children` node in front of a backdrop component.
  34. *
  35. * The Modal offers a few helpful features over using just a `<Portal/>` component and some styles:
  36. *
  37. * - Manages dialog stacking when one-at-a-time just isn't enough.
  38. * - Creates a backdrop, for disabling interaction below the modal.
  39. * - It properly manages focus; moving to the modal content, and keeping it there until the modal is closed.
  40. * - It disables scrolling of the page content while open.
  41. * - Adds the appropriate ARIA roles are automatically.
  42. * - Easily pluggable animations via a `<Transition/>` component.
  43. *
  44. * Note that, in the same way the backdrop element prevents users from clicking or interacting
  45. * with the page content underneath the Modal, Screen readers also need to be signaled to not to
  46. * interact with page content while the Modal is open. To do this, we use a common technique of applying
  47. * the `aria-hidden='true'` attribute to the non-Modal elements in the Modal `container`. This means that for
  48. * a Modal to be truly modal, it should have a `container` that is _outside_ your app's
  49. * React hierarchy (such as the default: document.body).
  50. */
  51. var Modal =
  52. /*#__PURE__*/
  53. function (_React$Component) {
  54. (0, _inheritsLoose2["default"])(Modal, _React$Component);
  55. function Modal() {
  56. var _this;
  57. for (var _len = arguments.length, _args = new Array(_len), _key = 0; _key < _len; _key++) {
  58. _args[_key] = arguments[_key];
  59. }
  60. _this = _React$Component.call.apply(_React$Component, [this].concat(_args)) || this;
  61. _this.state = {
  62. exited: !_this.props.show
  63. };
  64. _this.onShow = function () {
  65. var _this$props = _this.props,
  66. container = _this$props.container,
  67. containerClassName = _this$props.containerClassName,
  68. onShow = _this$props.onShow;
  69. _this.getModalManager().add((0, _assertThisInitialized2["default"])(_this), container, containerClassName);
  70. _this.removeKeydownListener = (0, _listen["default"])(document, 'keydown', _this.handleDocumentKeyDown);
  71. _this.removeFocusListener = (0, _listen["default"])(document, 'focus', // the timeout is necessary b/c this will run before the new modal is mounted
  72. // and so steals focus from it
  73. function () {
  74. return setTimeout(_this.enforceFocus);
  75. }, true);
  76. if (onShow) {
  77. onShow();
  78. } // autofocus after onShow, to not trigger a focus event for previous
  79. // modals before this one is shown.
  80. _this.autoFocus();
  81. };
  82. _this.onHide = function () {
  83. _this.getModalManager().remove((0, _assertThisInitialized2["default"])(_this));
  84. _this.removeKeydownListener();
  85. _this.removeFocusListener();
  86. if (_this.props.restoreFocus) {
  87. _this.restoreLastFocus();
  88. }
  89. };
  90. _this.setDialogRef = function (ref) {
  91. _this.dialog = ref;
  92. };
  93. _this.setBackdropRef = function (ref) {
  94. _this.backdrop = ref && _reactDom["default"].findDOMNode(ref);
  95. };
  96. _this.handleHidden = function () {
  97. _this.setState({
  98. exited: true
  99. });
  100. _this.onHide();
  101. if (_this.props.onExited) {
  102. var _this$props2;
  103. (_this$props2 = _this.props).onExited.apply(_this$props2, arguments);
  104. }
  105. };
  106. _this.handleBackdropClick = function (e) {
  107. if (e.target !== e.currentTarget) {
  108. return;
  109. }
  110. if (_this.props.onBackdropClick) {
  111. _this.props.onBackdropClick(e);
  112. }
  113. if (_this.props.backdrop === true) {
  114. _this.props.onHide();
  115. }
  116. };
  117. _this.handleDocumentKeyDown = function (e) {
  118. if (_this.props.keyboard && e.keyCode === 27 && _this.isTopModal()) {
  119. if (_this.props.onEscapeKeyDown) {
  120. _this.props.onEscapeKeyDown(e);
  121. }
  122. _this.props.onHide();
  123. }
  124. };
  125. _this.enforceFocus = function () {
  126. if (!_this.props.enforceFocus || !_this._isMounted || !_this.isTopModal()) {
  127. return;
  128. }
  129. var currentActiveElement = (0, _activeElement["default"])((0, _ownerDocument["default"])((0, _assertThisInitialized2["default"])(_this)));
  130. if (_this.dialog && !(0, _contains["default"])(_this.dialog, currentActiveElement)) {
  131. _this.dialog.focus();
  132. }
  133. };
  134. _this.renderBackdrop = function () {
  135. var _this$props3 = _this.props,
  136. renderBackdrop = _this$props3.renderBackdrop,
  137. Transition = _this$props3.backdropTransition;
  138. var backdrop = renderBackdrop({
  139. ref: _this.setBackdropRef,
  140. onClick: _this.handleBackdropClick
  141. });
  142. if (Transition) {
  143. backdrop = _react["default"].createElement(Transition, {
  144. appear: true,
  145. "in": _this.props.show
  146. }, backdrop);
  147. }
  148. return backdrop;
  149. };
  150. return _this;
  151. }
  152. Modal.getDerivedStateFromProps = function getDerivedStateFromProps(nextProps) {
  153. if (nextProps.show) {
  154. return {
  155. exited: false
  156. };
  157. }
  158. if (!nextProps.transition) {
  159. // Otherwise let handleHidden take care of marking exited.
  160. return {
  161. exited: true
  162. };
  163. }
  164. return null;
  165. };
  166. var _proto = Modal.prototype;
  167. _proto.componentDidMount = function componentDidMount() {
  168. this._isMounted = true;
  169. if (this.props.show) {
  170. this.onShow();
  171. }
  172. };
  173. _proto.componentDidUpdate = function componentDidUpdate(prevProps) {
  174. var transition = this.props.transition;
  175. if (prevProps.show && !this.props.show && !transition) {
  176. // Otherwise handleHidden will call this.
  177. this.onHide();
  178. } else if (!prevProps.show && this.props.show) {
  179. this.onShow();
  180. }
  181. };
  182. _proto.componentWillUnmount = function componentWillUnmount() {
  183. var _this$props4 = this.props,
  184. show = _this$props4.show,
  185. transition = _this$props4.transition;
  186. this._isMounted = false;
  187. if (show || transition && !this.state.exited) {
  188. this.onHide();
  189. }
  190. };
  191. _proto.getSnapshotBeforeUpdate = function getSnapshotBeforeUpdate(prevProps) {
  192. if (_canUseDOM["default"] && !prevProps.show && this.props.show) {
  193. this.lastFocus = (0, _activeElement["default"])();
  194. }
  195. return null;
  196. };
  197. _proto.getModalManager = function getModalManager() {
  198. if (this.props.manager) {
  199. return this.props.manager;
  200. }
  201. if (!manager) {
  202. manager = new _ModalManager["default"]();
  203. }
  204. return manager;
  205. };
  206. _proto.restoreLastFocus = function restoreLastFocus() {
  207. // Support: <=IE11 doesn't support `focus()` on svg elements (RB: #917)
  208. if (this.lastFocus && this.lastFocus.focus) {
  209. this.lastFocus.focus(this.props.restoreFocusOptions);
  210. this.lastFocus = null;
  211. }
  212. };
  213. _proto.autoFocus = function autoFocus() {
  214. if (!this.props.autoFocus) return;
  215. var currentActiveElement = (0, _activeElement["default"])((0, _ownerDocument["default"])(this));
  216. if (this.dialog && !(0, _contains["default"])(this.dialog, currentActiveElement)) {
  217. this.lastFocus = currentActiveElement;
  218. this.dialog.focus();
  219. }
  220. };
  221. _proto.isTopModal = function isTopModal() {
  222. return this.getModalManager().isTopModal(this);
  223. };
  224. _proto.render = function render() {
  225. var _this$props5 = this.props,
  226. show = _this$props5.show,
  227. container = _this$props5.container,
  228. children = _this$props5.children,
  229. renderDialog = _this$props5.renderDialog,
  230. _this$props5$role = _this$props5.role,
  231. role = _this$props5$role === void 0 ? 'dialog' : _this$props5$role,
  232. Transition = _this$props5.transition,
  233. backdrop = _this$props5.backdrop,
  234. className = _this$props5.className,
  235. style = _this$props5.style,
  236. onExit = _this$props5.onExit,
  237. onExiting = _this$props5.onExiting,
  238. onEnter = _this$props5.onEnter,
  239. onEntering = _this$props5.onEntering,
  240. onEntered = _this$props5.onEntered,
  241. props = (0, _objectWithoutPropertiesLoose2["default"])(_this$props5, ["show", "container", "children", "renderDialog", "role", "transition", "backdrop", "className", "style", "onExit", "onExiting", "onEnter", "onEntering", "onEntered"]);
  242. if (!(show || Transition && !this.state.exited)) {
  243. return null;
  244. }
  245. var dialogProps = (0, _extends2["default"])({
  246. role: role,
  247. ref: this.setDialogRef,
  248. // apparently only works on the dialog role element
  249. 'aria-modal': role === 'dialog' ? true : undefined
  250. }, omitProps(props, Modal.propTypes), {
  251. style: style,
  252. className: className,
  253. tabIndex: '-1'
  254. });
  255. var dialog = renderDialog ? renderDialog(dialogProps) : _react["default"].createElement("div", dialogProps, _react["default"].cloneElement(children, {
  256. role: 'document'
  257. }));
  258. if (Transition) {
  259. dialog = _react["default"].createElement(Transition, {
  260. appear: true,
  261. unmountOnExit: true,
  262. "in": show,
  263. onExit: onExit,
  264. onExiting: onExiting,
  265. onExited: this.handleHidden,
  266. onEnter: onEnter,
  267. onEntering: onEntering,
  268. onEntered: onEntered
  269. }, dialog);
  270. }
  271. return _reactDom["default"].createPortal(_react["default"].createElement(_react["default"].Fragment, null, backdrop && this.renderBackdrop(), dialog), container);
  272. };
  273. return Modal;
  274. }(_react["default"].Component); // dumb HOC for the sake react-docgen
  275. Modal.propTypes = {
  276. /**
  277. * Set the visibility of the Modal
  278. */
  279. show: _propTypes["default"].bool,
  280. /**
  281. * A DOM element, a `ref` to an element, or function that returns either. The Modal is appended to it's `container` element.
  282. *
  283. * For the sake of assistive technologies, the container should usually be the document body, so that the rest of the
  284. * page content can be placed behind a virtual backdrop as well as a visual one.
  285. */
  286. container: _propTypes["default"].any,
  287. /**
  288. * A callback fired when the Modal is opening.
  289. */
  290. onShow: _propTypes["default"].func,
  291. /**
  292. * A callback fired when either the backdrop is clicked, or the escape key is pressed.
  293. *
  294. * The `onHide` callback only signals intent from the Modal,
  295. * you must actually set the `show` prop to `false` for the Modal to close.
  296. */
  297. onHide: _propTypes["default"].func,
  298. /**
  299. * Include a backdrop component.
  300. */
  301. backdrop: _propTypes["default"].oneOfType([_propTypes["default"].bool, _propTypes["default"].oneOf(['static'])]),
  302. /**
  303. * A function that returns the dialog component. Useful for custom
  304. * rendering. **Note:** the component should make sure to apply the provided ref.
  305. *
  306. * ```js
  307. * renderDialog={props => <MyDialog {...props} />}
  308. * ```
  309. */
  310. renderDialog: _propTypes["default"].func,
  311. /**
  312. * A function that returns a backdrop component. Useful for custom
  313. * backdrop rendering.
  314. *
  315. * ```js
  316. * renderBackdrop={props => <MyBackdrop {...props} />}
  317. * ```
  318. */
  319. renderBackdrop: _propTypes["default"].func,
  320. /**
  321. * A callback fired when the escape key, if specified in `keyboard`, is pressed.
  322. */
  323. onEscapeKeyDown: _propTypes["default"].func,
  324. /**
  325. * A callback fired when the backdrop, if specified, is clicked.
  326. */
  327. onBackdropClick: _propTypes["default"].func,
  328. /**
  329. * A css class or set of classes applied to the modal container when the modal is open,
  330. * and removed when it is closed.
  331. */
  332. containerClassName: _propTypes["default"].string,
  333. /**
  334. * Close the modal when escape key is pressed
  335. */
  336. keyboard: _propTypes["default"].bool,
  337. /**
  338. * A `react-transition-group@2.0.0` `<Transition/>` component used
  339. * to control animations for the dialog component.
  340. */
  341. transition: _propTypes["default"].elementType,
  342. /**
  343. * A `react-transition-group@2.0.0` `<Transition/>` component used
  344. * to control animations for the backdrop components.
  345. */
  346. backdropTransition: _propTypes["default"].elementType,
  347. /**
  348. * When `true` The modal will automatically shift focus to itself when it opens, and
  349. * replace it to the last focused element when it closes. This also
  350. * works correctly with any Modal children that have the `autoFocus` prop.
  351. *
  352. * Generally this should never be set to `false` as it makes the Modal less
  353. * accessible to assistive technologies, like screen readers.
  354. */
  355. autoFocus: _propTypes["default"].bool,
  356. /**
  357. * When `true` The modal will prevent focus from leaving the Modal while open.
  358. *
  359. * Generally this should never be set to `false` as it makes the Modal less
  360. * accessible to assistive technologies, like screen readers.
  361. */
  362. enforceFocus: _propTypes["default"].bool,
  363. /**
  364. * When `true` The modal will restore focus to previously focused element once
  365. * modal is hidden
  366. */
  367. restoreFocus: _propTypes["default"].bool,
  368. /**
  369. * Options passed to focus function when `restoreFocus` is set to `true`
  370. *
  371. * @link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#Parameters
  372. */
  373. restoreFocusOptions: _propTypes["default"].shape({
  374. preventScroll: _propTypes["default"].bool
  375. }),
  376. /**
  377. * Callback fired before the Modal transitions in
  378. */
  379. onEnter: _propTypes["default"].func,
  380. /**
  381. * Callback fired as the Modal begins to transition in
  382. */
  383. onEntering: _propTypes["default"].func,
  384. /**
  385. * Callback fired after the Modal finishes transitioning in
  386. */
  387. onEntered: _propTypes["default"].func,
  388. /**
  389. * Callback fired right before the Modal transitions out
  390. */
  391. onExit: _propTypes["default"].func,
  392. /**
  393. * Callback fired as the Modal begins to transition out
  394. */
  395. onExiting: _propTypes["default"].func,
  396. /**
  397. * Callback fired after the Modal finishes transitioning out
  398. */
  399. onExited: _propTypes["default"].func,
  400. /**
  401. * A ModalManager instance used to track and manage the state of open
  402. * Modals. Useful when customizing how modals interact within a container
  403. */
  404. manager: _propTypes["default"].object
  405. };
  406. Modal.defaultProps = {
  407. show: false,
  408. role: 'dialog',
  409. backdrop: true,
  410. keyboard: true,
  411. autoFocus: true,
  412. enforceFocus: true,
  413. restoreFocus: true,
  414. onHide: function onHide() {},
  415. renderBackdrop: function renderBackdrop(props) {
  416. return _react["default"].createElement("div", props);
  417. }
  418. };
  419. function forwardRef(Component) {
  420. // eslint-disable-next-line react/display-name
  421. var ModalWithContainer = _react["default"].forwardRef(function (props, ref) {
  422. var resolved = (0, _useWaitForDOMRef["default"])(props.container);
  423. return resolved ? _react["default"].createElement(Component, (0, _extends2["default"])({}, props, {
  424. ref: ref,
  425. container: resolved
  426. })) : null;
  427. });
  428. ModalWithContainer.Manager = _ModalManager["default"];
  429. ModalWithContainer._Inner = Component;
  430. return ModalWithContainer;
  431. }
  432. var ModalWithContainer = forwardRef(Modal);
  433. ModalWithContainer.Manager = _ModalManager["default"];
  434. var _default = ModalWithContainer;
  435. exports["default"] = _default;
  436. module.exports = exports.default;