From 9aa3d842d20a37ea200f87b2fa36b2baf7877c2a Mon Sep 17 00:00:00 2001 From: "Zimin A.N." Date: Wed, 6 May 2026 22:12:17 +0300 Subject: [PATCH] =?UTF-8?q?fix(geo):=20AOI=20map=20blank=20=E2=80=94=20RAF?= =?UTF-8?q?=20poll=20=D0=B4=D0=BE=20=D0=BF=D0=BE=D1=8F=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20div=20+=20shrink-0=20=D0=B7=D0=B0=D1=89?= =?UTF-8?q?=D0=B8=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DevTools на пользовательском скрине показал: - Map div СУЩЕСТВУЕТ в DOM (display:block, height:520 inline) - Реальный размер 974×254 — flex-shrink порезал до ~50% запрошенного потому что parent (Modal body) overflow-hidden + max-h:calc(100vh-2rem) на узком viewport (497px) не вмещал 520+ контента. - Класса 'leaflet-container' на div НЕТ — L.map() так и не вызвался. Корень: Modal от @nstart/ui рендерит children только после useAnimatedPresence фликает isMounted=true (на след. рендер после isOpen=true). Мой useEffect фаерится РАНЬШЕ — div ещё не в DOM, mapDivRef.current=null. Старый guard 'if (!div) return' выходил БЕЗ schedule RAF — больше никогда не пробовал. Фикс в tryInit: schedule RAF и для null div, и для 0-size. Cap 100 кадров (~1.6s). Это finally доводит до L.map() когда div появится и получит размер. Plus: shrink-0 + min-height: 520 на map div чтобы flex-shrink не ужимал до 254px на узких viewport. Visual quality 520px stable. --- .../src/components/geo/AoiPickerDialog.tsx | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/ordinis-admin-ui/src/components/geo/AoiPickerDialog.tsx b/ordinis-admin-ui/src/components/geo/AoiPickerDialog.tsx index 7548822..8c06e46 100644 --- a/ordinis-admin-ui/src/components/geo/AoiPickerDialog.tsx +++ b/ordinis-admin-ui/src/components/geo/AoiPickerDialog.tsx @@ -62,14 +62,21 @@ export const AoiPickerDialog = ({ isOpen, onClose, onApply, initial }: Props) => const tryInit = () => { if (cancelled) return + if (mapRef.current) return // уже init'нут const div = mapDivRef.current - if (!div || mapRef.current) return - // Defer пока container не имеет реальный размер — RAF до 100 кадров - // (~1.6s @ 60fps), потом сдаёмся и init'им как есть (fallback). - if ((div.clientWidth === 0 || div.clientHeight === 0) && rafId < 100) { - rafId += 1 - requestAnimationFrame(tryInit) + // Modal от @nstart/ui рендерит children только после useAnimatedPresence + // флипает isMounted=true (на след. рендер после isOpen=true). Наш useEffect + // фаерится РАНЬШЕ — div ещё не в DOM. Plus modal transform animation + // изначально даёт 0×0 размер. Poll'им RAF до тех пор пока: + // - div существует (ref populated на render когда modal mounted'нул его) + // - clientWidth и clientHeight > 0 (после layout & transform settled) + // Cap на 100 кадров (~1.6s) — fallback если что-то совсем странное. + if (!div || div.clientWidth === 0 || div.clientHeight === 0) { + if (rafId < 100) { + rafId += 1 + requestAnimationFrame(tryInit) + } return } @@ -180,12 +187,12 @@ export const AoiPickerDialog = ({ isOpen, onClose, onApply, initial }: Props) =>