Фокус и табуляция

Gridstack.js предоставляет мощные инструменты для управления сетками и виджетами в веб-приложениях, включая обработку фокуса элементов и навигацию с помощью клавиши Tab. Понимание этих механизмов критично для создания доступного и интерактивного интерфейса.


Фокусировка виджетов

В Gridstack.js каждый виджет (grid-item) может быть сфокусирован с помощью стандартных методов DOM:

const widget = document.querySelector('.grid-stack-item');
widget.focus();

Для того чтобы виджет корректно получал фокус, он должен иметь атрибут tabindex:

<div class="grid-stack-item" tabindex="0">
  <div class="grid-stack-item-content">Контент</div>
</div>

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

  • tabindex="0" позволяет элементу участвовать в естественной последовательности табуляции.
  • tabindex="-1" делает элемент доступным для программной фокусировки, но исключает из стандартного порядка Tab.
  • Управление фокусом особенно важно для динамически добавляемых или удаляемых виджетов.

Навигация с помощью Tab

Gridstack.js не блокирует стандартную клавиатурную навигацию, однако при сложной сетке могут возникать нестандартные переходы между виджетами. Для правильной табуляции рекомендуется:

  1. Присвоить каждому виджету уникальный tabindex.
  2. При динамическом изменении сетки пересчитывать последовательность табуляции.
  3. Использовать события focus и blur для реализации визуальных индикаторов фокуса.

Пример обработки событий:

const grid = GridStack.init();
grid.on('added', (event, items) => {
  items.forEach(item => {
    const el = item.el;
    el.setAttribute('tabindex', '0');
    el.addEventListener('focus', () => {
      el.classList.add('focused');
    });
    el.addEventListener('blur', () => {
      el.classList.remove('focused');
    });
  });
});

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

Gridstack.js позволяет перетаскивать и изменять размеры виджетов с помощью drag-and-drop. Важно, чтобы фокус не терялся при интерактивных действиях:

  • Использовать событие dragstart для сохранения текущего элемента с фокусом.
  • После окончания dragstop возвращать фокус на тот же виджет:
grid.on('dragstart', (event, el) => {
  el.dataset.prevFocus = document.activeElement === el ? 'true' : 'false';
});

grid.on('dragstop', (event, el) => {
  if (el.dataset.prevFocus === 'true') {
    el.focus();
    delete el.dataset.prevFocus;
  }
});

Аналогично, при изменении размера через событие resizestop можно обеспечить сохранение фокуса:

grid.on('resizestop', (event, el) => {
  el.focus();
});

Программная табуляция внутри сетки

Для управления навигацией с помощью клавиши Tab можно реализовать собственный порядок перемещения между виджетами:

const widgets = Array.from(document.querySelectorAll('.grid-stack-item'));
widgets.forEach((widget, index) => {
  widget.addEventListener('keydown', (e) => {
    if (e.key === 'Tab') {
      e.preventDefault();
      let nextIndex = e.shiftKey ? index - 1 : index + 1;
      if (nextIndex >= widgets.length) nextIndex = 0;
      if (nextIndex < 0) nextIndex = widgets.length - 1;
      widgets[nextIndex].focus();
    }
  });
});

Особенности:

  • e.shiftKey позволяет обрабатывать обратную навигацию (Shift+Tab).
  • Такой подход гарантирует предсказуемую навигацию даже при динамическом изменении структуры сетки.
  • Можно комбинировать с визуальными подсветками для улучшения UX.

Взаимодействие с формами внутри виджетов

Если виджет содержит интерактивные элементы формы, важно корректно обрабатывать фокус внутри виджета и между виджетами:

const inputs = widget.querySelectorAll('input, select, textarea, button');
inputs.forEach(input => {
  input.addEventListener('keydown', (e) => {
    if (e.key === 'Tab') {
      e.stopPropagation(); // предотвращаем срабатывание глобальной табуляции
    }
  });
});

Такой подход позволяет:

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

Поддержка доступности

Gridstack.js не предоставляет встроенных средств управления доступностью, поэтому важно:

  1. Добавлять aria-* атрибуты для виджетов (role="region", aria-label).
  2. Поддерживать видимые индикаторы фокуса (outline, box-shadow).
  3. Управлять последовательностью табуляции через tabindex и события keydown.

Пример доступного виджета:

<div class="grid-stack-item" tabindex="0" role="region" aria-label="Виджет статистики">
  <div class="grid-stack-item-content">Статистика</div>
</div>

Итоговые рекомендации по фокусу и табуляции

  • Всегда присваивать виджетам tabindex.
  • Сохранять фокус при перетаскивании и изменении размера.
  • Реализовывать предсказуемую табуляцию с помощью событий keydown.
  • Обрабатывать вложенные элементы формы отдельно.
  • Добавлять атрибуты aria-* для поддержки доступности.

Эти практики позволяют создавать интерактивные, доступные и удобные сетки в Gridstack.js, где навигация клавиатурой становится естественной и предсказуемой.