UNPKG

@atlaskit/popup

Version:

A popup displays brief content in an overlay.

233 lines (230 loc) 9.22 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.useCloseManager = void 0; var _react = require("react"); var _bindEventListener = require("bind-event-listener"); var _noop = _interopRequireDefault(require("@atlaskit/ds-lib/noop")); var _layering = require("@atlaskit/layering"); var _platformFeatureFlags = require("@atlaskit/platform-feature-flags"); var _isElementInteractive = require("./utils/is-element-interactive"); var _useAnimationFrame2 = require("./utils/use-animation-frame"); var useCloseManager = exports.useCloseManager = function useCloseManager(_ref) { var isOpen = _ref.isOpen, onClose = _ref.onClose, popupRef = _ref.popupRef, triggerRef = _ref.triggerRef, autoFocus = _ref.autoFocus, shouldDisableFocusTrap = _ref.shouldDisableFocusTrap, capture = _ref.shouldUseCaptureOnOutsideClick, shouldCloseOnTab = _ref.shouldCloseOnTab, shouldRenderToParent = _ref.shouldRenderToParent; var _useLayering = (0, _layering.useLayering)(), isLayerDisabled = _useLayering.isLayerDisabled, currentLevel = _useLayering.currentLevel; var _useAnimationFrame = (0, _useAnimationFrame2.useAnimationFrame)(), requestFrame = _useAnimationFrame.requestFrame, cancelAllFrames = _useAnimationFrame.cancelAllFrames; (0, _react.useEffect)(function () { if (!isOpen || !popupRef) { return _noop.default; } var inIframe = window && window.self !== window.top && (0, _platformFeatureFlags.fg)('fix-dropdown-close-outside-iframe'); var closePopup = function closePopup(event) { if (onClose) { var _currentLevel = null; if (event.target instanceof HTMLElement) { var _event$target$closest; _currentLevel = (_event$target$closest = event.target.closest("[data-ds--level]")) === null || _event$target$closest === void 0 ? void 0 : _event$target$closest.getAttribute('data-ds--level'); } _currentLevel ? onClose(event, Number(_currentLevel)) : onClose(event); } if (shouldDisableFocusTrap && (0, _platformFeatureFlags.fg)('platform_dst_popup-disable-focuslock')) { // Restoring the normal focus order for trigger. requestFrame(function () { triggerRef === null || triggerRef === void 0 || triggerRef.setAttribute('tabindex', '0'); if (popupRef && autoFocus) { popupRef.setAttribute('tabindex', '0'); } }); } }; // This check is required for cases where components like // Select or DDM are placed inside a Popup. A click // on a MenuItem or Option would close the Popup, without registering // a click on DDM/Select. // Users would have to call `onClose` manually to close the Popup in these cases. // You can see the bug in action here: // https://codesandbox.io/s/atlaskitpopup-default-forked-2eb87?file=/example.tsx:0-1788 var onClick = function onClick(event) { var target = event.target; var doesDomNodeExist = document.body.contains(target); if (!doesDomNodeExist && !inIframe) { return; } if (isLayerDisabled()) { if (target instanceof HTMLElement) { var _target$closest; var layeredElement = (_target$closest = target.closest) === null || _target$closest === void 0 ? void 0 : _target$closest.call(target, "[data-ds--level]"); if (layeredElement) { var closeType = layeredElement.getAttribute('[data-ds--close--type]'); if (closeType === 'single') { // if the close type is single, we won't close other disabled layers when clicking outside return; } var levelOfClickedLayer = layeredElement.getAttribute('data-ds--level'); if (levelOfClickedLayer && Number(levelOfClickedLayer) > currentLevel) { // won't trigger onClick event when we click in a higher layer. return; } } } } var isClickOnPopup = popupRef && popupRef.contains(target); var isClickOnTrigger = triggerRef && triggerRef.contains(target); if (!isClickOnPopup && !isClickOnTrigger) { closePopup(event); // If there was an outside click on a non-interactive element, the focus should be on the trigger. if (document.activeElement && !(0, _isElementInteractive.isInteractiveElement)(document.activeElement) && (0, _platformFeatureFlags.fg)('platform_dst_popup-disable-focuslock')) { triggerRef === null || triggerRef === void 0 || triggerRef.focus(); } } }; var onKeyDown = function onKeyDown(event) { if ((0, _platformFeatureFlags.fg)('platform_dst_popup-disable-focuslock')) { var key = event.key, shiftKey = event.shiftKey; if (shiftKey && key === 'Tab' && !shouldRenderToParent) { if (isLayerDisabled()) { return; } // We need to move the focus to the popup trigger when the popup is displayed in React.Portal. requestFrame(function () { var isPopupFocusOut = popupRef && !popupRef.contains(document.activeElement); if (isPopupFocusOut) { closePopup(event); if (currentLevel === 1) { triggerRef === null || triggerRef === void 0 || triggerRef.focus(); } } }); return; } if (key === 'Tab') { var _document$activeEleme; // We have cases where we need to close the Popup on Tab press. // Example: DropdownMenu if (shouldCloseOnTab) { if (isLayerDisabled()) { return; } closePopup(event); return; } if (isLayerDisabled() && (_document$activeEleme = document.activeElement) !== null && _document$activeEleme !== void 0 && _document$activeEleme.closest('[aria-modal]')) { return; } if (shouldDisableFocusTrap) { if (shouldRenderToParent) { // We need to move the focus to the previous interactive element before popup trigger requestFrame(function () { var isPopupFocusOut = popupRef && !popupRef.contains(document.activeElement); if (isPopupFocusOut) { closePopup(event); } }); } else { requestFrame(function () { if (!document.hasFocus()) { closePopup(event); } }); } return; } } if (isLayerDisabled()) { return; } if (key === 'Escape' || key === 'Esc') { if (triggerRef && autoFocus) { triggerRef.focus(); } closePopup(event); } } else { if (isLayerDisabled()) { return; } var _key = event.key; if (_key === 'Escape' || _key === 'Esc' || shouldCloseOnTab && _key === 'Tab') { closePopup(event); } } }; var parentUnbind; // if the popup is inside iframe, we want to listen to click events outside iframe, // to close the popup correctly in the iframe. if (inIframe && isOpen) { parentUnbind = (0, _bindEventListener.bind)(window.parent.window, { type: 'click', listener: onClick, options: { capture: capture } }); } var unbind = _noop.default; if ((0, _platformFeatureFlags.fg)('popup-onclose-fix')) { setTimeout(function () { unbind = (0, _bindEventListener.bindAll)(window, [{ type: 'click', listener: onClick, options: { capture: capture } }, { type: 'keydown', listener: onKeyDown }]); }, 0); } else { unbind = (0, _bindEventListener.bindAll)(window, [{ type: 'click', listener: onClick, options: { capture: capture } }, { type: 'keydown', listener: onKeyDown }]); } // bind onBlur event listener to fix popup not close when clicking on iframe outside var unbindBlur = _noop.default; unbindBlur = (0, _bindEventListener.bind)(window, { type: 'blur', listener: function onBlur(e) { if (isLayerDisabled() || !(document.activeElement instanceof HTMLIFrameElement)) { return; } closePopup(e); } }); return function () { var _parentUnbind; if ((0, _platformFeatureFlags.fg)('popup-onclose-fix')) { setTimeout(function () { unbind(); }, 0); } else { unbind(); } cancelAllFrames(); (_parentUnbind = parentUnbind) === null || _parentUnbind === void 0 || _parentUnbind(); unbindBlur(); }; }, [isOpen, onClose, popupRef, triggerRef, autoFocus, shouldDisableFocusTrap, capture, isLayerDisabled, shouldCloseOnTab, currentLevel, shouldRenderToParent, requestFrame, cancelAllFrames]); };