rsuite
Version:
A suite of react components
228 lines (226 loc) • 8.35 kB
JavaScript
'use client';
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.default = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _react = _interopRequireWildcard(require("react"));
var _classnames = _interopRequireDefault(require("classnames"));
var _contains = _interopRequireDefault(require("dom-lib/contains"));
var _on = _interopRequireDefault(require("dom-lib/on"));
var _ModalManager = _interopRequireDefault(require("./ModalManager"));
var _Fade = _interopRequireDefault(require("../../Animation/Fade"));
var _Box = _interopRequireDefault(require("../Box"));
var _OverlayProvider = require("./OverlayProvider");
var _constants = require("../constants");
var _hooks = require("../hooks");
var _utils = require("../utils");
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
let manager;
function getManager() {
if (!manager) manager = new _ModalManager.default();
return manager;
}
const useModalManager = () => {
const modalManager = getManager();
const modal = (0, _react.useRef)({
dialog: null,
backdrop: null
});
return {
get dialog() {
var _modal$current;
return (_modal$current = modal.current) === null || _modal$current === void 0 ? void 0 : _modal$current.dialog;
},
add: (containerElement, containerClassName) => modalManager.add(modal.current, containerElement, containerClassName),
remove: () => modalManager.remove(modal.current),
isTopModal: () => modalManager.isTopModal(modal.current),
setDialogRef: (0, _react.useCallback)(ref => {
modal.current.dialog = ref;
}, []),
setBackdropRef: (0, _react.useCallback)(ref => {
modal.current.backdrop = ref;
}, [])
};
};
const Modal = (0, _utils.forwardRef)((props, ref) => {
const {
as,
children,
transition: Transition,
dialogTransitionTimeout,
style,
className,
container,
animationProps,
containerClassName,
keyboard = true,
enforceFocus = true,
backdrop = true,
backdropTransitionTimeout,
backdropStyle,
backdropClassName,
open,
autoFocus = true,
onEsc,
onExit,
onExiting,
onExited,
onEnter,
onEntering,
onEntered,
onClose,
onOpen,
...rest
} = props;
const [exited, setExited] = (0, _react.useState)(!open);
const {
Portal,
target: containerElement
} = (0, _hooks.usePortal)({
container
});
const modal = useModalManager();
if (open) {
if (exited) setExited(false);
} else if (!Transition && !exited) {
setExited(true);
}
const mountModal = open || Transition && !exited;
const lastFocus = (0, _react.useRef)(null);
const handleDocumentKeyDown = (0, _hooks.useEventCallback)(event => {
if (keyboard && event.key === _constants.KEY_VALUES.ESC && modal.isTopModal()) {
onEsc === null || onEsc === void 0 || onEsc(event);
onClose === null || onClose === void 0 || onClose(event);
}
});
const restoreLastFocus = (0, _react.useCallback)(() => {
if (lastFocus.current) {
var _lastFocus$current$fo, _lastFocus$current;
(_lastFocus$current$fo = (_lastFocus$current = lastFocus.current).focus) === null || _lastFocus$current$fo === void 0 || _lastFocus$current$fo.call(_lastFocus$current);
lastFocus.current = null;
}
}, []);
/**
* Determines if the currently focused element is inside the dialog,
* and if not, returns the focus to the dialog.
*
*/
const handleFocusDialog = (0, _hooks.useEventCallback)(onBeforeFocusCallback => {
const currentActiveElement = document.activeElement;
const dialog = modal.dialog;
if (dialog && currentActiveElement && !(0, _contains.default)(dialog, currentActiveElement)) {
onBeforeFocusCallback === null || onBeforeFocusCallback === void 0 || onBeforeFocusCallback();
dialog.focus();
}
});
const handleEnforceFocus = (0, _hooks.useEventCallback)(() => {
if (!enforceFocus || !modal.isTopModal()) {
return;
}
handleFocusDialog();
});
const documentKeyDownListener = (0, _react.useRef)(null);
const documentFocusListener = (0, _react.useRef)(null);
const handleOpen = (0, _hooks.useEventCallback)(() => {
if (containerElement) {
modal.add(containerElement, containerClassName);
}
if (!documentKeyDownListener.current) {
documentKeyDownListener.current = (0, _on.default)(document, 'keydown', handleDocumentKeyDown);
}
if (!documentFocusListener.current) {
documentFocusListener.current = (0, _on.default)(document, 'focus', handleEnforceFocus, true);
}
if (autoFocus) {
handleFocusDialog(() => {
lastFocus.current = document.activeElement;
});
}
onOpen === null || onOpen === void 0 || onOpen();
});
const handleClose = (0, _hooks.useEventCallback)(() => {
var _documentKeyDownListe, _documentFocusListene;
modal.remove();
(_documentKeyDownListe = documentKeyDownListener.current) === null || _documentKeyDownListe === void 0 || _documentKeyDownListe.off();
documentKeyDownListener.current = null;
(_documentFocusListene = documentFocusListener.current) === null || _documentFocusListene === void 0 || _documentFocusListene.off();
documentFocusListener.current = null;
restoreLastFocus();
});
(0, _react.useEffect)(() => {
if (!open) {
return;
}
handleOpen();
}, [open, handleOpen]);
(0, _react.useEffect)(() => {
if (!exited) {
return;
}
handleClose();
}, [exited, handleClose]);
(0, _hooks.useWillUnmount)(() => {
handleClose();
});
const handleExited = (0, _react.useCallback)(() => {
setExited(true);
}, []);
const overlayContainer = (0, _react.useCallback)(() => {
return modal.dialog;
}, [modal.dialog]);
if (!mountModal) {
return null;
}
const renderBackdrop = () => {
if (Transition) {
return /*#__PURE__*/_react.default.createElement(_Fade.default, {
transitionAppear: true,
in: open,
timeout: backdropTransitionTimeout
}, (fadeProps, ref) => {
const {
className,
...rest
} = fadeProps;
return /*#__PURE__*/_react.default.createElement("div", (0, _extends2.default)({
"aria-hidden": true,
"data-testid": "backdrop"
}, rest, {
style: backdropStyle,
ref: (0, _utils.mergeRefs)(modal.setBackdropRef, ref),
className: (0, _classnames.default)(backdropClassName, className)
}));
});
}
return /*#__PURE__*/_react.default.createElement("div", {
"aria-hidden": true,
style: backdropStyle,
className: backdropClassName
});
};
const dialogElement = Transition ? /*#__PURE__*/_react.default.createElement(Transition, (0, _extends2.default)({}, animationProps, {
transitionAppear: true,
unmountOnExit: true,
in: open,
timeout: dialogTransitionTimeout,
onExit: onExit,
onExiting: onExiting,
onExited: (0, _utils.createChainedFunction)(handleExited, onExited),
onEnter: onEnter,
onEntering: onEntering,
onEntered: onEntered
}), children) : children;
return /*#__PURE__*/_react.default.createElement(_OverlayProvider.OverlayProvider, {
overlayContainer: overlayContainer
}, /*#__PURE__*/_react.default.createElement(Portal, null, backdrop && renderBackdrop(), /*#__PURE__*/_react.default.createElement(_Box.default, (0, _extends2.default)({
as: as
}, rest, {
ref: (0, _utils.mergeRefs)(modal.setDialogRef, ref),
style: style,
className: className,
tabIndex: -1
}), dialogElement)));
});
Modal.displayName = 'OverlayModal';
var _default = exports.default = Modal;