Newer
Older
gnexus-tasks / frontend / src / dropdownClamp.ts
// Фикс дропдаунов кита на мобильных: GnDropdown позиционирует меню от левого
// края триггера (position:absolute; left:0), и у триггеров у правого края
// экрана меню вылезает наружу. Кит не даёт ручек позиционирования — после
// открытия выравниваем меню (и по горизонтали, и по вертикали) в пределах
// вьюпорта, не трогая стили кита.
export function installDropdownClamp(): void {
  const clamp = () => {
    for (const dropdown of Array.from(document.querySelectorAll<HTMLElement>('.dropdown.is-open'))) {
      const menu = dropdown.querySelector<HTMLElement>('.dropdown-menu')
      if (!menu) continue
      const rect = dropdown.getBoundingClientRect()
      const vw = document.documentElement.clientWidth
      const vh = window.innerHeight

      // Горизонталь: если меню не помещается справа от триггера — открыть влево
      menu.style.left = 'auto'
      menu.style.right = 'auto'
      if (rect.left + menu.offsetWidth > vw - 8) menu.style.right = '0'
      else menu.style.left = '0'

      // Вертикаль: если не помещается под триггером, но есть место сверху — открыть вверх
      menu.style.top = ''
      menu.style.bottom = ''
      if (rect.bottom + 8 + menu.offsetHeight > vh && rect.top - menu.offsetHeight > 0) {
        menu.style.top = 'auto'
        menu.style.bottom = 'calc(100% + 8px)'
      }
    }
  }
  // Открытие дропдауна происходит по клику синхронно, DOM обновляется в
  // микротаске Vue — macrotask setTimeout отрабатывает уже после рендера
  document.addEventListener('click', () => setTimeout(clamp, 0), true)
}