react-responsive-modal 6.4.2 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/dist/index.cjs +339 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +148 -0
- package/dist/index.d.ts +148 -140
- package/dist/index.js +311 -5
- package/dist/index.js.map +1 -0
- package/package.json +52 -45
- package/src/FocusTrap.tsx +3 -3
- package/src/index.tsx +20 -14
- package/src/lib/focusTrapJs.ts +1 -1
- package/src/useScrollLock.ts +2 -2
- package/dist/CloseIcon.d.ts +0 -19
- package/dist/FocusTrap.d.ts +0 -7
- package/dist/lib/focusTrapJs.d.ts +0 -3
- package/dist/modalManager.d.ts +0 -20
- package/dist/react-responsive-modal.cjs.development.js +0 -472
- package/dist/react-responsive-modal.cjs.development.js.map +0 -1
- package/dist/react-responsive-modal.cjs.production.min.js +0 -2
- package/dist/react-responsive-modal.cjs.production.min.js.map +0 -1
- package/dist/react-responsive-modal.esm.js +0 -465
- package/dist/react-responsive-modal.esm.js.map +0 -1
- package/dist/useScrollLock.d.ts +0 -1
- package/dist/utils.d.ts +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,140 +1,148 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/index.d.ts
|
|
4
|
+
interface ModalProps {
|
|
5
|
+
/**
|
|
6
|
+
* Control if the modal is open or not.
|
|
7
|
+
*/
|
|
8
|
+
open: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Should the dialog be centered.
|
|
11
|
+
*
|
|
12
|
+
* Default to false.
|
|
13
|
+
*/
|
|
14
|
+
center?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Is the modal closable when user press esc key.
|
|
17
|
+
*
|
|
18
|
+
* Default to true.
|
|
19
|
+
*/
|
|
20
|
+
closeOnEsc?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Is the modal closable when user click on overlay.
|
|
23
|
+
*
|
|
24
|
+
* Default to true.
|
|
25
|
+
*/
|
|
26
|
+
closeOnOverlayClick?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Whether to block scrolling when dialog is open.
|
|
29
|
+
*
|
|
30
|
+
* Default to true.
|
|
31
|
+
*/
|
|
32
|
+
blockScroll?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Show the close icon.
|
|
35
|
+
*
|
|
36
|
+
* Default to true.
|
|
37
|
+
*/
|
|
38
|
+
showCloseIcon?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* id attribute for the close icon button.
|
|
41
|
+
*/
|
|
42
|
+
closeIconId?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Custom icon to render (svg, img, etc...).
|
|
45
|
+
*/
|
|
46
|
+
closeIcon?: React.ReactNode;
|
|
47
|
+
/**
|
|
48
|
+
* When the modal is open, trap focus within it.
|
|
49
|
+
*
|
|
50
|
+
* Default to true.
|
|
51
|
+
*/
|
|
52
|
+
focusTrapped?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Element to focus when focus trap is used.
|
|
55
|
+
*
|
|
56
|
+
* Default to undefined.
|
|
57
|
+
*/
|
|
58
|
+
initialFocusRef?: React.RefObject<HTMLElement | null>;
|
|
59
|
+
/**
|
|
60
|
+
* You can specify a container prop which should be of type `Element`.
|
|
61
|
+
* The portal will be rendered inside that element.
|
|
62
|
+
* The default behavior will create a div node and render it at the at the end of document.body.
|
|
63
|
+
*/
|
|
64
|
+
container?: Element | null;
|
|
65
|
+
/**
|
|
66
|
+
* An object containing classNames to style the modal.
|
|
67
|
+
*/
|
|
68
|
+
classNames?: {
|
|
69
|
+
root?: string;
|
|
70
|
+
overlay?: string;
|
|
71
|
+
overlayAnimationIn?: string;
|
|
72
|
+
overlayAnimationOut?: string;
|
|
73
|
+
modalContainer?: string;
|
|
74
|
+
modal?: string;
|
|
75
|
+
modalAnimationIn?: string;
|
|
76
|
+
modalAnimationOut?: string;
|
|
77
|
+
closeButton?: string;
|
|
78
|
+
closeIcon?: string;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* An object containing the styles objects to style the modal.
|
|
82
|
+
*/
|
|
83
|
+
styles?: {
|
|
84
|
+
root?: React.CSSProperties;
|
|
85
|
+
overlay?: React.CSSProperties;
|
|
86
|
+
modalContainer?: React.CSSProperties;
|
|
87
|
+
modal?: React.CSSProperties;
|
|
88
|
+
closeButton?: React.CSSProperties;
|
|
89
|
+
closeIcon?: React.CSSProperties;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Animation duration in milliseconds.
|
|
93
|
+
*
|
|
94
|
+
* Default to 300.
|
|
95
|
+
*/
|
|
96
|
+
animationDuration?: number;
|
|
97
|
+
/**
|
|
98
|
+
* ARIA role for modal
|
|
99
|
+
*
|
|
100
|
+
* Default to 'dialog'.
|
|
101
|
+
*/
|
|
102
|
+
role?: string;
|
|
103
|
+
/**
|
|
104
|
+
* ARIA label for modal
|
|
105
|
+
*/
|
|
106
|
+
ariaLabel?: string;
|
|
107
|
+
/**
|
|
108
|
+
* ARIA label for modal
|
|
109
|
+
*/
|
|
110
|
+
ariaLabelledby?: string;
|
|
111
|
+
/**
|
|
112
|
+
* ARIA description for modal
|
|
113
|
+
*/
|
|
114
|
+
ariaDescribedby?: string;
|
|
115
|
+
/**
|
|
116
|
+
* Avoid unpleasant flickering effect when body overflow is hidden. For more information see https://www.npmjs.com/package/body-scroll-lock
|
|
117
|
+
*/
|
|
118
|
+
reserveScrollBarGap?: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* id attribute for modal container
|
|
121
|
+
*/
|
|
122
|
+
containerId?: string;
|
|
123
|
+
/**
|
|
124
|
+
* id attribute for modal
|
|
125
|
+
*/
|
|
126
|
+
modalId?: string;
|
|
127
|
+
/**
|
|
128
|
+
* Callback fired when the Modal is requested to be closed by a click on the overlay or when user press esc key.
|
|
129
|
+
*/
|
|
130
|
+
onClose: () => void;
|
|
131
|
+
/**
|
|
132
|
+
* Callback fired when the escape key is pressed.
|
|
133
|
+
*/
|
|
134
|
+
onEscKeyDown?: (event: KeyboardEvent) => void;
|
|
135
|
+
/**
|
|
136
|
+
* Callback fired when the overlay is clicked.
|
|
137
|
+
*/
|
|
138
|
+
onOverlayClick?: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
|
139
|
+
/**
|
|
140
|
+
* Callback fired when the Modal has exited and the animation is finished.
|
|
141
|
+
*/
|
|
142
|
+
onAnimationEnd?: () => void;
|
|
143
|
+
children?: React.ReactNode;
|
|
144
|
+
}
|
|
145
|
+
declare const Modal: React.ForwardRefExoticComponent<ModalProps & React.RefAttributes<HTMLDivElement>>;
|
|
146
|
+
//#endregion
|
|
147
|
+
export { Modal, Modal as default, ModalProps };
|
|
148
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,314 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
3
|
+
import cx from "classnames";
|
|
4
|
+
import { useForwardedRef } from "@bedrock-layout/use-forwarded-ref";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { disableBodyScroll, enableBodyScroll } from "body-scroll-lock";
|
|
1
7
|
|
|
2
|
-
|
|
8
|
+
//#region src/CloseIcon.tsx
|
|
9
|
+
const CloseIcon = ({ classes: classes$1, classNames, styles, id, closeIcon, onClick }) => /* @__PURE__ */ jsx("button", {
|
|
10
|
+
id,
|
|
11
|
+
className: cx(classes$1.closeButton, classNames?.closeButton),
|
|
12
|
+
style: styles?.closeButton,
|
|
13
|
+
onClick,
|
|
14
|
+
"data-testid": "close-button",
|
|
15
|
+
children: closeIcon ? closeIcon : /* @__PURE__ */ jsx("svg", {
|
|
16
|
+
className: classNames?.closeIcon,
|
|
17
|
+
style: styles?.closeIcon,
|
|
18
|
+
width: 28,
|
|
19
|
+
height: 28,
|
|
20
|
+
viewBox: "0 0 36 36",
|
|
21
|
+
"data-testid": "close-icon",
|
|
22
|
+
children: /* @__PURE__ */ jsx("path", { 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" })
|
|
23
|
+
})
|
|
24
|
+
});
|
|
25
|
+
var CloseIcon_default = CloseIcon;
|
|
3
26
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/utils.ts
|
|
29
|
+
const isBrowser = typeof window !== "undefined";
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/lib/focusTrapJs.ts
|
|
33
|
+
const candidateSelectors = [
|
|
34
|
+
"input",
|
|
35
|
+
"select",
|
|
36
|
+
"textarea",
|
|
37
|
+
"a[href]",
|
|
38
|
+
"button",
|
|
39
|
+
"[tabindex]",
|
|
40
|
+
"audio[controls]",
|
|
41
|
+
"video[controls]",
|
|
42
|
+
"[contenteditable]:not([contenteditable=\"false\"])"
|
|
43
|
+
];
|
|
44
|
+
function isHidden(node) {
|
|
45
|
+
return node.offsetParent === null || getComputedStyle(node).visibility === "hidden";
|
|
46
|
+
}
|
|
47
|
+
function getCheckedRadio(nodes, form) {
|
|
48
|
+
for (var i = 0; i < nodes.length; i++) if (nodes[i].checked && nodes[i].form === form) return nodes[i];
|
|
49
|
+
}
|
|
50
|
+
function isNotRadioOrTabbableRadio(node) {
|
|
51
|
+
if (node.tagName !== "INPUT" || node.type !== "radio" || !node.name) return true;
|
|
52
|
+
var radioScope = node.form || node.ownerDocument;
|
|
53
|
+
var radioSet = radioScope.querySelectorAll("input[type=\"radio\"][name=\"" + node.name + "\"]");
|
|
54
|
+
var checked = getCheckedRadio(radioSet, node.form);
|
|
55
|
+
return checked === node || checked === void 0 && radioSet[0] === node;
|
|
56
|
+
}
|
|
57
|
+
function getAllTabbingElements(parentElem) {
|
|
58
|
+
var currentActiveElement = document.activeElement;
|
|
59
|
+
var tabbableNodes = parentElem.querySelectorAll(candidateSelectors.join(","));
|
|
60
|
+
var onlyTabbable = [];
|
|
61
|
+
for (var i = 0; i < tabbableNodes.length; i++) {
|
|
62
|
+
var node = tabbableNodes[i];
|
|
63
|
+
if (currentActiveElement === node || !node.disabled && getTabindex(node) > -1 && !isHidden(node) && isNotRadioOrTabbableRadio(node)) onlyTabbable.push(node);
|
|
64
|
+
}
|
|
65
|
+
return onlyTabbable;
|
|
66
|
+
}
|
|
67
|
+
function tabTrappingKey(event, parentElem) {
|
|
68
|
+
if (!event || event.key !== "Tab") return;
|
|
69
|
+
if (!parentElem || !parentElem.contains) {
|
|
70
|
+
if (process && true) console.warn("focus-trap-js: parent element is not defined");
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
if (!parentElem.contains(event.target)) return false;
|
|
74
|
+
var allTabbingElements = getAllTabbingElements(parentElem);
|
|
75
|
+
var firstFocusableElement = allTabbingElements[0];
|
|
76
|
+
var lastFocusableElement = allTabbingElements[allTabbingElements.length - 1];
|
|
77
|
+
if (event.shiftKey && event.target === firstFocusableElement) {
|
|
78
|
+
lastFocusableElement.focus();
|
|
79
|
+
event.preventDefault();
|
|
80
|
+
return true;
|
|
81
|
+
} else if (!event.shiftKey && event.target === lastFocusableElement) {
|
|
82
|
+
firstFocusableElement.focus();
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
function getTabindex(node) {
|
|
89
|
+
var tabindexAttr = parseInt(node.getAttribute("tabindex"), 10);
|
|
90
|
+
if (!isNaN(tabindexAttr)) return tabindexAttr;
|
|
91
|
+
if (isContentEditable(node)) return 0;
|
|
92
|
+
return node.tabIndex;
|
|
93
|
+
}
|
|
94
|
+
function isContentEditable(node) {
|
|
95
|
+
return node.getAttribute("contentEditable");
|
|
8
96
|
}
|
|
97
|
+
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/FocusTrap.tsx
|
|
100
|
+
const FocusTrap = ({ container, initialFocusRef }) => {
|
|
101
|
+
const refLastFocus = useRef(null);
|
|
102
|
+
/**
|
|
103
|
+
* Handle focus lock on the modal
|
|
104
|
+
*/
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
const handleKeyEvent = (event) => {
|
|
107
|
+
if (container?.current) tabTrappingKey(event, container.current);
|
|
108
|
+
};
|
|
109
|
+
if (isBrowser) document.addEventListener("keydown", handleKeyEvent);
|
|
110
|
+
if (isBrowser && container?.current) {
|
|
111
|
+
const savePreviousFocus = () => {
|
|
112
|
+
if (candidateSelectors.findIndex((selector) => document.activeElement?.matches(selector)) !== -1) refLastFocus.current = document.activeElement;
|
|
113
|
+
};
|
|
114
|
+
if (initialFocusRef) {
|
|
115
|
+
savePreviousFocus();
|
|
116
|
+
requestAnimationFrame(() => {
|
|
117
|
+
initialFocusRef.current?.focus();
|
|
118
|
+
});
|
|
119
|
+
} else {
|
|
120
|
+
const allTabbingElements = getAllTabbingElements(container.current);
|
|
121
|
+
if (allTabbingElements[0]) {
|
|
122
|
+
savePreviousFocus();
|
|
123
|
+
allTabbingElements[0].focus();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return () => {
|
|
128
|
+
if (isBrowser) {
|
|
129
|
+
document.removeEventListener("keydown", handleKeyEvent);
|
|
130
|
+
refLastFocus.current?.focus();
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}, [container, initialFocusRef]);
|
|
134
|
+
return null;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/modalManager.ts
|
|
139
|
+
let modals = [];
|
|
140
|
+
/**
|
|
141
|
+
* Handle the order of the modals.
|
|
142
|
+
* Inspired by the material-ui implementation.
|
|
143
|
+
*/
|
|
144
|
+
const modalManager = {
|
|
145
|
+
add: (newModal) => {
|
|
146
|
+
modals.push(newModal);
|
|
147
|
+
},
|
|
148
|
+
remove: (oldModal) => {
|
|
149
|
+
modals = modals.filter((modal) => modal !== oldModal);
|
|
150
|
+
},
|
|
151
|
+
isTopModal: (modal) => !!modals.length && modals[modals.length - 1] === modal
|
|
152
|
+
};
|
|
153
|
+
function useModalManager(ref, open) {
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
if (open) modalManager.add(ref);
|
|
156
|
+
return () => {
|
|
157
|
+
modalManager.remove(ref);
|
|
158
|
+
};
|
|
159
|
+
}, [open, ref]);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region src/useScrollLock.ts
|
|
164
|
+
const useScrollLock = (refModal, open, showPortal, blockScroll, reserveScrollBarGap) => {
|
|
165
|
+
const oldRef = useRef(null);
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
if (open && refModal.current && blockScroll) {
|
|
168
|
+
oldRef.current = refModal.current;
|
|
169
|
+
disableBodyScroll(refModal.current, { reserveScrollBarGap });
|
|
170
|
+
}
|
|
171
|
+
return () => {
|
|
172
|
+
if (oldRef.current) {
|
|
173
|
+
enableBodyScroll(oldRef.current);
|
|
174
|
+
oldRef.current = null;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
}, [
|
|
178
|
+
open,
|
|
179
|
+
showPortal,
|
|
180
|
+
refModal,
|
|
181
|
+
blockScroll,
|
|
182
|
+
reserveScrollBarGap
|
|
183
|
+
]);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
//#endregion
|
|
187
|
+
//#region src/index.tsx
|
|
188
|
+
const classes = {
|
|
189
|
+
root: "react-responsive-modal-root",
|
|
190
|
+
overlay: "react-responsive-modal-overlay",
|
|
191
|
+
overlayAnimationIn: "react-responsive-modal-overlay-in",
|
|
192
|
+
overlayAnimationOut: "react-responsive-modal-overlay-out",
|
|
193
|
+
modalContainer: "react-responsive-modal-container",
|
|
194
|
+
modalContainerCenter: "react-responsive-modal-containerCenter",
|
|
195
|
+
modal: "react-responsive-modal-modal",
|
|
196
|
+
modalAnimationIn: "react-responsive-modal-modal-in",
|
|
197
|
+
modalAnimationOut: "react-responsive-modal-modal-out",
|
|
198
|
+
closeButton: "react-responsive-modal-closeButton"
|
|
199
|
+
};
|
|
200
|
+
const Modal = React.forwardRef(({ open, center, blockScroll = true, closeOnEsc = true, closeOnOverlayClick = true, container, showCloseIcon = true, closeIconId, closeIcon, focusTrapped = true, initialFocusRef = void 0, animationDuration = 300, classNames, styles, role = "dialog", ariaLabel, ariaDescribedby, ariaLabelledby, containerId, modalId, onClose, onEscKeyDown, onOverlayClick, onAnimationEnd, children, reserveScrollBarGap }, ref) => {
|
|
201
|
+
const refDialog = useForwardedRef(ref);
|
|
202
|
+
const refModal = useRef(null);
|
|
203
|
+
const refShouldClose = useRef(null);
|
|
204
|
+
const refContainer = useRef(null);
|
|
205
|
+
if (refContainer.current === null && isBrowser) refContainer.current = document.createElement("div");
|
|
206
|
+
const [showPortal, setShowPortal] = useState(false);
|
|
207
|
+
useModalManager(refModal, open);
|
|
208
|
+
useScrollLock(refModal, open, showPortal, blockScroll, reserveScrollBarGap);
|
|
209
|
+
const handleOpen = () => {
|
|
210
|
+
if (refContainer.current && !container && !document.body.contains(refContainer.current)) document.body.appendChild(refContainer.current);
|
|
211
|
+
document.addEventListener("keydown", handleKeydown);
|
|
212
|
+
};
|
|
213
|
+
const handleClose = () => {
|
|
214
|
+
if (refContainer.current && !container && document.body.contains(refContainer.current)) document.body.removeChild(refContainer.current);
|
|
215
|
+
document.removeEventListener("keydown", handleKeydown);
|
|
216
|
+
};
|
|
217
|
+
const handleKeydown = (event) => {
|
|
218
|
+
if (event.keyCode !== 27 || !modalManager.isTopModal(refModal)) return;
|
|
219
|
+
onEscKeyDown?.(event);
|
|
220
|
+
if (closeOnEsc) onClose();
|
|
221
|
+
};
|
|
222
|
+
useEffect(() => {
|
|
223
|
+
return () => {
|
|
224
|
+
if (showPortal) handleClose();
|
|
225
|
+
};
|
|
226
|
+
}, [showPortal]);
|
|
227
|
+
useEffect(() => {
|
|
228
|
+
if (open && !showPortal) {
|
|
229
|
+
setShowPortal(true);
|
|
230
|
+
handleOpen();
|
|
231
|
+
}
|
|
232
|
+
}, [open]);
|
|
233
|
+
const handleClickOverlay = (event) => {
|
|
234
|
+
if (refShouldClose.current === null) refShouldClose.current = true;
|
|
235
|
+
if (!refShouldClose.current) {
|
|
236
|
+
refShouldClose.current = null;
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
onOverlayClick?.(event);
|
|
240
|
+
if (closeOnOverlayClick) onClose();
|
|
241
|
+
refShouldClose.current = null;
|
|
242
|
+
};
|
|
243
|
+
const handleModalEvent = () => {
|
|
244
|
+
refShouldClose.current = false;
|
|
245
|
+
};
|
|
246
|
+
const handleAnimationEnd = () => {
|
|
247
|
+
if (!open) setShowPortal(false);
|
|
248
|
+
onAnimationEnd?.();
|
|
249
|
+
};
|
|
250
|
+
const containerModal = container || refContainer.current;
|
|
251
|
+
const overlayAnimation = open ? classNames?.overlayAnimationIn ?? classes.overlayAnimationIn : classNames?.overlayAnimationOut ?? classes.overlayAnimationOut;
|
|
252
|
+
const modalAnimation = open ? classNames?.modalAnimationIn ?? classes.modalAnimationIn : classNames?.modalAnimationOut ?? classes.modalAnimationOut;
|
|
253
|
+
return showPortal && containerModal ? createPortal(/* @__PURE__ */ jsxs("div", {
|
|
254
|
+
className: cx(classes.root, classNames?.root),
|
|
255
|
+
style: styles?.root,
|
|
256
|
+
"data-testid": "root",
|
|
257
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
258
|
+
className: cx(classes.overlay, classNames?.overlay),
|
|
259
|
+
"data-testid": "overlay",
|
|
260
|
+
"aria-hidden": true,
|
|
261
|
+
style: {
|
|
262
|
+
animation: `${overlayAnimation} ${animationDuration}ms`,
|
|
263
|
+
...styles?.overlay
|
|
264
|
+
}
|
|
265
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
266
|
+
ref: refModal,
|
|
267
|
+
id: containerId,
|
|
268
|
+
className: cx(classes.modalContainer, center && classes.modalContainerCenter, classNames?.modalContainer),
|
|
269
|
+
style: styles?.modalContainer,
|
|
270
|
+
"data-testid": "modal-container",
|
|
271
|
+
onClick: handleClickOverlay,
|
|
272
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
273
|
+
ref: refDialog,
|
|
274
|
+
className: cx(classes.modal, classNames?.modal),
|
|
275
|
+
style: {
|
|
276
|
+
animation: `${modalAnimation} ${animationDuration}ms`,
|
|
277
|
+
...styles?.modal
|
|
278
|
+
},
|
|
279
|
+
onMouseDown: handleModalEvent,
|
|
280
|
+
onMouseUp: handleModalEvent,
|
|
281
|
+
onClick: handleModalEvent,
|
|
282
|
+
onAnimationEnd: handleAnimationEnd,
|
|
283
|
+
id: modalId,
|
|
284
|
+
role,
|
|
285
|
+
"aria-modal": "true",
|
|
286
|
+
"aria-label": ariaLabel,
|
|
287
|
+
"aria-labelledby": ariaLabelledby,
|
|
288
|
+
"aria-describedby": ariaDescribedby,
|
|
289
|
+
"data-testid": "modal",
|
|
290
|
+
tabIndex: -1,
|
|
291
|
+
children: [
|
|
292
|
+
focusTrapped && /* @__PURE__ */ jsx(FocusTrap, {
|
|
293
|
+
container: refDialog,
|
|
294
|
+
initialFocusRef
|
|
295
|
+
}),
|
|
296
|
+
children,
|
|
297
|
+
showCloseIcon && /* @__PURE__ */ jsx(CloseIcon_default, {
|
|
298
|
+
classes,
|
|
299
|
+
classNames,
|
|
300
|
+
styles,
|
|
301
|
+
closeIcon,
|
|
302
|
+
onClick: onClose,
|
|
303
|
+
id: closeIconId
|
|
304
|
+
})
|
|
305
|
+
]
|
|
306
|
+
})
|
|
307
|
+
})]
|
|
308
|
+
}), containerModal) : null;
|
|
309
|
+
});
|
|
310
|
+
var src_default = Modal;
|
|
311
|
+
|
|
312
|
+
//#endregion
|
|
313
|
+
export { Modal, src_default as default };
|
|
314
|
+
//# sourceMappingURL=index.js.map
|