1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 | import { __assign, __decorate, __extends } from "tslib";
|
17 | import classNames from "classnames";
|
18 | import * as React from "react";
|
19 | import { findDOMNode } from "react-dom";
|
20 | import { polyfill } from "react-lifecycles-compat";
|
21 | import { CSSTransition, TransitionGroup } from "react-transition-group";
|
22 | import { AbstractPureComponent2, Classes, Keys } from "../../common";
|
23 | import { DISPLAYNAME_PREFIX } from "../../common/props";
|
24 | import { isFunction } from "../../common/utils";
|
25 | import { Portal } from "../portal/portal";
|
26 |
|
27 |
|
28 | var Overlay = (function (_super) {
|
29 | __extends(Overlay, _super);
|
30 | function Overlay() {
|
31 | var _this = _super !== null && _super.apply(this, arguments) || this;
|
32 | _this.isAutoFocusing = false;
|
33 | _this.state = {
|
34 | hasEverOpened: _this.props.isOpen,
|
35 | };
|
36 |
|
37 | _this.containerElement = null;
|
38 |
|
39 | _this.startFocusTrapElement = null;
|
40 |
|
41 | _this.endFocusTrapElement = null;
|
42 | _this.refHandlers = {
|
43 |
|
44 |
|
45 | container: function (ref) { return (_this.containerElement = findDOMNode(ref)); },
|
46 | endFocusTrap: function (ref) { return (_this.endFocusTrapElement = ref); },
|
47 | startFocusTrap: function (ref) { return (_this.startFocusTrapElement = ref); },
|
48 | };
|
49 | _this.maybeRenderChild = function (child) {
|
50 | if (isFunction(child)) {
|
51 | child = child();
|
52 | }
|
53 | if (child == null) {
|
54 | return null;
|
55 | }
|
56 |
|
57 |
|
58 | var decoratedChild = typeof child === "object" ? (React.cloneElement(child, {
|
59 | className: classNames(child.props.className, Classes.OVERLAY_CONTENT),
|
60 | })) : (React.createElement("span", { className: Classes.OVERLAY_CONTENT }, child));
|
61 | var _a = _this.props, onOpening = _a.onOpening, onOpened = _a.onOpened, onClosing = _a.onClosing, transitionDuration = _a.transitionDuration, transitionName = _a.transitionName;
|
62 |
|
63 |
|
64 | var CSSTransitionImplicit = CSSTransition;
|
65 | return (React.createElement(CSSTransitionImplicit, { classNames: transitionName, onEntering: onOpening, onEntered: onOpened, onExiting: onClosing, onExited: _this.handleTransitionExited, timeout: transitionDuration, addEndListener: _this.handleTransitionAddEnd }, decoratedChild));
|
66 | };
|
67 | |
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 | _this.handleStartFocusTrapElementFocus = function (e) {
|
74 | var _a;
|
75 | if (!_this.props.enforceFocus || _this.isAutoFocusing) {
|
76 | return;
|
77 | }
|
78 |
|
79 |
|
80 |
|
81 |
|
82 | if (e.relatedTarget != null &&
|
83 | _this.containerElement.contains(e.relatedTarget) &&
|
84 | e.relatedTarget !== _this.endFocusTrapElement) {
|
85 | (_a = _this.endFocusTrapElement) === null || _a === void 0 ? void 0 : _a.focus();
|
86 | }
|
87 | };
|
88 | |
89 |
|
90 |
|
91 | _this.handleStartFocusTrapElementKeyDown = function (e) {
|
92 | var _a;
|
93 | if (!_this.props.enforceFocus) {
|
94 | return;
|
95 | }
|
96 |
|
97 |
|
98 | if (e.shiftKey && e.which === Keys.TAB) {
|
99 | var lastFocusableElement = _this.getKeyboardFocusableElements().pop();
|
100 | if (lastFocusableElement != null) {
|
101 | lastFocusableElement.focus();
|
102 | }
|
103 | else {
|
104 | (_a = _this.endFocusTrapElement) === null || _a === void 0 ? void 0 : _a.focus();
|
105 | }
|
106 | }
|
107 | };
|
108 | |
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 | _this.handleEndFocusTrapElementFocus = function (e) {
|
115 | var _a, _b;
|
116 |
|
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 | if (e.relatedTarget != null &&
|
123 | _this.containerElement.contains(e.relatedTarget) &&
|
124 | e.relatedTarget !== _this.startFocusTrapElement) {
|
125 | var firstFocusableElement = _this.getKeyboardFocusableElements().shift();
|
126 |
|
127 | if (!_this.isAutoFocusing && firstFocusableElement != null && firstFocusableElement !== e.relatedTarget) {
|
128 | firstFocusableElement.focus();
|
129 | }
|
130 | else {
|
131 | (_a = _this.startFocusTrapElement) === null || _a === void 0 ? void 0 : _a.focus();
|
132 | }
|
133 | }
|
134 | else {
|
135 | var lastFocusableElement = _this.getKeyboardFocusableElements().pop();
|
136 | if (lastFocusableElement != null) {
|
137 | lastFocusableElement.focus();
|
138 | }
|
139 | else {
|
140 |
|
141 | (_b = _this.startFocusTrapElement) === null || _b === void 0 ? void 0 : _b.focus();
|
142 | }
|
143 | }
|
144 | };
|
145 | _this.handleTransitionExited = function (node) {
|
146 | var _a, _b;
|
147 | if (_this.props.shouldReturnFocusOnClose && _this.lastActiveElementBeforeOpened instanceof HTMLElement) {
|
148 | _this.lastActiveElementBeforeOpened.focus();
|
149 | }
|
150 | (_b = (_a = _this.props).onClosed) === null || _b === void 0 ? void 0 : _b.call(_a, node);
|
151 | };
|
152 | _this.handleBackdropMouseDown = function (e) {
|
153 | var _a;
|
154 | var _b = _this.props, backdropProps = _b.backdropProps, canOutsideClickClose = _b.canOutsideClickClose, enforceFocus = _b.enforceFocus, onClose = _b.onClose;
|
155 | if (canOutsideClickClose) {
|
156 | onClose === null || onClose === void 0 ? void 0 : onClose(e);
|
157 | }
|
158 | if (enforceFocus) {
|
159 | _this.bringFocusInsideOverlay();
|
160 | }
|
161 | (_a = backdropProps === null || backdropProps === void 0 ? void 0 : backdropProps.onMouseDown) === null || _a === void 0 ? void 0 : _a.call(backdropProps, e);
|
162 | };
|
163 | _this.handleDocumentClick = function (e) {
|
164 | var _a = _this.props, canOutsideClickClose = _a.canOutsideClickClose, isOpen = _a.isOpen, onClose = _a.onClose;
|
165 |
|
166 | var eventTarget = (e.composed ? e.composedPath()[0] : e.target);
|
167 | var stackIndex = Overlay_1.openStack.indexOf(_this);
|
168 | var isClickInThisOverlayOrDescendant = Overlay_1.openStack
|
169 | .slice(stackIndex)
|
170 | .some(function (_a) {
|
171 | var elem = _a.containerElement;
|
172 |
|
173 |
|
174 | return elem && elem.contains(eventTarget) && !elem.isSameNode(eventTarget);
|
175 | });
|
176 | if (isOpen && !isClickInThisOverlayOrDescendant && canOutsideClickClose) {
|
177 |
|
178 | onClose === null || onClose === void 0 ? void 0 : onClose(e);
|
179 | }
|
180 | };
|
181 | |
182 |
|
183 |
|
184 |
|
185 | _this.handleDocumentFocus = function (e) {
|
186 |
|
187 | var eventTarget = e.composed ? e.composedPath()[0] : e.target;
|
188 | if (_this.props.enforceFocus &&
|
189 | _this.containerElement != null &&
|
190 | eventTarget instanceof Node &&
|
191 | !_this.containerElement.contains(eventTarget)) {
|
192 |
|
193 | e.preventDefault();
|
194 | e.stopImmediatePropagation();
|
195 | _this.bringFocusInsideOverlay();
|
196 | }
|
197 | };
|
198 | _this.handleKeyDown = function (e) {
|
199 | var _a = _this.props, canEscapeKeyClose = _a.canEscapeKeyClose, onClose = _a.onClose;
|
200 |
|
201 |
|
202 | if (e.which === Keys.ESCAPE && canEscapeKeyClose) {
|
203 | onClose === null || onClose === void 0 ? void 0 : onClose(e);
|
204 |
|
205 | e.preventDefault();
|
206 | }
|
207 | };
|
208 | _this.handleTransitionAddEnd = function () {
|
209 |
|
210 | };
|
211 | return _this;
|
212 | }
|
213 | Overlay_1 = Overlay;
|
214 | Overlay.getDerivedStateFromProps = function (_a) {
|
215 | var hasEverOpened = _a.isOpen;
|
216 | if (hasEverOpened) {
|
217 | return { hasEverOpened: hasEverOpened };
|
218 | }
|
219 | return null;
|
220 | };
|
221 | Overlay.prototype.render = function () {
|
222 | var _a;
|
223 | var _b;
|
224 |
|
225 | if (this.props.lazy && !this.state.hasEverOpened) {
|
226 | return null;
|
227 | }
|
228 | var _c = this.props, autoFocus = _c.autoFocus, children = _c.children, className = _c.className, enforceFocus = _c.enforceFocus, usePortal = _c.usePortal, isOpen = _c.isOpen;
|
229 |
|
230 |
|
231 |
|
232 | var childrenWithTransitions = isOpen ? (_b = React.Children.map(children, this.maybeRenderChild)) !== null && _b !== void 0 ? _b : [] : [];
|
233 | var maybeBackdrop = this.maybeRenderBackdrop();
|
234 | if (maybeBackdrop !== null) {
|
235 | childrenWithTransitions.unshift(maybeBackdrop);
|
236 | }
|
237 | if (isOpen && (autoFocus || enforceFocus) && childrenWithTransitions.length > 0) {
|
238 | childrenWithTransitions.unshift(this.renderDummyElement("__start", {
|
239 | className: Classes.OVERLAY_START_FOCUS_TRAP,
|
240 | onFocus: this.handleStartFocusTrapElementFocus,
|
241 | onKeyDown: this.handleStartFocusTrapElementKeyDown,
|
242 | ref: this.refHandlers.startFocusTrap,
|
243 | }));
|
244 | if (enforceFocus) {
|
245 | childrenWithTransitions.push(this.renderDummyElement("__end", {
|
246 | className: Classes.OVERLAY_END_FOCUS_TRAP,
|
247 | onFocus: this.handleEndFocusTrapElementFocus,
|
248 | ref: this.refHandlers.endFocusTrap,
|
249 | }));
|
250 | }
|
251 | }
|
252 | var containerClasses = classNames(Classes.OVERLAY, (_a = {},
|
253 | _a[Classes.OVERLAY_OPEN] = isOpen,
|
254 | _a[Classes.OVERLAY_INLINE] = !usePortal,
|
255 | _a), className);
|
256 | var transitionGroup = (React.createElement(TransitionGroup, { appear: true, "aria-live": "polite", className: containerClasses, component: "div", onKeyDown: this.handleKeyDown, ref: this.refHandlers.container }, childrenWithTransitions));
|
257 | if (usePortal) {
|
258 | return (React.createElement(Portal, { className: this.props.portalClassName, container: this.props.portalContainer }, transitionGroup));
|
259 | }
|
260 | else {
|
261 | return transitionGroup;
|
262 | }
|
263 | };
|
264 | Overlay.prototype.componentDidMount = function () {
|
265 | if (this.props.isOpen) {
|
266 | this.overlayWillOpen();
|
267 | }
|
268 | };
|
269 | Overlay.prototype.componentDidUpdate = function (prevProps) {
|
270 | if (prevProps.isOpen && !this.props.isOpen) {
|
271 | this.overlayWillClose();
|
272 | }
|
273 | else if (!prevProps.isOpen && this.props.isOpen) {
|
274 | this.overlayWillOpen();
|
275 | }
|
276 | };
|
277 | Overlay.prototype.componentWillUnmount = function () {
|
278 | this.overlayWillClose();
|
279 | };
|
280 | |
281 |
|
282 |
|
283 |
|
284 | Overlay.prototype.bringFocusInsideOverlay = function () {
|
285 | var _this = this;
|
286 |
|
287 | return this.requestAnimationFrame(function () {
|
288 | var _a;
|
289 |
|
290 |
|
291 | if (_this.containerElement == null || document.activeElement == null || !_this.props.isOpen) {
|
292 | return;
|
293 | }
|
294 | var isFocusOutsideModal = !_this.containerElement.contains(document.activeElement);
|
295 | if (isFocusOutsideModal) {
|
296 | (_a = _this.startFocusTrapElement) === null || _a === void 0 ? void 0 : _a.focus();
|
297 | _this.isAutoFocusing = false;
|
298 | }
|
299 | });
|
300 | };
|
301 | Overlay.prototype.maybeRenderBackdrop = function () {
|
302 | var _a = this.props, backdropClassName = _a.backdropClassName, backdropProps = _a.backdropProps, hasBackdrop = _a.hasBackdrop, isOpen = _a.isOpen, transitionDuration = _a.transitionDuration, transitionName = _a.transitionName;
|
303 | if (hasBackdrop && isOpen) {
|
304 | return (React.createElement(CSSTransition, { classNames: transitionName, key: "__backdrop", timeout: transitionDuration, addEndListener: this.handleTransitionAddEnd },
|
305 | React.createElement("div", __assign({}, backdropProps, { className: classNames(Classes.OVERLAY_BACKDROP, backdropClassName, backdropProps === null || backdropProps === void 0 ? void 0 : backdropProps.className), onMouseDown: this.handleBackdropMouseDown }))));
|
306 | }
|
307 | else {
|
308 | return null;
|
309 | }
|
310 | };
|
311 | Overlay.prototype.renderDummyElement = function (key, props) {
|
312 | var _a = this.props, transitionDuration = _a.transitionDuration, transitionName = _a.transitionName;
|
313 | return (React.createElement(CSSTransition, { classNames: transitionName, key: key, addEndListener: this.handleTransitionAddEnd, timeout: transitionDuration, unmountOnExit: true },
|
314 | React.createElement("div", __assign({ tabIndex: 0 }, props))));
|
315 | };
|
316 | Overlay.prototype.getKeyboardFocusableElements = function () {
|
317 | var focusableElements = this.containerElement !== null
|
318 | ? Array.from(
|
319 |
|
320 |
|
321 |
|
322 | this.containerElement.querySelectorAll([
|
323 | 'a[href]:not([tabindex="-1"])',
|
324 | 'button:not([disabled]):not([tabindex="-1"])',
|
325 | 'details:not([tabindex="-1"])',
|
326 | 'input:not([disabled]):not([tabindex="-1"])',
|
327 | 'select:not([disabled]):not([tabindex="-1"])',
|
328 | 'textarea:not([disabled]):not([tabindex="-1"])',
|
329 | '[tabindex]:not([tabindex="-1"])',
|
330 | ].join(",")))
|
331 | : [];
|
332 | return focusableElements.filter(function (el) {
|
333 | return !el.classList.contains(Classes.OVERLAY_START_FOCUS_TRAP) &&
|
334 | !el.classList.contains(Classes.OVERLAY_END_FOCUS_TRAP);
|
335 | });
|
336 | };
|
337 | Overlay.prototype.overlayWillClose = function () {
|
338 | document.removeEventListener("focus", this.handleDocumentFocus, true);
|
339 | document.removeEventListener("mousedown", this.handleDocumentClick);
|
340 | var openStack = Overlay_1.openStack;
|
341 | var stackIndex = openStack.indexOf(this);
|
342 | if (stackIndex !== -1) {
|
343 | openStack.splice(stackIndex, 1);
|
344 | if (openStack.length > 0) {
|
345 | var lastOpenedOverlay = Overlay_1.getLastOpened();
|
346 | if (lastOpenedOverlay.props.enforceFocus) {
|
347 | lastOpenedOverlay.bringFocusInsideOverlay();
|
348 | document.addEventListener("focus", lastOpenedOverlay.handleDocumentFocus, true);
|
349 | }
|
350 | }
|
351 | if (openStack.filter(function (o) { return o.props.usePortal && o.props.hasBackdrop; }).length === 0) {
|
352 | document.body.classList.remove(Classes.OVERLAY_OPEN);
|
353 | }
|
354 | }
|
355 | };
|
356 | Overlay.prototype.overlayWillOpen = function () {
|
357 | var getLastOpened = Overlay_1.getLastOpened, openStack = Overlay_1.openStack;
|
358 | if (openStack.length > 0) {
|
359 | document.removeEventListener("focus", getLastOpened().handleDocumentFocus, true);
|
360 | }
|
361 | openStack.push(this);
|
362 | if (this.props.autoFocus) {
|
363 | this.isAutoFocusing = true;
|
364 | this.bringFocusInsideOverlay();
|
365 | }
|
366 | if (this.props.enforceFocus) {
|
367 |
|
368 |
|
369 | document.addEventListener("focus", this.handleDocumentFocus, true);
|
370 | }
|
371 | if (this.props.canOutsideClickClose && !this.props.hasBackdrop) {
|
372 | document.addEventListener("mousedown", this.handleDocumentClick);
|
373 | }
|
374 | if (this.props.hasBackdrop && this.props.usePortal) {
|
375 |
|
376 | document.body.classList.add(Classes.OVERLAY_OPEN);
|
377 | }
|
378 | this.lastActiveElementBeforeOpened = document.activeElement;
|
379 | };
|
380 | var Overlay_1;
|
381 | Overlay.displayName = DISPLAYNAME_PREFIX + ".Overlay";
|
382 | Overlay.defaultProps = {
|
383 | autoFocus: true,
|
384 | backdropProps: {},
|
385 | canEscapeKeyClose: true,
|
386 | canOutsideClickClose: true,
|
387 | enforceFocus: true,
|
388 | hasBackdrop: true,
|
389 | isOpen: false,
|
390 | lazy: true,
|
391 | shouldReturnFocusOnClose: true,
|
392 | transitionDuration: 300,
|
393 | transitionName: Classes.OVERLAY,
|
394 | usePortal: true,
|
395 | };
|
396 | Overlay.openStack = [];
|
397 | Overlay.getLastOpened = function () { return Overlay_1.openStack[Overlay_1.openStack.length - 1]; };
|
398 | Overlay = Overlay_1 = __decorate([
|
399 | polyfill
|
400 | ], Overlay);
|
401 | return Overlay;
|
402 | }(AbstractPureComponent2));
|
403 | export { Overlay };
|
404 |
|
\ | No newline at end of file |