import { useCallback, useEffect, useRef } from 'react'; import type { MouseEvent as ReactMouseEvent } from 'react'; /** * لایه‌هایی که خودشان با Esc بسته می‌شوند و مودال نباید جایشان بسته شود: * منوی باز react-select و تقویم شمسی. */ const OPEN_LAYER_SELECTOR = '.cp-select__menu, .cp-calendar'; /** * رفتار بستنِ یک مودال: کلیک روی پس‌زمینه و کلید Esc. * * قبلاً پس‌زمینه یک `onClick={onClose}` ساده داشت و هر کلیکی که *روی آن تمام می‌شد* * مودال را می‌بست — انتخاب متن داخل فرم و رها کردن موس بیرون، یا کلیک روی گزینه‌ای * که همان لحظه unmount می‌شد. بستنِ ناخواستهٔ فرمِ نیمه‌پرشده آزاردهنده‌ترین باگ * پنل بود، پس شرط سخت‌تر شد: هم فشردن و هم رها کردن باید روی خودِ پس‌زمینه باشد. * * ```tsx * const dismiss = useOverlayDismiss(onClose); *
* ``` */ export function useOverlayDismiss(onClose: () => void, enabled = true) { const pressedOnOverlay = useRef(false); useEffect(() => { if (!enabled) return; const onKey = (e: KeyboardEvent) => { if (e.key !== 'Escape') return; // درا‌پ‌داون یا تقویمِ باز، خودش با Esc بسته می‌شود؛ مودال باید بماند. if (document.querySelector(OPEN_LAYER_SELECTOR)) return; onClose(); }; document.addEventListener('keydown', onKey); return () => document.removeEventListener('keydown', onKey); }, [enabled, onClose]); const onMouseDown = useCallback((e: ReactMouseEvent) => { pressedOnOverlay.current = e.target === e.currentTarget; }, []); const onClick = useCallback((e: ReactMouseEvent) => { const started = pressedOnOverlay.current; pressedOnOverlay.current = false; if (started && e.target === e.currentTarget) onClose(); }, [onClose]); return { onMouseDown, onClick }; }