Решение конфликтов фокуса

В интерфейсах веб-приложений правильное управление фокусом играет ключевую роль для доступности и удобства пользователя. React Aria предоставляет набор инструментов для контроля фокуса, предотвращения его потери и решения конфликтов, возникающих при взаимодействии с несколькими компонентами.


Природа конфликтов фокуса

Конфликты фокуса возникают, когда несколько элементов одновременно претендуют на получение фокуса, либо когда фокус неожиданно покидает текущий компонент. Основные причины:

  1. Модальные окна и всплывающие панели – когда открывается модальное окно, фокус должен быть заблокирован внутри него, иначе пользователь может попасть на элементы фонового интерфейса.
  2. Динамические списки и вкладки – при ререндере компонентов фокус может переместиться на другой элемент или исчезнуть вовсе.
  3. Комбинации компонентов с автосфокусом – несколько компонентов, установивших autoFocus, могут конкурировать за фокус.

React Aria предлагает инструменты для точного контроля этих ситуаций, включая hooks для фокуса, состояния управления клавиатурой и контексты блокировки фокуса.


Использование useFocusScope для ограничения фокуса

useFocusScope позволяет создать область, внутри которой фокус не покидает компоненты. Это особенно важно для модальных окон, всплывающих списков и кастомных диалогов.

import { useFocusScope } from "@react-aria/focus";
import { useRef } from "react";

function Modal({ isOpen }) {
  let ref = useRef();
  useFocusScope(ref, { restoreFocus: true });

  if (!isOpen) return null;

  return (
    <div ref={ref} role="dialog" aria-modal="true">
      <button>Закрыть</button>
      <input type="text" placeholder="Введите текст"/>
    </div>
  );
}

Ключевые моменты:

  • restoreFocus: true возвращает фокус на элемент, который был активен до открытия модального окна.
  • Область ref защищает внутренние элементы от случайного ухода фокуса на другие части интерфейса.

Контроль фокуса с useFocusRing и useFocus

useFocus предоставляет низкоуровневый контроль над событиями фокуса и blur. Используется для создания собственных компонентов с управляемым поведением при фокусе.

import { useFocus } from "@react-aria/focus";

function CustomButton(props) {
  let { focusProps, isFocused } = useFocus(props);

  return (
    <button {...focusProps} style={{ border: isFocused ? "2px solid blue" : "none" }}>
      {props.children}
    </button>
  );
}
  • focusProps содержит все необходимые обработчики (onFocus, onBlur) для корректного управления.
  • isFocused позволяет визуализировать состояние фокуса, повышая доступность.

useFocusRing строит поверх useFocus, предоставляя визуальные индикаторы фокуса, автоматически учитывая клавиатурные и мышиные события.


Блокировка фокуса с FocusScope и FocusManager

Для сложных интерфейсов с динамическими элементами полезно использовать контекст фокуса, предоставляемый React Aria.

import { FocusScope } from "@react-aria/focus";

function Dialog({ isOpen }) {
  return (
    <FocusScope contain restoreFocus autoFocus>
      <div role="dialog">
        <input placeholder="Имя"/>
        <button>Сохранить</button>
      </div>
    </FocusScope>
  );
}
  • contain предотвращает уход фокуса за пределы области.
  • autoFocus автоматически ставит фокус на первый интерактивный элемент внутри.
  • restoreFocus возвращает фокус на элемент, который был активен до открытия области.

Для динамических списков и вкладок FocusManager позволяет программно управлять порядком фокуса и корректно обрабатывать удаление или добавление элементов.


Работа с клавиатурой и предотвращение конфликтов

Конфликты часто возникают при навигации клавишами Tab и Shift+Tab. React Aria обеспечивает корректное поведение через:

  • useTabList и useTabPanel – автоматически управляют фокусом внутри вкладок.
  • useGrid и useList – обеспечивают навигацию стрелками и управление фокусом на динамических элементах.
  • focusManager.focusNext() и focusManager.focusPrevious() – программное управление фокусом для сложных кейсов.
import { focusManager } from "@react-aria/focus";

function handleKeyDown(event) {
  if (event.key === "ArrowDown") {
    focusManager.focusNext();
  } else if (event.key === "ArrowUp") {
    focusManager.focusPrevious();
  }
}

Такой подход предотвращает потерю фокуса при ререндере элементов и обеспечивает предсказуемое поведение.


Практические рекомендации

  1. Всегда использовать FocusScope для модальных окон – это предотвращает утечку фокуса на фоновый интерфейс.
  2. Не полагаться на autoFocus в нескольких компонентах одновременно – конфликт будет непредсказуем.
  3. Использовать focusManager для динамических списков и таблиц – гарантирует корректную навигацию.
  4. Добавлять визуальные индикаторы через useFocusRing – повышает доступность интерфейса для пользователей клавиатуры.

Эти механизмы позволяют создавать интерфейсы с корректным управлением фокусом, предотвращая конфликты и обеспечивая плавное, предсказуемое взаимодействие пользователя с любым количеством динамических компонентов.