import type { ElementRef, TeactNode } from '../../lib/teact/teact'; import type React from '../../lib/teact/teact'; import { beginHeavyAnimation, useEffect, useRef } from '../../lib/teact/teact'; import type { TextPart } from '../../types'; import buildClassName from '../../util/buildClassName'; import captureKeyboardListeners from '../../util/captureKeyboardListeners'; import { disableDirectTextInput, enableDirectTextInput } from '../../util/directInputManager'; import trapFocus from '../../util/trapFocus'; import useContextMenuHandlers from '../../hooks/useContextMenuHandlers'; import useFrozenProps from '../../hooks/useFrozenProps'; import useHistoryBack from '../../hooks/useHistoryBack'; import useLastCallback from '../../hooks/useLastCallback'; import useLayoutEffectWithPrevDeps from '../../hooks/useLayoutEffectWithPrevDeps'; import useOldLang from '../../hooks/useOldLang'; import useShowTransition from '../../hooks/useShowTransition'; import Button, { type OwnProps as ButtonProps } from './Button'; import Menu from './Menu'; import ModalStarBalanceBar from './ModalStarBalanceBar'; import Portal from './Portal'; import './Modal.scss'; export const ANIMATION_DURATION = 200; export type OwnProps = { title?: string | TextPart[]; className?: string; contentClassName?: string; headerClassName?: string; dialogClassName?: string; isOpen?: boolean; header?: TeactNode; isSlim?: boolean; hasCloseButton?: boolean; hasAbsoluteCloseButton?: boolean; absoluteCloseButtonColor?: ButtonProps['color']; noBackdrop?: boolean; noBackdropClose?: boolean; children: React.ReactNode; style?: string; dialogStyle?: string; dialogRef?: ElementRef; isLowStackPriority?: boolean; dialogContent?: React.ReactNode; moreMenuItems?: TeactNode; headerRightToolBar?: TeactNode; withBalanceBar?: boolean; currencyInBalanceBar?: 'TON' | 'XTR'; isCondensedHeader?: boolean; noFreezeOnClose?: boolean; onClose: NoneToVoidFunction; onCloseAnimationEnd?: NoneToVoidFunction; onEnter?: NoneToVoidFunction; }; const Modal = (props: OwnProps) => { const { dialogRef, isOpen, noBackdropClose, noFreezeOnClose, onClose, onCloseAnimationEnd, onEnter, } = props; const { ref: modalRef, shouldRender, } = useShowTransition({ isOpen, withShouldRender: true, onCloseAnimationEnd, }); const shouldFreeze = !noFreezeOnClose && !isOpen; const { title, isLowStackPriority, header, children, className, contentClassName, headerClassName, dialogClassName, isSlim, hasCloseButton, hasAbsoluteCloseButton, absoluteCloseButtonColor = 'translucent', noBackdrop, style, dialogStyle, dialogContent, moreMenuItems, headerRightToolBar: headerToolBar, withBalanceBar, isCondensedHeader, currencyInBalanceBar = 'XTR', } = useFrozenProps(props, shouldFreeze); const localDialogRef = useRef(); const moreButtonRef = useRef(); const menuRef = useRef(); const { isContextMenuOpen, contextMenuAnchor, handleContextMenu, handleContextMenuClose, handleContextMenuHide, } = useContextMenuHandlers(moreButtonRef); const actualDialogRef = dialogRef || localDialogRef; const getRootElement = useLastCallback(() => actualDialogRef.current); const getTriggerElement = useLastCallback(() => moreButtonRef.current); const getMenuElement = useLastCallback(() => menuRef.current); const getLayout = useLastCallback(() => ({ withPortal: true })); const withCloseButton = hasCloseButton || hasAbsoluteCloseButton; useEffect(() => { if (!isOpen) { return undefined; } disableDirectTextInput(); return enableDirectTextInput; }, [isOpen]); const handleEnter = useLastCallback((e: KeyboardEvent) => { if (!onEnter) { return false; } e.preventDefault(); onEnter(); return true; }); useEffect(() => ( isOpen ? captureKeyboardListeners({ onEsc: onClose, onEnter: handleEnter }) : undefined ), [isOpen, onClose, handleEnter]); useEffect(() => (isOpen && modalRef.current ? trapFocus(modalRef.current) : undefined), [isOpen, modalRef]); useHistoryBack({ isActive: isOpen, onBack: onClose, }); useLayoutEffectWithPrevDeps(([prevIsOpen]) => { document.body.classList.toggle('has-open-dialog', Boolean(isOpen)); if (isOpen || (!isOpen && prevIsOpen !== undefined)) { beginHeavyAnimation(ANIMATION_DURATION); } return () => { document.body.classList.remove('has-open-dialog'); }; }, [isOpen]); const lang = useOldLang(); if (!shouldRender) { return undefined; } function renderHeader() { if (header) { return header; } const closeButton = withCloseButton ? (