Клавиатурный доступ к D&D

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


Основные концепции

В D&D с клавиатуры важно разделять три состояния:

  1. Фокус на элементе – элемент должен быть доступен через клавишу Tab и иметь видимое обозначение фокуса.
  2. Выбор (pick up) элемента – сигнал пользователю о том, что элемент готов к перемещению.
  3. Перемещение и сброс (drop) – навигация с помощью клавиш и подтверждение позиции элемента.

ally.js предоставляет функции для управления фокусом и проверкой доступности элементов, что упрощает реализацию этих состояний.


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

Базовая функция для установки фокуса:

import * as ally from 'ally.js';

const element = document.querySelector('#draggable');
ally.focus.element(element);

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

  • ally.focus.element() гарантирует, что элемент станет видимым для экранного читателя.
  • Можно использовать ally.query.tabsequence() для получения списка всех элементов, доступных через Tab. Это позволяет динамически определить, куда элемент может быть перемещён при клавиатурной навигации.
const tabbable = ally.query.tabsequence({ context: document });
  • tabbable возвращает массив объектов с информацией о каждом элементе, включая координаты, видимость и доступность.

Инициация drag-состояния

Для активации D&D с клавиатуры необходимо добавить обработчик событий клавиатуры. Обычно используется клавиша Enter или Space для «поднятия» элемента:

element.addEventListener('keydown', (event) => {
  if (event.key === 'Enter' || event.key === ' ') {
    element.setAttribute('aria-grabbed', 'true');
    element.classList.add('dragging');
    event.preventDefault();
  }
});

Важно:

  • Атрибут aria-grabbed сигнализирует о том, что элемент выбран для перемещения.
  • Класс dragging позволяет визуально выделить элемент.
  • event.preventDefault() предотвращает стандартное поведение клавиш, чтобы не нарушить логику D&D.

Навигация по возможным целям

После поднятия элемента пользователь должен иметь возможность перемещать его к другим позициям с клавиатуры. ally.js помогает определить элементы, куда можно «перетащить» объект.

const dropTargets = ally.query.tabsequence({ context: container, includeOnlyTabbable: true });

Для перемещения можно использовать стрелочные клавиши:

document.addEventListener('keydown', (event) => {
  if (element.classList.contains('dragging')) {
    let currentIndex = dropTargets.indexOf(document.activeElement);
    if (event.key === 'ArrowDown') {
      let nextIndex = (currentIndex + 1) % dropTargets.length;
      ally.focus.element(dropTargets[nextIndex].node);
      event.preventDefault();
    }
    if (event.key === 'ArrowUp') {
      let prevIndex = (currentIndex - 1 + dropTargets.length) % dropTargets.length;
      ally.focus.element(dropTargets[prevIndex].node);
      event.preventDefault();
    }
  }
});

Особенности реализации:

  • Навигация циклична, чтобы Tab и стрелки не блокировались на границах.
  • dropTargets[index].node — реальный DOM-элемент для фокуса.
  • Можно расширять логику, добавляя поддержку горизонтальных перемещений и сеток.

Завершение drag-состояния

Для сброса элемента на выбранную позицию применяются те же клавиши, что и для выбора (обычно Enter или Space):

document.addEventListener('keydown', (event) => {
  if (element.classList.contains('dragging') && (event.key === 'Enter' || event.key === ' ')) {
    element.setAttribute('aria-grabbed', 'false');
    element.classList.remove('dragging');
    event.preventDefault();
  }
});

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


aria-атрибуты для доступности

Для полноценной доступности следует использовать:

  • aria-grabbed="true|false" — состояние выбранного элемента.
  • aria-dropeffect="move" — тип операции на цели.
  • role="list" и role="listitem" для контейнеров и элементов.
  • Видимые подсказки фокуса с помощью CSS:
.dragging {
  outline: 2px dashed #007acc;
  background-color: #e6f7ff;
}

Использование ally.js для проверки доступности

Библиотека позволяет убедиться, что после перемещения элемент остаётся доступным:

ally.query.firstTabbable({ context: container });
ally.query.focused({ context: document });

Эти функции помогают:

  • Проверить, что фокус корректно перемещается.
  • Убедиться, что элементы видимы и интерактивны для экранных читателей.
  • Автоматизировать тестирование D&D-интерфейсов на соответствие стандартам WCAG.

Интеграция с существующими D&D-библиотеками

ally.js можно комбинировать с Dragula, SortableJS или нативными drag-and-drop API:

  • ally.js управляет фокусом и клавиатурной навигацией.
  • Существующая библиотека управляет визуальным перетаскиванием и изменением DOM.
  • Такой подход обеспечивает доступность без переписывания всей логики D&D.

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

  • Всегда предоставлять очевидные визуальные индикаторы фокуса и drag-состояния.
  • Использовать ally.js для динамического определения tabbable-элементов и целей перемещения.
  • Проверять доступность с экранными читателями и клавиатурной навигацией.
  • Разделять логику D&D на состояния: idle, dragging, dropTargetFocused.