Фокус менеджмент

В библиотеке Shopify Draggable управление фокусом является важным аспектом для обеспечения доступности и удобства взаимодействия с перетаскиваемыми элементами. Фокус-менеджмент отвечает за то, чтобы элементы, которые могут быть перемещены или изменены, правильно реагировали на клавиатурные события и оставались доступными для пользователей с особыми потребностями.

Состояния фокуса

Каждый элемент в Draggable может находиться в одном из нескольких состояний:

  1. Неактивный элемент – элемент существует на странице, но не выбран и не перетаскивается.
  2. Выбранный элемент (selected) – элемент активирован для перетаскивания, фокус может быть установлен через клавиатуру или мышь.
  3. Перетаскиваемый элемент (dragging) – элемент активно перемещается пользователем, его положение обновляется динамически.

Для корректной работы важно отслеживать эти состояния через события Draggable:

const draggable = new Draggable(document.querySelectorAll('.draggable'), {
  draggable: '.item'
});

draggable.on('drag:start', (event) => {
  event.source.classList.add('dragging');
});

draggable.on('drag:stop', (event) => {
  event.source.classList.remove('dragging');
});

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

Draggable предоставляет возможность навигации и управления элементами с помощью клавиатуры. Элементы должны быть фокусируемыми, обычно это достигается с помощью атрибута tabindex="0":

<div class="item" tabindex="0">Элемент 1</div>
<div class="item" tabindex="0">Элемент 2</div>

События drag:start и drag:stop могут быть расширены обработчиками клавиатурных действий:

draggable.on('drag:move', (event) => {
  if(event.originalEvent.key === 'ArrowUp') {
    // Логика перемещения элемента вверх
  } else if(event.originalEvent.key === 'ArrowDown') {
    // Логика перемещения элемента вниз
  }
});

Фокус при динамическом изменении DOM

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

function addItem() {
  const newItem = document.createElement('div');
  newItem.classList.add('item');
  newItem.setAttribute('tabindex', '0');
  newItem.textContent = 'Новый элемент';
  container.appendChild(newItem);
  newItem.focus();
}

Удаление элемента также должно корректно смещать фокус:

function removeItem(item) {
  const nextFocus = item.nextElementSibling || item.previousElementSibling;
  item.remove();
  if(nextFocus) nextFocus.focus();
}

Интеграция с событиями Draggable

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

  • drag:over – активируется, когда элемент пересекает границу другой области; можно менять фокус на текущий контейнер.
  • sortable:sorted – применяется при завершении сортировки; можно установить фокус на перемещённый элемент.
  • droppable:over – используется для элементов, которые можно сбросить; фокус может автоматически переходить на приёмный контейнер.

Пример:

draggable.on('sortable:sorted', (event) => {
  event.newContainer.focus();
});

Взаимодействие с ARIA-атрибутами

Для доступности необходимо правильно использовать ARIA-атрибуты:

  • aria-grabbed="true" – указывает, что элемент выбран для перетаскивания.
  • aria-dropeffect="move" – обозначает возможное действие при сбросе элемента.
  • role="listitem" и role="list" – обеспечивают семантическое соответствие элементам списка.
draggable.on('drag:start', (event) => {
  event.source.setAttribute('aria-grabbed', 'true');
});

draggable.on('drag:stop', (event) => {
  event.source.setAttribute('aria-grabbed', 'false');
});

Рекомендации по UX-фокус-менеджмента

  • При перемещении элементов визуальный фокус должен совпадать с логическим фокусом.
  • Использовать плавные анимации, чтобы пользователь видел перемещение и понимал текущую позицию элемента.
  • Для больших списков добавить клавиатурную навигацию с ограничением на видимые элементы.
  • При динамическом изменении DOM всегда проверять, что активный элемент остаётся доступным для фокуса.

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

const draggable = new Draggable(document.querySelectorAll('.draggable'), {
  draggable: '.item',
  delay: 100,
  mirror: {
    constrainDimensions: true
  }
});

draggable.on('drag:start', (event) => {
  event.source.classList.add('dragging');
  event.source.setAttribute('aria-grabbed', 'true');
});

draggable.on('drag:stop', (event) => {
  event.source.classList.remove('dragging');
  event.source.setAttribute('aria-grabbed', 'false');
  event.source.focus();
});

draggable.on('sortable:sorted', (event) => {
  event.newContainer.focus();
});

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

Фокус-менеджмент в Shopify Draggable требует синхронизации состояния элементов, обработки клавиатурных действий и корректного применения ARIA-атрибутов, что делает интерфейс доступным и интуитивно понятным.