import React, { useEffect, useMemo, useRef, useState } from 'react'; import type { MaterialsZoomTool, NpcsZoomTool } from '../../../shared/types'; import styles from '../materials/MaterialOverlay.module.css'; import { SceneOverlayViewContext } from './SceneOverlayViewContext'; export type SceneOverlayCloseAction = { key: string; label: string; onClose: () => void; }; type SceneOverlayHostProps = { /** Есть ли что показывать (материал и/или NPC). */ active: boolean; zoomTool?: MaterialsZoomTool | NpcsZoomTool; onZoomAt?: (nx: number, ny: number, target: EventTarget | null) => void; closes?: readonly SceneOverlayCloseAction[]; children: React.ReactNode; }; /** * Общий слой подложки для Materials + NPCs: один root и один `.dim`. * Кадры остаются в дочерних оверлеях (`embedded`). */ export function SceneOverlayHost({ active, zoomTool = null, onZoomAt, closes = [], children, }: SceneOverlayHostProps) { const rootRef = useRef(null); const [view, setView] = useState({ w: 1, h: 1 }); useEffect(() => { const el = rootRef.current; if (!el) return; let raf = 0; const sync = () => { if (raf !== 0) return; raf = window.requestAnimationFrame(() => { raf = 0; const w = Math.max(1, el.clientWidth); const h = Math.max(1, el.clientHeight); setView((prev) => (prev.w === w && prev.h === h ? prev : { w, h })); }); }; sync(); const ro = new ResizeObserver(sync); ro.observe(el); return () => { ro.disconnect(); if (raf !== 0) window.cancelAnimationFrame(raf); }; }, [active]); const ctx = useMemo(() => ({ rootRef, view }), [view]); if (!active) return null; const captureZoom = Boolean(zoomTool && onZoomAt); const zoomCursor = zoomTool === 'zoomIn' ? styles.cursorZoomIn : zoomTool === 'zoomOut' ? styles.cursorZoomOut : ''; const toNorm = (clientX: number, clientY: number) => { const root = rootRef.current; if (!root) return { nx: 0.5, ny: 0.5 }; const r = root.getBoundingClientRect(); return { nx: (clientX - r.left) / Math.max(1, r.width), ny: (clientY - r.top) / Math.max(1, r.height), }; }; return (
{ if (!captureZoom || !onZoomAt) return; e.stopPropagation(); const { nx, ny } = toNorm(e.clientX, e.clientY); onZoomAt(nx, ny, e.target); }} >
{children} {closes.length > 0 ? (
{closes.map((c) => ( ))}
) : null}
); }