1 | import { forwardRef, useEffect, createElement, useRef, useCallback } from 'react';
|
2 | import { Portal } from '@reach/portal';
|
3 | import { getOwnerDocument } from '@reach/utils/owner-document';
|
4 | import { isString } from '@reach/utils/type-check';
|
5 | import { noop } from '@reach/utils/noop';
|
6 | import { useCheckStyles } from '@reach/utils/dev-utils';
|
7 | import { useComposedRefs } from '@reach/utils/compose-refs';
|
8 | import { composeEventHandlers } from '@reach/utils/compose-event-handlers';
|
9 | import FocusLock from 'react-focus-lock';
|
10 | import { RemoveScroll } from 'react-remove-scroll';
|
11 | import PropTypes from 'prop-types';
|
12 |
|
13 | function _extends() {
|
14 | _extends = Object.assign || function (target) {
|
15 | for (var i = 1; i < arguments.length; i++) {
|
16 | var source = arguments[i];
|
17 |
|
18 | for (var key in source) {
|
19 | if (Object.prototype.hasOwnProperty.call(source, key)) {
|
20 | target[key] = source[key];
|
21 | }
|
22 | }
|
23 | }
|
24 |
|
25 | return target;
|
26 | };
|
27 |
|
28 | return _extends.apply(this, arguments);
|
29 | }
|
30 |
|
31 | function _objectWithoutPropertiesLoose(source, excluded) {
|
32 | if (source == null) return {};
|
33 | var target = {};
|
34 | var sourceKeys = Object.keys(source);
|
35 | var key, i;
|
36 |
|
37 | for (i = 0; i < sourceKeys.length; i++) {
|
38 | key = sourceKeys[i];
|
39 | if (excluded.indexOf(key) >= 0) continue;
|
40 | target[key] = source[key];
|
41 | }
|
42 |
|
43 | return target;
|
44 | }
|
45 |
|
46 | var _excluded = ["as", "isOpen"],
|
47 | _excluded2 = ["allowPinchZoom", "as", "dangerouslyBypassFocusLock", "dangerouslyBypassScrollLock", "initialFocusRef", "onClick", "onDismiss", "onKeyDown", "onMouseDown", "unstable_lockFocusAcrossFrames"],
|
48 | _excluded3 = ["as", "onClick", "onKeyDown"],
|
49 | _excluded4 = ["allowPinchZoom", "initialFocusRef", "isOpen", "onDismiss"];
|
50 | var overlayPropTypes = {
|
51 | allowPinchZoom: PropTypes.bool,
|
52 | dangerouslyBypassFocusLock: PropTypes.bool,
|
53 | dangerouslyBypassScrollLock: PropTypes.bool,
|
54 |
|
55 | initialFocusRef: function initialFocusRef() {
|
56 | return null;
|
57 | },
|
58 | onDismiss: PropTypes.func
|
59 | };
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 | var DialogOverlay = forwardRef(function DialogOverlay(_ref, forwardedRef) {
|
73 | var _ref$as = _ref.as,
|
74 | Comp = _ref$as === void 0 ? "div" : _ref$as,
|
75 | _ref$isOpen = _ref.isOpen,
|
76 | isOpen = _ref$isOpen === void 0 ? true : _ref$isOpen,
|
77 | props = _objectWithoutPropertiesLoose(_ref, _excluded);
|
78 |
|
79 | useCheckStyles("dialog");
|
80 |
|
81 |
|
82 |
|
83 | useEffect(function () {
|
84 | if (isOpen) {
|
85 |
|
86 | window.__REACH_DISABLE_TOOLTIPS = true;
|
87 | } else {
|
88 | window.requestAnimationFrame(function () {
|
89 |
|
90 |
|
91 | window.__REACH_DISABLE_TOOLTIPS = false;
|
92 | });
|
93 | }
|
94 | }, [isOpen]);
|
95 | return isOpen ? createElement(Portal, {
|
96 | "data-reach-dialog-wrapper": ""
|
97 | }, createElement(DialogInner, _extends({
|
98 | ref: forwardedRef,
|
99 | as: Comp
|
100 | }, props))) : null;
|
101 | });
|
102 |
|
103 | if (process.env.NODE_ENV !== "production") {
|
104 | DialogOverlay.displayName = "DialogOverlay";
|
105 | DialogOverlay.propTypes = _extends({}, overlayPropTypes, {
|
106 | isOpen: PropTypes.bool
|
107 | });
|
108 | }
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 |
|
115 | var DialogInner = forwardRef(function DialogInner(_ref2, forwardedRef) {
|
116 | var allowPinchZoom = _ref2.allowPinchZoom,
|
117 | _ref2$as = _ref2.as,
|
118 | Comp = _ref2$as === void 0 ? "div" : _ref2$as,
|
119 | _ref2$dangerouslyBypa = _ref2.dangerouslyBypassFocusLock,
|
120 | dangerouslyBypassFocusLock = _ref2$dangerouslyBypa === void 0 ? false : _ref2$dangerouslyBypa,
|
121 | _ref2$dangerouslyBypa2 = _ref2.dangerouslyBypassScrollLock,
|
122 | dangerouslyBypassScrollLock = _ref2$dangerouslyBypa2 === void 0 ? false : _ref2$dangerouslyBypa2,
|
123 | initialFocusRef = _ref2.initialFocusRef,
|
124 | onClick = _ref2.onClick,
|
125 | _ref2$onDismiss = _ref2.onDismiss,
|
126 | onDismiss = _ref2$onDismiss === void 0 ? noop : _ref2$onDismiss,
|
127 | onKeyDown = _ref2.onKeyDown,
|
128 | onMouseDown = _ref2.onMouseDown,
|
129 | unstable_lockFocusAcrossFrames = _ref2.unstable_lockFocusAcrossFrames,
|
130 | props = _objectWithoutPropertiesLoose(_ref2, _excluded2);
|
131 |
|
132 | var lockFocusAcrossFramesIsDefined = unstable_lockFocusAcrossFrames !== undefined;
|
133 |
|
134 | if (process.env.NODE_ENV !== "production") {
|
135 |
|
136 | useEffect(function () {
|
137 | if (lockFocusAcrossFramesIsDefined) {
|
138 | console.warn("The unstable_lockFocusAcrossFrames in @reach/dialog is deprecated. It will be removed in the next minor release.");
|
139 | }
|
140 | }, [lockFocusAcrossFramesIsDefined]);
|
141 | }
|
142 |
|
143 | var mouseDownTarget = useRef(null);
|
144 | var overlayNode = useRef(null);
|
145 | var ref = useComposedRefs(overlayNode, forwardedRef);
|
146 | var activateFocusLock = useCallback(function () {
|
147 | if (initialFocusRef && initialFocusRef.current) {
|
148 | initialFocusRef.current.focus();
|
149 | }
|
150 | }, [initialFocusRef]);
|
151 |
|
152 | function handleClick(event) {
|
153 | if (mouseDownTarget.current === event.target) {
|
154 | event.stopPropagation();
|
155 | onDismiss(event);
|
156 | }
|
157 | }
|
158 |
|
159 | function handleKeyDown(event) {
|
160 | if (event.key === "Escape") {
|
161 | event.stopPropagation();
|
162 | onDismiss(event);
|
163 | }
|
164 | }
|
165 |
|
166 | function handleMouseDown(event) {
|
167 | mouseDownTarget.current = event.target;
|
168 | }
|
169 |
|
170 | useEffect(function () {
|
171 | return overlayNode.current ? createAriaHider(overlayNode.current) : void null;
|
172 | }, []);
|
173 | return createElement(FocusLock, {
|
174 | autoFocus: true,
|
175 | returnFocus: true,
|
176 | onActivation: activateFocusLock,
|
177 | disabled: dangerouslyBypassFocusLock,
|
178 | crossFrame: unstable_lockFocusAcrossFrames != null ? unstable_lockFocusAcrossFrames : true
|
179 | }, createElement(RemoveScroll, {
|
180 | allowPinchZoom: allowPinchZoom,
|
181 | enabled: !dangerouslyBypassScrollLock
|
182 | }, createElement(Comp, _extends({}, props, {
|
183 | ref: ref,
|
184 | "data-reach-dialog-overlay": ""
|
185 | |
186 |
|
187 |
|
188 |
|
189 |
|
190 | ,
|
191 | onClick: composeEventHandlers(onClick, handleClick),
|
192 | onKeyDown: composeEventHandlers(onKeyDown, handleKeyDown),
|
193 | onMouseDown: composeEventHandlers(onMouseDown, handleMouseDown)
|
194 | }))));
|
195 | });
|
196 |
|
197 | if (process.env.NODE_ENV !== "production") {
|
198 | DialogOverlay.displayName = "DialogOverlay";
|
199 | DialogOverlay.propTypes = _extends({}, overlayPropTypes);
|
200 | }
|
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 |
|
212 |
|
213 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 | var DialogContent = forwardRef(function DialogContent(_ref3, forwardedRef) {
|
220 | var _ref3$as = _ref3.as,
|
221 | Comp = _ref3$as === void 0 ? "div" : _ref3$as,
|
222 | onClick = _ref3.onClick;
|
223 | _ref3.onKeyDown;
|
224 | var props = _objectWithoutPropertiesLoose(_ref3, _excluded3);
|
225 |
|
226 | return createElement(Comp, _extends({
|
227 | "aria-modal": "true",
|
228 | role: "dialog",
|
229 | tabIndex: -1
|
230 | }, props, {
|
231 | ref: forwardedRef,
|
232 | "data-reach-dialog-content": "",
|
233 | onClick: composeEventHandlers(onClick, function (event) {
|
234 | event.stopPropagation();
|
235 | })
|
236 | }));
|
237 | });
|
238 |
|
239 |
|
240 |
|
241 |
|
242 | if (process.env.NODE_ENV !== "production") {
|
243 | DialogContent.displayName = "DialogContent";
|
244 | DialogContent.propTypes = {
|
245 | "aria-label": ariaLabelType,
|
246 | "aria-labelledby": ariaLabelType
|
247 | };
|
248 | }
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 |
|
256 |
|
257 |
|
258 |
|
259 |
|
260 | var Dialog = forwardRef(function Dialog(_ref4, forwardedRef) {
|
261 | var _ref4$allowPinchZoom = _ref4.allowPinchZoom,
|
262 | allowPinchZoom = _ref4$allowPinchZoom === void 0 ? false : _ref4$allowPinchZoom,
|
263 | initialFocusRef = _ref4.initialFocusRef,
|
264 | isOpen = _ref4.isOpen,
|
265 | _ref4$onDismiss = _ref4.onDismiss,
|
266 | onDismiss = _ref4$onDismiss === void 0 ? noop : _ref4$onDismiss,
|
267 | props = _objectWithoutPropertiesLoose(_ref4, _excluded4);
|
268 |
|
269 | return createElement(DialogOverlay, {
|
270 | allowPinchZoom: allowPinchZoom,
|
271 | initialFocusRef: initialFocusRef,
|
272 | isOpen: isOpen,
|
273 | onDismiss: onDismiss
|
274 | }, createElement(DialogContent, _extends({
|
275 | ref: forwardedRef
|
276 | }, props)));
|
277 | });
|
278 |
|
279 |
|
280 |
|
281 |
|
282 | if (process.env.NODE_ENV !== "production") {
|
283 | Dialog.displayName = "Dialog";
|
284 | Dialog.propTypes = {
|
285 | isOpen: PropTypes.bool,
|
286 | onDismiss: PropTypes.func,
|
287 | "aria-label": ariaLabelType,
|
288 | "aria-labelledby": ariaLabelType
|
289 | };
|
290 | }
|
291 |
|
292 |
|
293 | function createAriaHider(dialogNode) {
|
294 | var originalValues = [];
|
295 | var rootNodes = [];
|
296 | var ownerDocument = getOwnerDocument(dialogNode);
|
297 |
|
298 | if (!dialogNode) {
|
299 | if (process.env.NODE_ENV !== "production") {
|
300 | console.warn("A ref has not yet been attached to a dialog node when attempting to call `createAriaHider`.");
|
301 | }
|
302 |
|
303 | return noop;
|
304 | }
|
305 |
|
306 | Array.prototype.forEach.call(ownerDocument.querySelectorAll("body > *"), function (node) {
|
307 | var _dialogNode$parentNod, _dialogNode$parentNod2;
|
308 |
|
309 | var portalNode = (_dialogNode$parentNod = dialogNode.parentNode) == null ? void 0 : (_dialogNode$parentNod2 = _dialogNode$parentNod.parentNode) == null ? void 0 : _dialogNode$parentNod2.parentNode;
|
310 |
|
311 | if (node === portalNode) {
|
312 | return;
|
313 | }
|
314 |
|
315 | var attr = node.getAttribute("aria-hidden");
|
316 | var alreadyHidden = attr !== null && attr !== "false";
|
317 |
|
318 | if (alreadyHidden) {
|
319 | return;
|
320 | }
|
321 |
|
322 | originalValues.push(attr);
|
323 | rootNodes.push(node);
|
324 | node.setAttribute("aria-hidden", "true");
|
325 | });
|
326 | return function () {
|
327 | rootNodes.forEach(function (node, index) {
|
328 | var originalValue = originalValues[index];
|
329 |
|
330 | if (originalValue === null) {
|
331 | node.removeAttribute("aria-hidden");
|
332 | } else {
|
333 | node.setAttribute("aria-hidden", originalValue);
|
334 | }
|
335 | });
|
336 | };
|
337 | }
|
338 |
|
339 | function ariaLabelType(props, propName, compName, location, propFullName) {
|
340 | var details = "\nSee https://www.w3.org/TR/wai-aria/#aria-label for details.";
|
341 |
|
342 | if (!props["aria-label"] && !props["aria-labelledby"]) {
|
343 | return new Error("A <" + compName + "> must have either an `aria-label` or `aria-labelledby` prop.\n " + details);
|
344 | }
|
345 |
|
346 | if (props["aria-label"] && props["aria-labelledby"]) {
|
347 | return new Error("You provided both `aria-label` and `aria-labelledby` props to a <" + compName + ">. If the a label for this component is visible on the screen, that label's component should be given a unique ID prop, and that ID should be passed as the `aria-labelledby` prop into <" + compName + ">. If the label cannot be determined programmatically from the content of the element, an alternative label should be provided as the `aria-label` prop, which will be used as an `aria-label` on the HTML tag." + details);
|
348 | } else if (props[propName] != null && !isString(props[propName])) {
|
349 | return new Error("Invalid prop `" + propName + "` supplied to `" + compName + "`. Expected `string`, received `" + (Array.isArray(propFullName) ? "array" : typeof propFullName) + "`.");
|
350 | }
|
351 |
|
352 | return null;
|
353 | }
|
354 |
|
355 | export default Dialog;
|
356 | export { Dialog, DialogContent, DialogOverlay };
|