Focus management

Muuri — это библиотека для создания интерактивных сеток с поддержкой drag-and-drop, фильтрации и сортировки. Одним из важных аспектов работы с сеткой является управление фокусом элементов, что напрямую влияет на доступность, навигацию с клавиатуры и взаимодействие с пользователем. Muuri предоставляет гибкие механизмы для контроля фокуса и взаимодействия с элементами сетки.


Фокусируемые элементы

В Muuri каждый элемент сетки (item) может быть фокусируемым благодаря стандартным HTML-атрибутам. Для того чтобы элемент мог получать фокус, необходимо:

<div class="item" tabindex="0">Элемент 1</div>
<div class="item" tabindex="0">Элемент 2</div>
  • tabindex="0" — делает элемент доступным для фокусировки через клавишу Tab.
  • tabindex="-1" — позволяет программно установить фокус на элемент, но исключает его из последовательности табуляции.

Muuri позволяет получать ссылку на объект элемента через метод getItem или напрямую через массив grid.getItems(), что важно для управления фокусом программно.

const item = grid.getItems()[0];
item.getElement().focus();

Программное управление фокусом

Muuri предоставляет возможность отслеживать события фокуса и управлять им через API элементов. Основные методы:

  • item.getElement() — возвращает DOM-элемент.
  • item.show() / item.hide() — отображение и скрытие элемента с автоматическим сохранением позиции.
  • item.getId() — идентификатор элемента для логики фокусировки.

Пример установки фокуса на первый видимый элемент:

const visibleItems = grid.getItems().filter(item => !item.isHidden());
if (visibleItems.length > 0) {
  visibleItems[0].getElement().focus();
}

События фокуса и взаимодействие с клавиатурой

Для создания удобной навигации важно отслеживать события focus и blur:

grid.getItems().forEach(item => {
  const el = item.getElement();
  el.addEventListener('focus', () => {
    el.classList.add('focused');
  });
  el.addEventListener('blur', () => {
    el.classList.remove('focused');
  });
});

Muuri не ограничивает использование стандартных событий клавиатуры. С помощью keydown можно реализовать навигацию между элементами:

document.addEventListener('keydown', (e) => {
  if (e.key === 'ArrowRight') {
    const focused = document.activeElement;
    const currentItemIndex = grid.getItems().findIndex(item => item.getElement() === focused);
    const nextItem = grid.getItems()[currentItemIndex + 1];
    if (nextItem) nextItem.getElement().focus();
  }
});

Управление фокусом при фильтрации и сортировке

При динамическом изменении сетки важно контролировать фокус, чтобы пользователь не потерял ориентацию:

grid.filter(item => item.getElement().textContent.includes('активный'));
const visibleItems = grid.getItems().filter(item => !item.isHidden());
if (visibleItems.length > 0) {
  visibleItems[0].getElement().focus();
}
  • После применения фильтрации или сортировки рекомендуется устанавливать фокус на первый видимый элемент или на элемент, с которым пользователь уже взаимодействовал.
  • Muuri автоматически перестраивает DOM, поэтому прямые ссылки на элементы могут потерять актуальность. Использование grid.getItems() гарантирует корректное управление фокусом.

Автоматический фокус при drag-and-drop

Muuri позволяет реализовать drag-and-drop, и в этом случае важно корректно восстанавливать фокус:

grid.on('dragEnd', item => {
  item.getElement().focus();
});
  • Это обеспечивает плавный пользовательский опыт и поддерживает доступность.
  • В комбинации с tabindex и обработкой клавиш можно создать полностью клавиатурно-доступную сетку.

Кастомные стратегии фокусировки

Можно реализовать собственную логику фокусировки, например, перемещать фокус в зависимости от положения элемента:

function focusNearestItem(currentItem, direction) {
  const allItems = grid.getItems().filter(i => !i.isHidden());
  const currentRect = currentItem.getElement().getBoundingClientRect();
  let nearestItem = null;
  let minDistance = Infinity;

  allItems.forEach(item => {
    if (item === currentItem) return;
    const rect = item.getElement().getBoundingClientRect();
    const distance = Math.hypot(rect.left - currentRect.left, rect.top - currentRect.top);
    if (distance < minDistance) {
      minDistance = distance;
      nearestItem = item;
    }
  });

  if (nearestItem) nearestItem.getElement().focus();
}
  • Такой подход позволяет имитировать сеточную навигацию, где стрелки управляют движением фокуса по рядам и колонкам.
  • Важно учитывать видимость элементов, чтобы фокус не попадал на скрытые или фильтрованные элементы.

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

  • Все элементы сетки должны иметь tabindex="0" или быть естественно фокусируемыми (button, a, input).
  • При динамическом обновлении сетки использовать grid.getItems() для получения актуального списка элементов.
  • Обрабатывать события focus и blur для визуальной подсветки активного элемента.
  • При drag-and-drop восстанавливать фокус на перетаскиваемый элемент для сохранения удобства клавиатурной навигации.
  • Фокусировать элементы после фильтрации и сортировки для поддержания контекста пользователя.

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