useSelectableList для списков

Хук useSelectableList из библиотеки React Aria предназначен для организации доступных, управляемых списков с поддержкой выделения элементов. Он обеспечивает корректное взаимодействие с клавиатурой, мышью и вспомогательными технологиями (screen readers), а также интеграцию с состоянием списка.

Импорт и базовая структура

import { useSelectableList } from '@react-aria/listbox';
import { useListState } from '@react-stately/list';

Для начала работы необходимо создать состояние списка с помощью useListState. Оно управляет коллекцией элементов, выбранными элементами и текущим фокусом. Затем хук useSelectableList связывает это состояние с DOM-элементами.

const state = useListState({ selectionMode: 'single', items: myItems });

const ref = useRef();
const { listProps, selectionManager } = useSelectableList({ selectionMode: 'single' }, state, ref);
  • listProps – объект с атрибутами для контейнера списка (role, aria-*), которые нужно развернуть на корневом элементе.
  • selectionManager – объект, предоставляющий методы для управления выбором элементов и фокусом.

Конфигурация и ключевые параметры

Хук принимает три аргумента:

  1. props – настройки списка:

    • selectionMode'none' | 'single' | 'multiple'. Определяет, может ли пользователь выбирать элементы и сколько.
    • disabledKeys – массив ключей элементов, которые нельзя выбирать.
    • onSelectionChange – коллбэк, вызываемый при изменении выбора.
  2. state – объект состояния списка, создаваемый useListState.

  3. refref на контейнер списка, необходимый для правильного управления фокусом и событиями.

Работа с элементами списка

Каждый элемент списка должен иметь уникальный key. React Aria использует этот ключ для отслеживания выделения и навигации. Элементы можно рендерить с помощью метода state.collection.map:

<ul {...listProps} ref={ref}>
  {state.collection.map((item) => (
    <li
      key={item.key}
      {...item.props}
      aria-selected={state.selectionManager.isSelected(item.key)}
    >
      {item.rendered}
    </li>
  ))}
</ul>
  • aria-selected отражает текущее состояние выбора элемента.
  • item.props содержит атрибуты для обеспечения правильной доступности и взаимодействия.

Управление выделением и фокусом

selectionManager предоставляет методы:

  • isSelected(key) – проверяет, выбран ли элемент.
  • select(key) – выделяет элемент (заменяет текущее выделение в режиме 'single' или добавляет в 'multiple').
  • toggleSelection(key) – переключает состояние элемента в режиме 'multiple'.
  • clearSelection() – снимает выделение со всех элементов.
  • setFocusedKey(key) – устанавливает фокус на конкретный элемент.

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

const handleClick = (key) => {
  selectionManager.toggleSelection(key);
  selectionManager.setFocusedKey(key);
};

Поддержка клавиатурной навигации

useSelectableList автоматически обрабатывает:

  • Стрелки вверх/вниз для перемещения фокуса.
  • Home/End для перехода к первому и последнему элементу.
  • Пробел/Enter для выбора элементов.
  • Модификаторы Shift и Cmd/Ctrl для множественного выбора.

Важно обеспечить правильное применение listProps и item.props, чтобы клавиатурная навигация работала корректно.

Интеграция с кастомными компонентами

Списки не обязательно должны быть <ul>/<li>. Любой контейнер и элементы можно использовать, главное – корректно развернуть listProps и item.props. Пример кастомного компонента:

<div {...listProps} ref={ref} className="custom-list">
  {state.collection.map((item) => (
    <div
      key={item.key}
      {...item.props}
      className={`custom-item ${selectionManager.isSelected(item.key) ? 'selected' : ''}`}
    >
      {item.rendered}
    </div>
  ))}
</div>

Управление состоянием множественного выбора

Для множественного выбора можно использовать selectionMode: 'multiple'. В этом случае selectionManager.toggleSelection(key) позволяет добавлять или удалять элементы из текущего выделения. onSelectionChange получает массив выбранных ключей:

const state = useListState({
  selectionMode: 'multiple',
  items: myItems,
  onSelectionChange: (keys) => console.log(keys),
});

Особенности и рекомендации

  • Все элементы должны иметь уникальные key.
  • Для динамических списков важно правильно обновлять state.collection.
  • Не использовать useSelectableList без состояния useListState, иначе функционал выделения и фокуса не будет работать.
  • Для экранных читалок и полной доступности необходимо правильно применять aria-* атрибуты, предоставляемые хуком.

useSelectableList позволяет создавать списки, полностью совместимые с WAI-ARIA, поддерживающие клавиатуру, мышь и доступные для ассистивных технологий. Комбинируя его с useListState, можно построить гибкие интерфейсы с одиночным или множественным выделением, полностью управляемые и кастомизируемые.