diff --git a/ordinis-admin-ui/src/components/form/SchemaDrivenForm.tsx b/ordinis-admin-ui/src/components/form/SchemaDrivenForm.tsx index 98abaf0..a1da121 100644 --- a/ordinis-admin-ui/src/components/form/SchemaDrivenForm.tsx +++ b/ordinis-admin-ui/src/components/form/SchemaDrivenForm.tsx @@ -3,6 +3,7 @@ import { useForm, Controller, type SubmitHandler } from 'react-hook-form' import { useTranslation } from 'react-i18next' import Ajv, { type ErrorObject } from 'ajv' import addFormats from 'ajv-formats' +import { CaretDownIcon, CircleNotchIcon } from '@phosphor-icons/react' import { Alert, Badge, @@ -747,13 +748,10 @@ const ReferenceSelectField = ({ // scroll и type-ahead search работают OOTB. SingleSelect popup // через createPortal с position:fixed не делает auto-flip и уходит // за нижний край когда триггер близко к низу modal'а. - // Caret chevron как inline SVG в data-uri — match'ит SingleSelect - // CaretDown иконку (color carbon/40, weight 'regular', size ~16). - // appearance-none убирает OS default стрелку, иначе на macOS Safari - // рендерится двойной chevron + native blue border. - const caretSvg = - "url(\"data:image/svg+xml;utf8,\")" - + // + // Right adornment: spinner если идёт fetch options, иначе caret. + // pointer-events-none — иконка не блокирует click по нативному + // select (он принимает все клики, включая поверх стрелки). return ( (
{label} - +
+ + +
{hint} {errorMsg}
diff --git a/ordinis-admin-ui/src/components/geo/AoiPickerDialog.tsx b/ordinis-admin-ui/src/components/geo/AoiPickerDialog.tsx index d78a2d7..d20fb2f 100644 --- a/ordinis-admin-ui/src/components/geo/AoiPickerDialog.tsx +++ b/ordinis-admin-ui/src/components/geo/AoiPickerDialog.tsx @@ -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) =>