@atlaskit/popup
Version:
A popup displays brief content in an overlay.
233 lines (230 loc) • 9.22 kB
JavaScript
;
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]);
};