'use client'; import { type ReactNode, useEffect, useRef, useSyncExternalStore } from 'react'; import { useFocusTrap } from '../hooks/useFocusTrap'; // Module-level modal emitter (mirrors the context-menu host pattern). let current: ReactNode | null = null; const listeners = new Set<() => void>(); const emit = () => listeners.forEach((l) => l()); export function openModal(node: ReactNode) { current = node; emit(); } export function closeModal() { current = null; emit(); } /** Whether a modal is currently mounted. Read by global Escape handling so the * modal (which owns Escape) is never fought over by another handler. */ export function isModalOpen(): boolean { return current !== null; } function subscribe(cb: () => void) { listeners.add(cb); return () => listeners.delete(cb); } export function ModalHost() { const node = useSyncExternalStore( subscribe, () => current, () => null, ); const ref = useRef(null); useFocusTrap(ref, Boolean(node), closeModal); useEffect(() => { if (!node) return; const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') closeModal(); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [node]); if (!node) return null; return (
{ if (e.target === e.currentTarget) closeModal(); }} > {node}
); }