fix(ui): AOI map blank на open + FK select loading spinner
AOI map blank: Pickер открывался с пустым контейнером — без zoom controls и attribution. Leaflet вычисляет tile grid от containerSize в момент L.map() create(); modal animation + flex-1 layout давали 0×0 на первом кадре, тайлы не запрашивались, и map оставался blank. setTimeout(invalidateSize, 100ms) не покрывал случай когда modal ещё анимировал. Решение: ResizeObserver на map div + invalidateSize при каждом resize. Покрывает modal fade-in, dev tools toggle, window resize. Плюс requestAnimationFrame invalidate для initial flush. Inline style.minHeight=420/height=60vh — защита если flex не распределил высоту. FK select loading state: Раньше при isLoading select disabled + показывал '…'. Теперь: - chevron справа заменяется на animated CircleNotchIcon (animate-spin) - aria-busy=true для screen readers - цвет spinner ultramarain — маркирует активность Refactor: chevron перенесён из background-image в абсолютно позиционированный <div pointer-events-none>. Pointer-events-none не блокирует click по нативному select.
This commit is contained in:
@@ -115,11 +115,25 @@ export const AoiPickerDialog = ({ isOpen, onClose, onApply, initial }: Props) =>
|
||||
|
||||
mapRef.current = map
|
||||
|
||||
// Forces map to recompute size: modal animations cause containerSize=0
|
||||
// первой кадр. После 100ms layout settled.
|
||||
const t = setTimeout(() => map.invalidateSize(), 100)
|
||||
// Modal animation + flex layout = mapDiv может стартовать с 0×0 на
|
||||
// первом кадре. Leaflet вычисляет tile grid от containerSize в момент
|
||||
// create() — если 0, тайлы не запрашиваются и карта остаётся пустой.
|
||||
//
|
||||
// Решение: ResizeObserver на контейнер, invalidateSize при каждом
|
||||
// resize. Это надёжно покрывает modal fade-in, dev tools toggle,
|
||||
// window resize. Дополнительно invalidateSize на следующем
|
||||
// animation frame — для случая когда RO не fire'ит (initial size
|
||||
// уже окончательный).
|
||||
const ro = new ResizeObserver(() => {
|
||||
if (mapRef.current) mapRef.current.invalidateSize()
|
||||
})
|
||||
ro.observe(div)
|
||||
const raf = requestAnimationFrame(() => {
|
||||
if (mapRef.current) mapRef.current.invalidateSize()
|
||||
})
|
||||
return () => {
|
||||
clearTimeout(t)
|
||||
ro.disconnect()
|
||||
cancelAnimationFrame(raf)
|
||||
}
|
||||
}, [isOpen, initial])
|
||||
|
||||
@@ -162,7 +176,11 @@ export const AoiPickerDialog = ({ isOpen, onClose, onApply, initial }: Props) =>
|
||||
|
||||
<div
|
||||
ref={mapDivRef}
|
||||
className="flex-1 min-h-[420px] rounded-sm border border-regolith"
|
||||
className="flex-1 rounded-sm border border-regolith"
|
||||
// Inline height — Leaflet требует non-zero containerSize в момент
|
||||
// L.map() create(). flex-1 не гарантирует высоту до layout pass;
|
||||
// explicit 420px минимум защищает от blank карты.
|
||||
style={{ minHeight: 420, height: '60vh' }}
|
||||
aria-label={t('aoi.dialog.mapAriaLabel')}
|
||||
/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user