UNPKG

@rc-component/dialog

Version:
205 lines (195 loc) 6.41 kB
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } import classNames from 'classnames'; import contains from "@rc-component/util/es/Dom/contains"; import useId from "@rc-component/util/es/hooks/useId"; import KeyCode from "@rc-component/util/es/KeyCode"; import pickAttrs from "@rc-component/util/es/pickAttrs"; import * as React from 'react'; import { useEffect, useRef } from 'react'; import { getMotionName } from "../util"; import Content from "./Content"; import Mask from "./Mask"; import { warning } from "@rc-component/util/es/warning"; const Dialog = props => { const { prefixCls = 'rc-dialog', zIndex, visible = false, keyboard = true, focusTriggerAfterClose = true, // scrollLocker, // Wrapper wrapStyle, wrapClassName, wrapProps, onClose, afterOpenChange, afterClose, // Dialog transitionName, animation, closable = true, // Mask mask = true, maskTransitionName, maskAnimation, maskClosable = true, maskStyle, maskProps, rootClassName, rootStyle, classNames: modalClassNames, styles: modalStyles } = props; if (process.env.NODE_ENV !== 'production') { ['wrapStyle', 'bodyStyle', 'maskStyle'].forEach(prop => { // (prop in props) && console.error(`Warning: ${prop} is deprecated, please use styles instead.`) warning(!(prop in props), `${prop} is deprecated, please use styles instead.`); }); if ('wrapClassName' in props) { warning(false, `wrapClassName is deprecated, please use classNames instead.`); } } const lastOutSideActiveElementRef = useRef(); const wrapperRef = useRef(); const contentRef = useRef(); const [animatedVisible, setAnimatedVisible] = React.useState(visible); // ========================== Init ========================== const ariaId = useId(); function saveLastOutSideActiveElementRef() { if (!contains(wrapperRef.current, document.activeElement)) { lastOutSideActiveElementRef.current = document.activeElement; } } function focusDialogContent() { if (!contains(wrapperRef.current, document.activeElement)) { contentRef.current?.focus(); } } // ========================= Events ========================= // Close action will trigger by: // 1. When hide motion end // 2. Controlled `open` to `false` immediately after set to `true` which will not trigger motion function doClose() { // Clean up scroll bar & focus back setAnimatedVisible(false); if (mask && lastOutSideActiveElementRef.current && focusTriggerAfterClose) { try { lastOutSideActiveElementRef.current.focus({ preventScroll: true }); } catch (e) { // Do nothing } lastOutSideActiveElementRef.current = null; } // Trigger afterClose only when change visible from true to false if (animatedVisible) { afterClose?.(); } } function onDialogVisibleChanged(newVisible) { // Try to focus if (newVisible) { focusDialogContent(); } else { doClose(); } afterOpenChange?.(newVisible); } function onInternalClose(e) { onClose?.(e); } // >>> Content const contentClickRef = useRef(false); const contentTimeoutRef = useRef(); // We need record content click incase content popup out of dialog const onContentMouseDown = () => { clearTimeout(contentTimeoutRef.current); contentClickRef.current = true; }; const onContentMouseUp = () => { contentTimeoutRef.current = setTimeout(() => { contentClickRef.current = false; }); }; // >>> Wrapper // Close only when element not on dialog let onWrapperClick = null; if (maskClosable) { onWrapperClick = e => { if (contentClickRef.current) { contentClickRef.current = false; } else if (wrapperRef.current === e.target) { onInternalClose(e); } }; } function onWrapperKeyDown(e) { if (keyboard && e.keyCode === KeyCode.ESC) { e.stopPropagation(); onInternalClose(e); return; } // keep focus inside dialog if (visible && e.keyCode === KeyCode.TAB) { contentRef.current.changeActive(!e.shiftKey); } } // ========================= Effect ========================= useEffect(() => { if (visible) { setAnimatedVisible(true); saveLastOutSideActiveElementRef(); } else if (animatedVisible && contentRef.current.enableMotion() && !contentRef.current.inMotion()) { doClose(); } }, [visible]); // Remove direct should also check the scroll bar update useEffect(() => () => { clearTimeout(contentTimeoutRef.current); }, []); const mergedStyle = { zIndex, ...wrapStyle, ...modalStyles?.wrapper, display: !animatedVisible ? 'none' : null }; // ========================= Render ========================= return /*#__PURE__*/React.createElement("div", _extends({ className: classNames(`${prefixCls}-root`, rootClassName), style: rootStyle }, pickAttrs(props, { data: true })), /*#__PURE__*/React.createElement(Mask, { prefixCls: prefixCls, visible: mask && visible, motionName: getMotionName(prefixCls, maskTransitionName, maskAnimation), style: { zIndex, ...maskStyle, ...modalStyles?.mask }, maskProps: maskProps, className: modalClassNames?.mask }), /*#__PURE__*/React.createElement("div", _extends({ tabIndex: -1, onKeyDown: onWrapperKeyDown, className: classNames(`${prefixCls}-wrap`, wrapClassName, modalClassNames?.wrapper), ref: wrapperRef, onClick: onWrapperClick, style: mergedStyle }, wrapProps), /*#__PURE__*/React.createElement(Content, _extends({}, props, { onMouseDown: onContentMouseDown, onMouseUp: onContentMouseUp, ref: contentRef, closable: closable, ariaId: ariaId, prefixCls: prefixCls, visible: visible && animatedVisible, onClose: onInternalClose, onVisibleChanged: onDialogVisibleChanged, motionName: getMotionName(prefixCls, transitionName, animation) })))); }; export default Dialog;