1 | import React, { useRef, useEffect, useState } from 'react';
|
2 | import ReactDom from 'react-dom';
|
3 | import cx from 'classnames';
|
4 | import noScroll from 'no-scroll';
|
5 |
|
6 | function _extends() {
|
7 | _extends = Object.assign || function (target) {
|
8 | for (var i = 1; i < arguments.length; i++) {
|
9 | var source = arguments[i];
|
10 |
|
11 | for (var key in source) {
|
12 | if (Object.prototype.hasOwnProperty.call(source, key)) {
|
13 | target[key] = source[key];
|
14 | }
|
15 | }
|
16 | }
|
17 |
|
18 | return target;
|
19 | };
|
20 |
|
21 | return _extends.apply(this, arguments);
|
22 | }
|
23 |
|
24 | var CloseIcon = function CloseIcon(_ref) {
|
25 | var classes = _ref.classes,
|
26 | classNames = _ref.classNames,
|
27 | styles = _ref.styles,
|
28 | id = _ref.id,
|
29 | closeIcon = _ref.closeIcon,
|
30 | onClickCloseIcon = _ref.onClickCloseIcon;
|
31 | return React.createElement("button", {
|
32 | id: id,
|
33 | className: cx(classes.closeButton, classNames === null || classNames === void 0 ? void 0 : classNames.closeButton),
|
34 | style: styles === null || styles === void 0 ? void 0 : styles.closeButton,
|
35 | onClick: onClickCloseIcon,
|
36 | "data-testid": "close-button"
|
37 | }, closeIcon ? closeIcon : React.createElement("svg", {
|
38 | className: classNames === null || classNames === void 0 ? void 0 : classNames.closeIcon,
|
39 | style: styles === null || styles === void 0 ? void 0 : styles.closeIcon,
|
40 | xmlns: "http://www.w3.org/2000/svg",
|
41 | width: 28,
|
42 | height: 28,
|
43 | viewBox: "0 0 36 36",
|
44 | "data-testid": "close-icon"
|
45 | }, React.createElement("path", {
|
46 | d: "M28.5 9.62L26.38 7.5 18 15.88 9.62 7.5 7.5 9.62 15.88 18 7.5 26.38l2.12 2.12L18 20.12l8.38 8.38 2.12-2.12L20.12 18z"
|
47 | })));
|
48 | };
|
49 |
|
50 | var _modals = [];
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 | var modalManager = {
|
57 | |
58 |
|
59 |
|
60 | modals: function modals() {
|
61 | return _modals;
|
62 | },
|
63 |
|
64 | |
65 |
|
66 |
|
67 | add: function add(newModal, blockScroll) {
|
68 | if (_modals.findIndex(function (modal) {
|
69 | return modal.element === newModal;
|
70 | }) === -1) {
|
71 | _modals.push({
|
72 | element: newModal,
|
73 | blockScroll: blockScroll
|
74 | });
|
75 | }
|
76 | },
|
77 |
|
78 | |
79 |
|
80 |
|
81 | remove: function remove(oldModal) {
|
82 | var index = _modals.findIndex(function (modal) {
|
83 | return modal.element === oldModal;
|
84 | });
|
85 |
|
86 | if (index !== -1) {
|
87 | _modals.splice(index, 1);
|
88 | }
|
89 | },
|
90 |
|
91 | |
92 |
|
93 |
|
94 | isTopModal: function isTopModal(modal) {
|
95 | var _modals2;
|
96 |
|
97 | return !!_modals.length && ((_modals2 = _modals[_modals.length - 1]) === null || _modals2 === void 0 ? void 0 : _modals2.element) === modal;
|
98 | }
|
99 | };
|
100 |
|
101 | var isBrowser = typeof window !== 'undefined';
|
102 | var blockNoScroll = function blockNoScroll() {
|
103 | noScroll.on();
|
104 | };
|
105 | var unblockNoScroll = function unblockNoScroll() {
|
106 |
|
107 |
|
108 | var modals = modalManager.modals().filter(function (modal) {
|
109 | return modal.blockScroll;
|
110 | });
|
111 |
|
112 | if (modals.length === 0) {
|
113 | noScroll.off();
|
114 | }
|
115 | };
|
116 |
|
117 |
|
118 | var candidateSelectors = ['input', 'select', 'textarea', 'a[href]', 'button', '[tabindex]', 'audio[controls]', 'video[controls]', '[contenteditable]:not([contenteditable="false"])'];
|
119 |
|
120 | function isHidden(node) {
|
121 |
|
122 |
|
123 | return node.offsetParent === null || getComputedStyle(node).visibility === 'hidden';
|
124 | }
|
125 |
|
126 | function getAllTabbingElements(parentElem) {
|
127 | var tabbableNodes = parentElem.querySelectorAll(candidateSelectors.join(','));
|
128 | var onlyTabbable = [];
|
129 |
|
130 | for (var i = 0; i < tabbableNodes.length; i++) {
|
131 | var node = tabbableNodes[i];
|
132 |
|
133 | if (!node.disabled && getTabindex(node) > -1 && !isHidden(node)) {
|
134 | onlyTabbable.push(node);
|
135 | }
|
136 | }
|
137 |
|
138 | return onlyTabbable;
|
139 | }
|
140 | function tabTrappingKey(event, parentElem) {
|
141 |
|
142 | if (!event || event.key !== 'Tab') return;
|
143 |
|
144 | if (!parentElem || !parentElem.contains) {
|
145 | if (process && process.env.NODE_ENV === 'development') {
|
146 | console.warn('focus-trap-js: parent element is not defined');
|
147 | }
|
148 |
|
149 | return false;
|
150 | }
|
151 |
|
152 | if (!parentElem.contains(event.target)) {
|
153 | return false;
|
154 | }
|
155 |
|
156 | var allTabbingElements = getAllTabbingElements(parentElem);
|
157 | var firstFocusableElement = allTabbingElements[0];
|
158 | var lastFocusableElement = allTabbingElements[allTabbingElements.length - 1];
|
159 |
|
160 | if (event.shiftKey && event.target === firstFocusableElement) {
|
161 | lastFocusableElement.focus();
|
162 | event.preventDefault();
|
163 | return true;
|
164 | } else if (!event.shiftKey && event.target === lastFocusableElement) {
|
165 | firstFocusableElement.focus();
|
166 | event.preventDefault();
|
167 | return true;
|
168 | }
|
169 |
|
170 | return false;
|
171 | }
|
172 |
|
173 | function getTabindex(node) {
|
174 | var tabindexAttr = parseInt(node.getAttribute('tabindex'), 10);
|
175 | if (!isNaN(tabindexAttr)) return tabindexAttr;
|
176 |
|
177 |
|
178 | if (isContentEditable(node)) return 0;
|
179 | return node.tabIndex;
|
180 | }
|
181 |
|
182 | function isContentEditable(node) {
|
183 | return node.getAttribute('contentEditable');
|
184 | }
|
185 |
|
186 | var FocusTrap = function FocusTrap(_ref) {
|
187 | var container = _ref.container;
|
188 | var refLastFocus = useRef();
|
189 | |
190 |
|
191 |
|
192 |
|
193 | useEffect(function () {
|
194 | var handleKeyEvent = function handleKeyEvent(event) {
|
195 | if (container === null || container === void 0 ? void 0 : container.current) {
|
196 | tabTrappingKey(event, container.current);
|
197 | }
|
198 | };
|
199 |
|
200 | if (isBrowser) {
|
201 | document.addEventListener('keydown', handleKeyEvent);
|
202 | }
|
203 |
|
204 |
|
205 | if (isBrowser && (container === null || container === void 0 ? void 0 : container.current)) {
|
206 | var allTabbingElements = getAllTabbingElements(container.current);
|
207 |
|
208 | if (allTabbingElements[0]) {
|
209 |
|
210 |
|
211 | if (candidateSelectors.findIndex(function (selector) {
|
212 | var _document$activeEleme;
|
213 |
|
214 | return (_document$activeEleme = document.activeElement) === null || _document$activeEleme === void 0 ? void 0 : _document$activeEleme.matches(selector);
|
215 | }) !== -1) {
|
216 | refLastFocus.current = document.activeElement;
|
217 | }
|
218 |
|
219 | allTabbingElements[0].focus();
|
220 | }
|
221 | }
|
222 |
|
223 | return function () {
|
224 | if (isBrowser) {
|
225 | var _refLastFocus$current;
|
226 |
|
227 | document.removeEventListener('keydown', handleKeyEvent);
|
228 |
|
229 | (_refLastFocus$current = refLastFocus.current) === null || _refLastFocus$current === void 0 ? void 0 : _refLastFocus$current.focus();
|
230 | }
|
231 | };
|
232 | }, [container]);
|
233 | return null;
|
234 | };
|
235 |
|
236 | var classes = {
|
237 | overlay: 'react-responsive-modal-overlay',
|
238 | modal: 'react-responsive-modal-modal',
|
239 | modalCenter: 'react-responsive-modal-modalCenter',
|
240 | closeButton: 'react-responsive-modal-closeButton',
|
241 | animationIn: 'react-responsive-modal-fadeIn',
|
242 | animationOut: 'react-responsive-modal-fadeOut'
|
243 | };
|
244 | var Modal = function Modal(_ref) {
|
245 | var _classNames$animation, _classNames$animation2;
|
246 |
|
247 | var open = _ref.open,
|
248 | center = _ref.center,
|
249 | _ref$blockScroll = _ref.blockScroll,
|
250 | blockScroll = _ref$blockScroll === void 0 ? true : _ref$blockScroll,
|
251 | _ref$closeOnEsc = _ref.closeOnEsc,
|
252 | closeOnEsc = _ref$closeOnEsc === void 0 ? true : _ref$closeOnEsc,
|
253 | _ref$closeOnOverlayCl = _ref.closeOnOverlayClick,
|
254 | closeOnOverlayClick = _ref$closeOnOverlayCl === void 0 ? true : _ref$closeOnOverlayCl,
|
255 | container = _ref.container,
|
256 | _ref$showCloseIcon = _ref.showCloseIcon,
|
257 | showCloseIcon = _ref$showCloseIcon === void 0 ? true : _ref$showCloseIcon,
|
258 | closeIconId = _ref.closeIconId,
|
259 | closeIcon = _ref.closeIcon,
|
260 | _ref$focusTrapped = _ref.focusTrapped,
|
261 | focusTrapped = _ref$focusTrapped === void 0 ? true : _ref$focusTrapped,
|
262 | _ref$animationDuratio = _ref.animationDuration,
|
263 | animationDuration = _ref$animationDuratio === void 0 ? 500 : _ref$animationDuratio,
|
264 | classNames = _ref.classNames,
|
265 | styles = _ref.styles,
|
266 | _ref$role = _ref.role,
|
267 | role = _ref$role === void 0 ? 'dialog' : _ref$role,
|
268 | ariaDescribedby = _ref.ariaDescribedby,
|
269 | ariaLabelledby = _ref.ariaLabelledby,
|
270 | modalId = _ref.modalId,
|
271 | onClose = _ref.onClose,
|
272 | onEscKeyDown = _ref.onEscKeyDown,
|
273 | onOverlayClick = _ref.onOverlayClick,
|
274 | onAnimationEnd = _ref.onAnimationEnd,
|
275 | children = _ref.children;
|
276 | var refModal = useRef(null);
|
277 | var refShouldClose = useRef(null);
|
278 | var refContainer = useRef(null);
|
279 |
|
280 |
|
281 | if (refContainer.current === null && isBrowser) {
|
282 | refContainer.current = document.createElement('div');
|
283 | }
|
284 |
|
285 | var _useState = useState(open),
|
286 | showPortal = _useState[0],
|
287 | setShowPortal = _useState[1];
|
288 |
|
289 | var handleOpen = function handleOpen() {
|
290 | modalManager.add(refContainer.current, blockScroll);
|
291 |
|
292 | if (blockScroll) {
|
293 | blockNoScroll();
|
294 | }
|
295 |
|
296 | if (refContainer.current && !container && !document.body.contains(refContainer.current)) {
|
297 | document.body.appendChild(refContainer.current);
|
298 | }
|
299 |
|
300 | document.addEventListener('keydown', handleKeydown);
|
301 | };
|
302 |
|
303 | var handleClose = function handleClose() {
|
304 | modalManager.remove(refContainer.current);
|
305 |
|
306 | if (blockScroll) {
|
307 | unblockNoScroll();
|
308 | }
|
309 |
|
310 | if (refContainer.current && !container && document.body.contains(refContainer.current)) {
|
311 | document.body.removeChild(refContainer.current);
|
312 | }
|
313 |
|
314 | document.removeEventListener('keydown', handleKeydown);
|
315 | };
|
316 |
|
317 | var handleKeydown = function handleKeydown(event) {
|
318 |
|
319 | if (event.keyCode !== 27 || !modalManager.isTopModal(refContainer.current)) {
|
320 | return;
|
321 | }
|
322 |
|
323 | if (onEscKeyDown) {
|
324 | onEscKeyDown(event);
|
325 | }
|
326 |
|
327 | if (closeOnEsc) {
|
328 | onClose();
|
329 | }
|
330 | };
|
331 |
|
332 | useEffect(function () {
|
333 |
|
334 | if (open) {
|
335 | handleOpen();
|
336 | }
|
337 |
|
338 | return function () {
|
339 |
|
340 | if (showPortal) {
|
341 | handleClose();
|
342 | }
|
343 | };
|
344 | }, []);
|
345 | useEffect(function () {
|
346 |
|
347 | if (open && !showPortal) {
|
348 | setShowPortal(true);
|
349 | handleOpen();
|
350 | }
|
351 | }, [open]);
|
352 |
|
353 | var handleClickOverlay = function handleClickOverlay(event) {
|
354 | if (refShouldClose.current === null) {
|
355 | refShouldClose.current = true;
|
356 | }
|
357 |
|
358 | if (!refShouldClose.current) {
|
359 | refShouldClose.current = null;
|
360 | return;
|
361 | }
|
362 |
|
363 | if (onOverlayClick) {
|
364 | onOverlayClick(event);
|
365 | }
|
366 |
|
367 | if (closeOnOverlayClick) {
|
368 | onClose();
|
369 | }
|
370 |
|
371 | refShouldClose.current = null;
|
372 | };
|
373 |
|
374 | var handleModalEvent = function handleModalEvent() {
|
375 | refShouldClose.current = false;
|
376 | };
|
377 |
|
378 | var handleClickCloseIcon = function handleClickCloseIcon() {
|
379 | onClose();
|
380 | };
|
381 |
|
382 | var handleAnimationEnd = function handleAnimationEnd() {
|
383 | if (!open) {
|
384 | setShowPortal(false);
|
385 | handleClose();
|
386 | }
|
387 |
|
388 | if (blockScroll) {
|
389 | unblockNoScroll();
|
390 | }
|
391 |
|
392 | if (onAnimationEnd) {
|
393 | onAnimationEnd();
|
394 | }
|
395 | };
|
396 |
|
397 | return showPortal ? ReactDom.createPortal(React.createElement("div", {
|
398 | style: _extends({
|
399 | animation: (open ? (_classNames$animation = classNames === null || classNames === void 0 ? void 0 : classNames.animationIn) !== null && _classNames$animation !== void 0 ? _classNames$animation : classes.animationIn : (_classNames$animation2 = classNames === null || classNames === void 0 ? void 0 : classNames.animationOut) !== null && _classNames$animation2 !== void 0 ? _classNames$animation2 : classes.animationOut) + " " + animationDuration + "ms"
|
400 | }, styles === null || styles === void 0 ? void 0 : styles.overlay),
|
401 | className: cx(classes.overlay, classNames === null || classNames === void 0 ? void 0 : classNames.overlay),
|
402 | onClick: handleClickOverlay,
|
403 | onAnimationEnd: handleAnimationEnd,
|
404 | "data-testid": "overlay"
|
405 | }, React.createElement("div", {
|
406 | ref: refModal,
|
407 | className: cx(classes.modal, center && classes.modalCenter, classNames === null || classNames === void 0 ? void 0 : classNames.modal),
|
408 | style: styles === null || styles === void 0 ? void 0 : styles.modal,
|
409 | onMouseDown: handleModalEvent,
|
410 | onMouseUp: handleModalEvent,
|
411 | onClick: handleModalEvent,
|
412 | id: modalId,
|
413 | role: role,
|
414 | "aria-modal": "true",
|
415 | "aria-labelledby": ariaLabelledby,
|
416 | "aria-describedby": ariaDescribedby,
|
417 | "data-testid": "modal"
|
418 | }, focusTrapped && React.createElement(FocusTrap, {
|
419 | container: refModal
|
420 | }), children, showCloseIcon && React.createElement(CloseIcon, {
|
421 | classes: classes,
|
422 | classNames: classNames,
|
423 | styles: styles,
|
424 | closeIcon: closeIcon,
|
425 | onClickCloseIcon: handleClickCloseIcon,
|
426 | id: closeIconId
|
427 | }))), container || refContainer.current) : null;
|
428 | };
|
429 |
|
430 | export default Modal;
|
431 | export { Modal };
|
432 |
|