1 | "use strict";
|
2 |
|
3 | Object.defineProperty(exports, "__esModule", {
|
4 | value: true
|
5 | });
|
6 | exports.default = void 0;
|
7 | var _react = _interopRequireDefault(require("react"));
|
8 | var _propTypes = _interopRequireDefault(require("prop-types"));
|
9 | var _classnames = _interopRequireDefault(require("classnames"));
|
10 | var _Portal = _interopRequireDefault(require("./Portal"));
|
11 | var _Fade = _interopRequireDefault(require("./Fade"));
|
12 | var _utils = require("./utils");
|
13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
14 | function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
15 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
16 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
17 | function _extends() { _extends = Object.assign ? Object.assign.bind() : 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); }
|
18 | function noop() {}
|
19 | const FadePropTypes = _propTypes.default.shape(_Fade.default.propTypes);
|
20 | const propTypes = {
|
21 |
|
22 | autoFocus: _propTypes.default.bool,
|
23 |
|
24 | backdrop: _propTypes.default.oneOfType([_propTypes.default.bool, _propTypes.default.oneOf(['static'])]),
|
25 |
|
26 | backdropClassName: _propTypes.default.string,
|
27 | backdropTransition: FadePropTypes,
|
28 |
|
29 | centered: _propTypes.default.bool,
|
30 |
|
31 | children: _propTypes.default.node,
|
32 |
|
33 | contentClassName: _propTypes.default.string,
|
34 | className: _propTypes.default.string,
|
35 | container: _utils.targetPropType,
|
36 | cssModule: _propTypes.default.object,
|
37 | external: _propTypes.default.node,
|
38 |
|
39 | fade: _propTypes.default.bool,
|
40 |
|
41 | fullscreen: _propTypes.default.oneOfType([_propTypes.default.bool, _propTypes.default.oneOf(['sm', 'md', 'lg', 'xl'])]),
|
42 | innerRef: _propTypes.default.oneOfType([_propTypes.default.object, _propTypes.default.string, _propTypes.default.func]),
|
43 |
|
44 | isOpen: _propTypes.default.bool,
|
45 |
|
46 | keyboard: _propTypes.default.bool,
|
47 |
|
48 | labelledBy: _propTypes.default.string,
|
49 | modalClassName: _propTypes.default.string,
|
50 | modalTransition: FadePropTypes,
|
51 |
|
52 | onClosed: _propTypes.default.func,
|
53 |
|
54 | onEnter: _propTypes.default.func,
|
55 |
|
56 | onExit: _propTypes.default.func,
|
57 |
|
58 | onOpened: _propTypes.default.func,
|
59 |
|
60 | returnFocusAfterClose: _propTypes.default.bool,
|
61 |
|
62 | role: _propTypes.default.string,
|
63 |
|
64 | scrollable: _propTypes.default.bool,
|
65 |
|
66 | size: _propTypes.default.string,
|
67 |
|
68 | toggle: _propTypes.default.func,
|
69 | trapFocus: _propTypes.default.bool,
|
70 |
|
71 | unmountOnClose: _propTypes.default.bool,
|
72 | wrapClassName: _propTypes.default.string,
|
73 | zIndex: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string])
|
74 | };
|
75 | const propsToOmit = Object.keys(propTypes);
|
76 | const defaultProps = {
|
77 | isOpen: false,
|
78 | autoFocus: true,
|
79 | centered: false,
|
80 | scrollable: false,
|
81 | role: 'dialog',
|
82 | backdrop: true,
|
83 | keyboard: true,
|
84 | zIndex: 1050,
|
85 | fade: true,
|
86 | onOpened: noop,
|
87 | onClosed: noop,
|
88 | modalTransition: {
|
89 | timeout: _utils.TransitionTimeouts.Modal
|
90 | },
|
91 | backdropTransition: {
|
92 | mountOnEnter: true,
|
93 | timeout: _utils.TransitionTimeouts.Fade
|
94 | },
|
95 |
|
96 | unmountOnClose: true,
|
97 | returnFocusAfterClose: true,
|
98 | container: 'body',
|
99 | trapFocus: false
|
100 | };
|
101 | class Modal extends _react.default.Component {
|
102 | constructor(props) {
|
103 | super(props);
|
104 | this._element = null;
|
105 | this._originalBodyPadding = null;
|
106 | this.getFocusableChildren = this.getFocusableChildren.bind(this);
|
107 | this.handleBackdropClick = this.handleBackdropClick.bind(this);
|
108 | this.handleBackdropMouseDown = this.handleBackdropMouseDown.bind(this);
|
109 | this.handleEscape = this.handleEscape.bind(this);
|
110 | this.handleStaticBackdropAnimation = this.handleStaticBackdropAnimation.bind(this);
|
111 | this.handleTab = this.handleTab.bind(this);
|
112 | this.onOpened = this.onOpened.bind(this);
|
113 | this.onClosed = this.onClosed.bind(this);
|
114 | this.manageFocusAfterClose = this.manageFocusAfterClose.bind(this);
|
115 | this.clearBackdropAnimationTimeout = this.clearBackdropAnimationTimeout.bind(this);
|
116 | this.trapFocus = this.trapFocus.bind(this);
|
117 | this.state = {
|
118 | isOpen: false,
|
119 | showStaticBackdropAnimation: false
|
120 | };
|
121 | }
|
122 | componentDidMount() {
|
123 | const {
|
124 | isOpen,
|
125 | autoFocus,
|
126 | onEnter
|
127 | } = this.props;
|
128 | if (isOpen) {
|
129 | this.init();
|
130 | this.setState({
|
131 | isOpen: true
|
132 | });
|
133 | if (autoFocus) {
|
134 | this.setFocus();
|
135 | }
|
136 | }
|
137 | if (onEnter) {
|
138 | onEnter();
|
139 | }
|
140 |
|
141 |
|
142 | document.addEventListener('focus', this.trapFocus, true);
|
143 | this._isMounted = true;
|
144 | }
|
145 | componentDidUpdate(prevProps, prevState) {
|
146 | if (this.props.isOpen && !prevProps.isOpen) {
|
147 | this.init();
|
148 | this.setState({
|
149 | isOpen: true
|
150 | });
|
151 |
|
152 | return;
|
153 | }
|
154 |
|
155 |
|
156 | if (this.props.autoFocus && this.state.isOpen && !prevState.isOpen) {
|
157 | this.setFocus();
|
158 | }
|
159 | if (this._element && prevProps.zIndex !== this.props.zIndex) {
|
160 | this._element.style.zIndex = this.props.zIndex;
|
161 | }
|
162 | }
|
163 | componentWillUnmount() {
|
164 | this.clearBackdropAnimationTimeout();
|
165 | if (this.props.onExit) {
|
166 | this.props.onExit();
|
167 | }
|
168 | if (this._element) {
|
169 | this.destroy();
|
170 | if (this.props.isOpen || this.state.isOpen) {
|
171 | this.close();
|
172 | }
|
173 | }
|
174 | document.removeEventListener('focus', this.trapFocus, true);
|
175 | this._isMounted = false;
|
176 | }
|
177 |
|
178 |
|
179 | handleBackdropClick(e) {
|
180 | if (e.target === this._mouseDownElement) {
|
181 | e.stopPropagation();
|
182 | const backdrop = this._dialog ? this._dialog.parentNode : null;
|
183 | if (backdrop && e.target === backdrop && this.props.backdrop === 'static') {
|
184 | this.handleStaticBackdropAnimation();
|
185 | }
|
186 | if (!this.props.isOpen || this.props.backdrop !== true) return;
|
187 | if (backdrop && e.target === backdrop && this.props.toggle) {
|
188 | this.props.toggle(e);
|
189 | }
|
190 | }
|
191 | }
|
192 | handleTab(e) {
|
193 | if (e.which !== 9) return;
|
194 | if (this.modalIndex < Modal.openCount - 1) return;
|
195 |
|
196 | const focusableChildren = this.getFocusableChildren();
|
197 | const totalFocusable = focusableChildren.length;
|
198 | if (totalFocusable === 0) return;
|
199 | const currentFocus = this.getFocusedChild();
|
200 | let focusedIndex = 0;
|
201 | for (let i = 0; i < totalFocusable; i += 1) {
|
202 | if (focusableChildren[i] === currentFocus) {
|
203 | focusedIndex = i;
|
204 | break;
|
205 | }
|
206 | }
|
207 | if (e.shiftKey && focusedIndex === 0) {
|
208 | e.preventDefault();
|
209 | focusableChildren[totalFocusable - 1].focus();
|
210 | } else if (!e.shiftKey && focusedIndex === totalFocusable - 1) {
|
211 | e.preventDefault();
|
212 | focusableChildren[0].focus();
|
213 | }
|
214 | }
|
215 | handleBackdropMouseDown(e) {
|
216 | this._mouseDownElement = e.target;
|
217 | }
|
218 | handleEscape(e) {
|
219 | if (this.props.isOpen && e.keyCode === _utils.keyCodes.esc && this.props.toggle) {
|
220 | if (this.props.keyboard) {
|
221 | e.preventDefault();
|
222 | e.stopPropagation();
|
223 | this.props.toggle(e);
|
224 | } else if (this.props.backdrop === 'static') {
|
225 | e.preventDefault();
|
226 | e.stopPropagation();
|
227 | this.handleStaticBackdropAnimation();
|
228 | }
|
229 | }
|
230 | }
|
231 | handleStaticBackdropAnimation() {
|
232 | this.clearBackdropAnimationTimeout();
|
233 | this.setState({
|
234 | showStaticBackdropAnimation: true
|
235 | });
|
236 | this._backdropAnimationTimeout = setTimeout(() => {
|
237 | this.setState({
|
238 | showStaticBackdropAnimation: false
|
239 | });
|
240 | }, 100);
|
241 | }
|
242 | onOpened(node, isAppearing) {
|
243 | this.props.onOpened();
|
244 | (this.props.modalTransition.onEntered || noop)(node, isAppearing);
|
245 | }
|
246 | onClosed(node) {
|
247 | const {
|
248 | unmountOnClose
|
249 | } = this.props;
|
250 |
|
251 | this.props.onClosed();
|
252 | (this.props.modalTransition.onExited || noop)(node);
|
253 | if (unmountOnClose) {
|
254 | this.destroy();
|
255 | }
|
256 | this.close();
|
257 | if (this._isMounted) {
|
258 | this.setState({
|
259 | isOpen: false
|
260 | });
|
261 | }
|
262 | }
|
263 | setFocus() {
|
264 | if (this._dialog && this._dialog.parentNode && typeof this._dialog.parentNode.focus === 'function') {
|
265 | this._dialog.parentNode.focus();
|
266 | }
|
267 | }
|
268 | getFocusableChildren() {
|
269 | return this._element.querySelectorAll(_utils.focusableElements.join(', '));
|
270 | }
|
271 | getFocusedChild() {
|
272 | let currentFocus;
|
273 | const focusableChildren = this.getFocusableChildren();
|
274 | try {
|
275 | currentFocus = document.activeElement;
|
276 | } catch (err) {
|
277 | currentFocus = focusableChildren[0];
|
278 | }
|
279 | return currentFocus;
|
280 | }
|
281 | trapFocus(ev) {
|
282 | if (!this.props.trapFocus) {
|
283 | return;
|
284 | }
|
285 | if (!this._element) {
|
286 |
|
287 | return;
|
288 | }
|
289 | if (this._dialog && this._dialog.parentNode === ev.target) {
|
290 |
|
291 | return;
|
292 | }
|
293 | if (this.modalIndex < Modal.openCount - 1) {
|
294 |
|
295 | return;
|
296 | }
|
297 | const children = this.getFocusableChildren();
|
298 | for (let i = 0; i < children.length; i += 1) {
|
299 |
|
300 | if (children[i] === ev.target) return;
|
301 | }
|
302 | if (children.length > 0) {
|
303 |
|
304 | ev.preventDefault();
|
305 | ev.stopPropagation();
|
306 | children[0].focus();
|
307 | }
|
308 | }
|
309 | init() {
|
310 | try {
|
311 | this._triggeringElement = document.activeElement;
|
312 | } catch (err) {
|
313 | this._triggeringElement = null;
|
314 | }
|
315 | if (!this._element) {
|
316 | this._element = document.createElement('div');
|
317 | this._element.setAttribute('tabindex', '-1');
|
318 | this._element.style.position = 'relative';
|
319 | this._element.style.zIndex = this.props.zIndex;
|
320 | this._mountContainer = (0, _utils.getTarget)(this.props.container);
|
321 | this._mountContainer.appendChild(this._element);
|
322 | }
|
323 | this._originalBodyPadding = (0, _utils.getOriginalBodyPadding)();
|
324 | if (Modal.openCount < 1) {
|
325 | Modal.originalBodyOverflow = window.getComputedStyle(document.body).overflow;
|
326 | }
|
327 | (0, _utils.conditionallyUpdateScrollbar)();
|
328 | if (Modal.openCount === 0) {
|
329 | document.body.className = (0, _classnames.default)(document.body.className, (0, _utils.mapToCssModules)('modal-open', this.props.cssModule));
|
330 | document.body.style.overflow = 'hidden';
|
331 | }
|
332 | this.modalIndex = Modal.openCount;
|
333 | Modal.openCount += 1;
|
334 | }
|
335 | destroy() {
|
336 | if (this._element) {
|
337 | this._mountContainer.removeChild(this._element);
|
338 | this._element = null;
|
339 | }
|
340 | this.manageFocusAfterClose();
|
341 | }
|
342 | manageFocusAfterClose() {
|
343 | if (this._triggeringElement) {
|
344 | const {
|
345 | returnFocusAfterClose
|
346 | } = this.props;
|
347 | if (this._triggeringElement.focus && returnFocusAfterClose) this._triggeringElement.focus();
|
348 | this._triggeringElement = null;
|
349 | }
|
350 | }
|
351 | close() {
|
352 | if (Modal.openCount <= 1) {
|
353 | const modalOpenClassName = (0, _utils.mapToCssModules)('modal-open', this.props.cssModule);
|
354 |
|
355 | const modalOpenClassNameRegex = new RegExp(`(^| )${modalOpenClassName}( |$)`);
|
356 | document.body.className = document.body.className.replace(modalOpenClassNameRegex, ' ').trim();
|
357 | document.body.style.overflow = Modal.originalBodyOverflow;
|
358 | }
|
359 | this.manageFocusAfterClose();
|
360 | Modal.openCount = Math.max(0, Modal.openCount - 1);
|
361 | (0, _utils.setScrollbarWidth)(this._originalBodyPadding);
|
362 | }
|
363 | clearBackdropAnimationTimeout() {
|
364 | if (this._backdropAnimationTimeout) {
|
365 | clearTimeout(this._backdropAnimationTimeout);
|
366 | this._backdropAnimationTimeout = undefined;
|
367 | }
|
368 | }
|
369 | renderModalDialog() {
|
370 | const attributes = (0, _utils.omit)(this.props, propsToOmit);
|
371 | const dialogBaseClass = 'modal-dialog';
|
372 | return _react.default.createElement("div", _extends({}, attributes, {
|
373 | className: (0, _utils.mapToCssModules)((0, _classnames.default)(dialogBaseClass, this.props.className, {
|
374 | [`modal-${this.props.size}`]: this.props.size,
|
375 | [`${dialogBaseClass}-centered`]: this.props.centered,
|
376 | [`${dialogBaseClass}-scrollable`]: this.props.scrollable,
|
377 | 'modal-fullscreen': this.props.fullscreen === true,
|
378 | [`modal-fullscreen-${this.props.fullscreen}-down`]: typeof this.props.fullscreen === 'string'
|
379 | }), this.props.cssModule),
|
380 | role: "document",
|
381 | ref: c => {
|
382 | this._dialog = c;
|
383 | }
|
384 | }), _react.default.createElement("div", {
|
385 | className: (0, _utils.mapToCssModules)((0, _classnames.default)('modal-content', this.props.contentClassName), this.props.cssModule)
|
386 | }, this.props.children));
|
387 | }
|
388 | render() {
|
389 | const {
|
390 | unmountOnClose
|
391 | } = this.props;
|
392 | if (!!this._element && (this.state.isOpen || !unmountOnClose)) {
|
393 | const isModalHidden = !!this._element && !this.state.isOpen && !unmountOnClose;
|
394 | this._element.style.display = isModalHidden ? 'none' : 'block';
|
395 | const {
|
396 | wrapClassName,
|
397 | modalClassName,
|
398 | backdropClassName,
|
399 | cssModule,
|
400 | isOpen,
|
401 | backdrop,
|
402 | role,
|
403 | labelledBy,
|
404 | external,
|
405 | innerRef
|
406 | } = this.props;
|
407 | const modalAttributes = {
|
408 | onClick: this.handleBackdropClick,
|
409 | onMouseDown: this.handleBackdropMouseDown,
|
410 | onKeyUp: this.handleEscape,
|
411 | onKeyDown: this.handleTab,
|
412 | style: {
|
413 | display: 'block'
|
414 | },
|
415 | 'aria-labelledby': labelledBy,
|
416 | 'aria-modal': true,
|
417 | role,
|
418 | tabIndex: '-1'
|
419 | };
|
420 | const hasTransition = this.props.fade;
|
421 | const modalTransition = _objectSpread(_objectSpread(_objectSpread({}, _Fade.default.defaultProps), this.props.modalTransition), {}, {
|
422 | baseClass: hasTransition ? this.props.modalTransition.baseClass : '',
|
423 | timeout: hasTransition ? this.props.modalTransition.timeout : 0
|
424 | });
|
425 | const backdropTransition = _objectSpread(_objectSpread(_objectSpread({}, _Fade.default.defaultProps), this.props.backdropTransition), {}, {
|
426 | baseClass: hasTransition ? this.props.backdropTransition.baseClass : '',
|
427 | timeout: hasTransition ? this.props.backdropTransition.timeout : 0
|
428 | });
|
429 | const Backdrop = backdrop && (hasTransition ? _react.default.createElement(_Fade.default, _extends({}, backdropTransition, {
|
430 | in: isOpen && !!backdrop,
|
431 | cssModule: cssModule,
|
432 | className: (0, _utils.mapToCssModules)((0, _classnames.default)('modal-backdrop', backdropClassName), cssModule)
|
433 | })) : _react.default.createElement("div", {
|
434 | className: (0, _utils.mapToCssModules)((0, _classnames.default)('modal-backdrop', 'show', backdropClassName), cssModule)
|
435 | }));
|
436 | return _react.default.createElement(_Portal.default, {
|
437 | node: this._element
|
438 | }, _react.default.createElement("div", {
|
439 | className: (0, _utils.mapToCssModules)(wrapClassName)
|
440 | }, _react.default.createElement(_Fade.default, _extends({}, modalAttributes, modalTransition, {
|
441 | in: isOpen,
|
442 | onEntered: this.onOpened,
|
443 | onExited: this.onClosed,
|
444 | cssModule: cssModule,
|
445 | className: (0, _utils.mapToCssModules)((0, _classnames.default)('modal', modalClassName, this.state.showStaticBackdropAnimation && 'modal-static'), cssModule),
|
446 | innerRef: innerRef
|
447 | }), external, this.renderModalDialog()), Backdrop));
|
448 | }
|
449 | return null;
|
450 | }
|
451 | }
|
452 | Modal.propTypes = propTypes;
|
453 | Modal.defaultProps = defaultProps;
|
454 | Modal.openCount = 0;
|
455 | Modal.originalBodyOverflow = null;
|
456 | var _default = Modal;
|
457 | exports.default = _default; |
\ | No newline at end of file |