Dashboard sipadu mbip
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Modal.js 15KB

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