import React, { CSSProperties } from 'react'; import PropTypes from 'prop-types'; import cls from 'classnames'; import { cssClasses } from '@douyinfe/semi-foundation/modal/constants'; import ConfigContext, { ContextValue } from '../configProvider/context'; import Button from '../iconButton'; import Typography from '../typography'; import BaseComponent from '../_base/baseComponent'; import ModalContentFoundation, { ModalContentAdapter, ModalContentProps, ModalContentState } from '@douyinfe/semi-foundation/modal/modalContentFoundation'; import { get, isFunction, noop } from 'lodash'; import { IconClose } from '@douyinfe/semi-icons'; import FocusTrapHandle from "@douyinfe/semi-foundation/utils/FocusHandle"; let uuid = 0; export interface ModalContentReactProps extends ModalContentProps { children?: React.ReactNode } export default class ModalContent extends BaseComponent { static contextType = ConfigContext; static propTypes = { close: PropTypes.func, getContainerContext: PropTypes.func, contentClassName: PropTypes.string, maskClassName: PropTypes.string, onAnimationEnd: PropTypes.func, preventScroll: PropTypes.bool, }; static defaultProps = { close: noop, getContainerContext: noop, contentClassName: '', maskClassName: '' }; dialogId: string; private timeoutId: NodeJS.Timeout; modalDialogRef: React.MutableRefObject; foundation: ModalContentFoundation; context: ContextValue; focusTrapHandle: FocusTrapHandle; constructor(props: ModalContentProps) { super(props); this.state = { dialogMouseDown: false, prevFocusElement: FocusTrapHandle.getActiveElement(), }; this.foundation = new ModalContentFoundation(this.adapter); this.dialogId = `dialog-${uuid++}`; this.modalDialogRef = React.createRef(); } get adapter(): ModalContentAdapter { return { ...super.adapter, notifyClose: (e: React.MouseEvent) => { this.props.onClose(e); }, notifyDialogMouseDown: () => { this.setState({ dialogMouseDown: true }); }, notifyDialogMouseUp: () => { if (this.state.dialogMouseDown) { // Not setting setTimeout triggers close when modal external mouseUp this.timeoutId = setTimeout(() => { this.setState({ dialogMouseDown: false }); }, 0); } }, addKeyDownEventListener: () => { if (this.props.closeOnEsc) { document.addEventListener('keydown', this.foundation.handleKeyDown); } }, removeKeyDownEventListener: () => { if (this.props.closeOnEsc) { document.removeEventListener('keydown', this.foundation.handleKeyDown); } }, getMouseState: () => this.state.dialogMouseDown, modalDialogFocus: () => { const { preventScroll } = this.props; let activeElementInDialog; if (this.modalDialogRef) { const activeElement = FocusTrapHandle.getActiveElement(); activeElementInDialog = this.modalDialogRef.current.contains(activeElement); this.focusTrapHandle?.destroy(); this.focusTrapHandle = new FocusTrapHandle(this.modalDialogRef.current, { preventScroll }); } if (!activeElementInDialog) { this.modalDialogRef?.current?.focus({ preventScroll }); } }, modalDialogBlur: () => { this.modalDialogRef?.current.blur(); this.focusTrapHandle?.destroy(); }, prevFocusElementReFocus: () => { const { prevFocusElement } = this.state; const { preventScroll } = this.props; const focus = get(prevFocusElement, 'focus'); isFunction(focus) && prevFocusElement.focus({ preventScroll }); } }; } componentDidMount() { this.foundation.handleKeyDownEventListenerMount(); this.foundation.modalDialogFocus(); const nodes = FocusTrapHandle.getFocusableElements(this.modalDialogRef.current); if (!this.modalDialogRef.current.contains(document.activeElement)) { // focus on first focusable element nodes[0]?.focus(); } } componentWillUnmount() { clearTimeout(this.timeoutId); this.foundation.destroy(); } onKeyDown = (e: React.MouseEvent) => { this.foundation.handleKeyDown(e); }; // Record when clicking the modal box onDialogMouseDown = () => { this.foundation.handleDialogMouseDown(); }; // Cancel recording when clicking the modal box at the end onMaskMouseUp = () => { this.foundation.handleMaskMouseUp(); }; // onMaskClick will judge dialogMouseDown before onMaskMouseUp updates dialogMouseDown onMaskClick = (e: React.MouseEvent) => { this.foundation.handleMaskClick(e); }; close = (e: React.MouseEvent) => { this.foundation.close(e); }; getMaskElement = () => { const { ...props } = this.props; const { mask, maskClassName } = props; if (mask) { const className = cls(`${cssClasses.DIALOG}-mask`, { // [`${cssClasses.DIALOG}-mask-hidden`]: !props.visible, }); return
; } return null; }; renderCloseBtn = () => { const { closable, closeIcon, } = this.props; let closer; if (closable) { const iconType = closeIcon || ; closer = (