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

363 строки
12 KiB

  1. import _objectSpread from "@babel/runtime/helpers/esm/objectSpread";
  2. import _extends from "@babel/runtime/helpers/esm/extends";
  3. import _inheritsLoose from "@babel/runtime/helpers/esm/inheritsLoose";
  4. import _assertThisInitialized from "@babel/runtime/helpers/esm/assertThisInitialized";
  5. import React from 'react';
  6. import PropTypes from 'prop-types';
  7. import classNames from 'classnames';
  8. import Portal from './Portal';
  9. import Fade from './Fade';
  10. import { getOriginalBodyPadding, conditionallyUpdateScrollbar, setScrollbarWidth, mapToCssModules, omit, focusableElements, TransitionTimeouts } from './utils';
  11. function noop() {}
  12. var FadePropTypes = PropTypes.shape(Fade.propTypes);
  13. var propTypes = {
  14. isOpen: PropTypes.bool,
  15. autoFocus: PropTypes.bool,
  16. centered: PropTypes.bool,
  17. size: PropTypes.string,
  18. toggle: PropTypes.func,
  19. keyboard: PropTypes.bool,
  20. role: PropTypes.string,
  21. labelledBy: PropTypes.string,
  22. backdrop: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['static'])]),
  23. onEnter: PropTypes.func,
  24. onExit: PropTypes.func,
  25. onOpened: PropTypes.func,
  26. onClosed: PropTypes.func,
  27. children: PropTypes.node,
  28. className: PropTypes.string,
  29. wrapClassName: PropTypes.string,
  30. modalClassName: PropTypes.string,
  31. backdropClassName: PropTypes.string,
  32. contentClassName: PropTypes.string,
  33. external: PropTypes.node,
  34. fade: PropTypes.bool,
  35. cssModule: PropTypes.object,
  36. zIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  37. backdropTransition: FadePropTypes,
  38. modalTransition: FadePropTypes,
  39. innerRef: PropTypes.oneOfType([PropTypes.object, PropTypes.string, PropTypes.func])
  40. };
  41. var propsToOmit = Object.keys(propTypes);
  42. var defaultProps = {
  43. isOpen: false,
  44. autoFocus: true,
  45. centered: false,
  46. role: 'dialog',
  47. backdrop: true,
  48. keyboard: true,
  49. zIndex: 1050,
  50. fade: true,
  51. onOpened: noop,
  52. onClosed: noop,
  53. modalTransition: {
  54. timeout: TransitionTimeouts.Modal
  55. },
  56. backdropTransition: {
  57. mountOnEnter: true,
  58. timeout: TransitionTimeouts.Fade // uses standard fade transition
  59. }
  60. };
  61. var Modal =
  62. /*#__PURE__*/
  63. function (_React$Component) {
  64. _inheritsLoose(Modal, _React$Component);
  65. function Modal(props) {
  66. var _this;
  67. _this = _React$Component.call(this, props) || this;
  68. _this._element = null;
  69. _this._originalBodyPadding = null;
  70. _this.getFocusableChildren = _this.getFocusableChildren.bind(_assertThisInitialized(_assertThisInitialized(_this)));
  71. _this.handleBackdropClick = _this.handleBackdropClick.bind(_assertThisInitialized(_assertThisInitialized(_this)));
  72. _this.handleBackdropMouseDown = _this.handleBackdropMouseDown.bind(_assertThisInitialized(_assertThisInitialized(_this)));
  73. _this.handleEscape = _this.handleEscape.bind(_assertThisInitialized(_assertThisInitialized(_this)));
  74. _this.handleTab = _this.handleTab.bind(_assertThisInitialized(_assertThisInitialized(_this)));
  75. _this.onOpened = _this.onOpened.bind(_assertThisInitialized(_assertThisInitialized(_this)));
  76. _this.onClosed = _this.onClosed.bind(_assertThisInitialized(_assertThisInitialized(_this)));
  77. _this.state = {
  78. isOpen: props.isOpen
  79. };
  80. if (props.isOpen) {
  81. _this.init();
  82. }
  83. return _this;
  84. }
  85. var _proto = Modal.prototype;
  86. _proto.componentDidMount = function componentDidMount() {
  87. if (this.props.onEnter) {
  88. this.props.onEnter();
  89. }
  90. if (this.state.isOpen && this.props.autoFocus) {
  91. this.setFocus();
  92. }
  93. this._isMounted = true;
  94. };
  95. _proto.componentWillReceiveProps = function componentWillReceiveProps(nextProps) {
  96. if (nextProps.isOpen && !this.props.isOpen) {
  97. this.setState({
  98. isOpen: nextProps.isOpen
  99. });
  100. }
  101. };
  102. _proto.componentWillUpdate = function componentWillUpdate(nextProps, nextState) {
  103. if (nextState.isOpen && !this.state.isOpen) {
  104. this.init();
  105. }
  106. };
  107. _proto.componentDidUpdate = function componentDidUpdate(prevProps, prevState) {
  108. if (this.props.autoFocus && this.state.isOpen && !prevState.isOpen) {
  109. this.setFocus();
  110. }
  111. if (this._element && prevProps.zIndex !== this.props.zIndex) {
  112. this._element.style.zIndex = this.props.zIndex;
  113. }
  114. };
  115. _proto.componentWillUnmount = function componentWillUnmount() {
  116. if (this.props.onExit) {
  117. this.props.onExit();
  118. }
  119. if (this.state.isOpen) {
  120. this.destroy();
  121. }
  122. this._isMounted = false;
  123. };
  124. _proto.onOpened = function onOpened(node, isAppearing) {
  125. this.props.onOpened();
  126. (this.props.modalTransition.onEntered || noop)(node, isAppearing);
  127. };
  128. _proto.onClosed = function onClosed(node) {
  129. // so all methods get called before it is unmounted
  130. this.props.onClosed();
  131. (this.props.modalTransition.onExited || noop)(node);
  132. this.destroy();
  133. if (this._isMounted) {
  134. this.setState({
  135. isOpen: false
  136. });
  137. }
  138. };
  139. _proto.setFocus = function setFocus() {
  140. if (this._dialog && this._dialog.parentNode && typeof this._dialog.parentNode.focus === 'function') {
  141. this._dialog.parentNode.focus();
  142. }
  143. };
  144. _proto.getFocusableChildren = function getFocusableChildren() {
  145. return this._element.querySelectorAll(focusableElements.join(', '));
  146. };
  147. _proto.getFocusedChild = function getFocusedChild() {
  148. var currentFocus;
  149. var focusableChildren = this.getFocusableChildren();
  150. try {
  151. currentFocus = document.activeElement;
  152. } catch (err) {
  153. currentFocus = focusableChildren[0];
  154. }
  155. return currentFocus;
  156. } // not mouseUp because scrollbar fires it, shouldn't close when user scrolls
  157. ;
  158. _proto.handleBackdropClick = function handleBackdropClick(e) {
  159. if (e.target === this._mouseDownElement) {
  160. e.stopPropagation();
  161. if (!this.props.isOpen || this.props.backdrop !== true) return;
  162. var backdrop = this._dialog ? this._dialog.parentNode : null;
  163. if (backdrop && e.target === backdrop && this.props.toggle) {
  164. this.props.toggle(e);
  165. }
  166. }
  167. };
  168. _proto.handleTab = function handleTab(e) {
  169. if (e.which !== 9) return;
  170. var focusableChildren = this.getFocusableChildren();
  171. var totalFocusable = focusableChildren.length;
  172. var currentFocus = this.getFocusedChild();
  173. var focusedIndex = 0;
  174. for (var i = 0; i < totalFocusable; i += 1) {
  175. if (focusableChildren[i] === currentFocus) {
  176. focusedIndex = i;
  177. break;
  178. }
  179. }
  180. if (e.shiftKey && focusedIndex === 0) {
  181. e.preventDefault();
  182. focusableChildren[totalFocusable - 1].focus();
  183. } else if (!e.shiftKey && focusedIndex === totalFocusable - 1) {
  184. e.preventDefault();
  185. focusableChildren[0].focus();
  186. }
  187. };
  188. _proto.handleBackdropMouseDown = function handleBackdropMouseDown(e) {
  189. this._mouseDownElement = e.target;
  190. };
  191. _proto.handleEscape = function handleEscape(e) {
  192. if (this.props.isOpen && this.props.keyboard && e.keyCode === 27 && this.props.toggle) {
  193. e.preventDefault();
  194. e.stopPropagation();
  195. this.props.toggle(e);
  196. }
  197. };
  198. _proto.init = function init() {
  199. try {
  200. this._triggeringElement = document.activeElement;
  201. } catch (err) {
  202. this._triggeringElement = null;
  203. }
  204. this._element = document.createElement('div');
  205. this._element.setAttribute('tabindex', '-1');
  206. this._element.style.position = 'relative';
  207. this._element.style.zIndex = this.props.zIndex;
  208. this._originalBodyPadding = getOriginalBodyPadding();
  209. conditionallyUpdateScrollbar();
  210. document.body.appendChild(this._element);
  211. if (Modal.openCount === 0) {
  212. document.body.className = classNames(document.body.className, mapToCssModules('modal-open', this.props.cssModule));
  213. }
  214. Modal.openCount += 1;
  215. };
  216. _proto.destroy = function destroy() {
  217. if (this._element) {
  218. document.body.removeChild(this._element);
  219. this._element = null;
  220. }
  221. if (this._triggeringElement) {
  222. if (this._triggeringElement.focus) this._triggeringElement.focus();
  223. this._triggeringElement = null;
  224. }
  225. if (Modal.openCount <= 1) {
  226. var modalOpenClassName = mapToCssModules('modal-open', this.props.cssModule); // Use regex to prevent matching `modal-open` as part of a different class, e.g. `my-modal-opened`
  227. var modalOpenClassNameRegex = new RegExp("(^| )" + modalOpenClassName + "( |$)");
  228. document.body.className = document.body.className.replace(modalOpenClassNameRegex, ' ').trim();
  229. }
  230. Modal.openCount -= 1;
  231. setScrollbarWidth(this._originalBodyPadding);
  232. };
  233. _proto.renderModalDialog = function renderModalDialog() {
  234. var _classNames,
  235. _this2 = this;
  236. var attributes = omit(this.props, propsToOmit);
  237. var dialogBaseClass = 'modal-dialog';
  238. return React.createElement("div", _extends({}, attributes, {
  239. className: mapToCssModules(classNames(dialogBaseClass, this.props.className, (_classNames = {}, _classNames["modal-" + this.props.size] = this.props.size, _classNames[dialogBaseClass + "-centered"] = this.props.centered, _classNames)), this.props.cssModule),
  240. role: "document",
  241. ref: function ref(c) {
  242. _this2._dialog = c;
  243. }
  244. }), React.createElement("div", {
  245. className: mapToCssModules(classNames('modal-content', this.props.contentClassName), this.props.cssModule)
  246. }, this.props.children));
  247. };
  248. _proto.render = function render() {
  249. if (this.state.isOpen) {
  250. var _this$props = this.props,
  251. wrapClassName = _this$props.wrapClassName,
  252. modalClassName = _this$props.modalClassName,
  253. backdropClassName = _this$props.backdropClassName,
  254. cssModule = _this$props.cssModule,
  255. isOpen = _this$props.isOpen,
  256. backdrop = _this$props.backdrop,
  257. role = _this$props.role,
  258. labelledBy = _this$props.labelledBy,
  259. external = _this$props.external,
  260. innerRef = _this$props.innerRef;
  261. var modalAttributes = {
  262. onClick: this.handleBackdropClick,
  263. onMouseDown: this.handleBackdropMouseDown,
  264. onKeyUp: this.handleEscape,
  265. onKeyDown: this.handleTab,
  266. style: {
  267. display: 'block'
  268. },
  269. 'aria-labelledby': labelledBy,
  270. role: role,
  271. tabIndex: '-1'
  272. };
  273. var hasTransition = this.props.fade;
  274. var modalTransition = _objectSpread({}, Fade.defaultProps, this.props.modalTransition, {
  275. baseClass: hasTransition ? this.props.modalTransition.baseClass : '',
  276. timeout: hasTransition ? this.props.modalTransition.timeout : 0
  277. });
  278. var backdropTransition = _objectSpread({}, Fade.defaultProps, this.props.backdropTransition, {
  279. baseClass: hasTransition ? this.props.backdropTransition.baseClass : '',
  280. timeout: hasTransition ? this.props.backdropTransition.timeout : 0
  281. });
  282. var Backdrop = backdrop && (hasTransition ? React.createElement(Fade, _extends({}, backdropTransition, {
  283. in: isOpen && !!backdrop,
  284. cssModule: cssModule,
  285. className: mapToCssModules(classNames('modal-backdrop', backdropClassName), cssModule)
  286. })) : React.createElement("div", {
  287. className: mapToCssModules(classNames('modal-backdrop', 'show', backdropClassName), cssModule)
  288. }));
  289. return React.createElement(Portal, {
  290. node: this._element
  291. }, React.createElement("div", {
  292. className: mapToCssModules(wrapClassName)
  293. }, React.createElement(Fade, _extends({}, modalAttributes, modalTransition, {
  294. in: isOpen,
  295. onEntered: this.onOpened,
  296. onExited: this.onClosed,
  297. cssModule: cssModule,
  298. className: mapToCssModules(classNames('modal', modalClassName), cssModule),
  299. innerRef: innerRef
  300. }), external, this.renderModalDialog()), Backdrop));
  301. }
  302. return null;
  303. };
  304. return Modal;
  305. }(React.Component);
  306. Modal.propTypes = propTypes;
  307. Modal.defaultProps = defaultProps;
  308. Modal.openCount = 0;
  309. export default Modal;