/** * Workaround для бага позиционирования Vuetify connected-location-strategy. * * Когда активатор `v-select`/`v-autocomplete` находится внутри * `position: fixed`-контейнера (кастомный дровер, диалог), Vuetify включает * ветку `activatorFixed` (`isFixedPosition()` → true). Её `getIntrinsicSize()` * вычитает `el.style.left` из измеренной геометрии оверлея; на переходном * кадре, когда контент ещё отрисован в нулевой позиции, а инлайновый * `style.left` уже не нулевой, `contentBox.x` становится отрицательным и * стратегия аккумулирует смещение — меню уезжает на кратное X активатора * (за край экрана). * * Обычно гонку сглаживают пересчёты, размазанные по анимации открытия. Под * `prefers-reduced-motion: reduce` (умолчание Windows Server) анимации нет — * один пересчёт на «плохом» кадре остаётся финальным. * * Фикс: дождаться, пока контент оверлея отрисован и геометрически стабилен, * затем один раз послать `resize` — Vuetify пересчитает позицию по уже * устоявшейся геометрии и поставит меню корректно. Безопасно при motion ON * (пересчёт по стабильной геометрии идемпотентен) и для не-fixed контейнеров. * * Привязывать к `@update:menu` нужного `v-autocomplete`/`v-select`. */ export function repositionMenuAfterOpen(open: boolean): void { if (!open || typeof window === 'undefined') return; let prevLeft = Number.NaN; let stableFrames = 0; let totalFrames = 0; const tick = (): void => { // Последний открытый overlay-menu (на случай вложенных оверлеев). const menus = document.querySelectorAll('.v-overlay.v-menu .v-overlay__content'); const el = menus[menus.length - 1]; if (el && el.getBoundingClientRect().width > 0) { const left = Math.round(el.getBoundingClientRect().left); stableFrames = left === prevLeft ? stableFrames + 1 : 0; prevLeft = left; // 3 кадра без движения = геометрия устоялась → один чистый пересчёт. if (stableFrames >= 3) { window.dispatchEvent(new Event('resize')); return; } } // Предохранитель ~1.5 c: не зацикливаться, если оверлей не появился. if (++totalFrames < 90) requestAnimationFrame(tick); }; requestAnimationFrame(tick); }