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

Фокус-менеджмент в Pikaday представляет собой ключевой слой взаимодействия между пользователем, полем ввода и всплывающим календарём. Корректная работа с фокусом определяет не только удобство навигации с клавиатуры, но и предсказуемость поведения компонента в сложных интерфейсах, где календарь используется внутри модальных окон, форм с валидацией и динамически создаваемых DOM-структур.

При активации календаря через input-элемент происходит захват текущего состояния фокуса. Основная задача — сохранить ссылку на элемент, который инициировал открытие, чтобы после закрытия календаря вернуть фокус в исходную точку. Эта модель критична для форм, где пользователь последовательно заполняет поля.

Фокус в момент открытия можно описать как двухуровневый:

  • внешний уровень: input, связанный с календарём
  • внутренний уровень: контейнер календаря и элементы навигации по датам

При открытии календаря активным становится внутренний уровень, но логически сохраняется связь с внешним элементом.

Захват и восстановление фокуса

При инициализации экземпляра Pikaday фиксируется reference на input-элемент. Это позволяет реализовать поведение восстановления фокуса после закрытия:

  • при onOpen сохраняется активный элемент документа (document.activeElement)
  • при onClose выполняется возврат фокуса в input
  • при уничтожении экземпляра выполняется очистка ссылок, чтобы избежать “висячих” фокусов

Особое значение имеет ситуация, когда календарь закрывается не программно, а через внешние события (например, клик вне области). В этом случае восстановление фокуса должно учитывать, был ли фокус внутри календаря на момент закрытия.

Фокус внутри календаря

Внутренний контейнер календаря ведёт себя как временная фокусная область. Важно, что он не всегда реализует полноценный focus trap, но должен имитировать его поведение в сценариях с клавиатурной навигацией.

Основные элементы фокуса внутри календаря:

  • кнопки переключения месяцев
  • сетка дней
  • элементы “предыдущий/следующий месяц”
  • быстрый переход к текущей дате (если реализован в кастомной сборке)

При навигации клавишами Tab и Shift+Tab необходимо обеспечить предсказуемый порядок обхода элементов. Типичная ошибка — попадание фокуса в элементы вне календаря, что разрушает контекст взаимодействия.

Клавиатурная навигация и roving tabindex

Сетка дней реализует модель roving tabindex, при которой только одна ячейка даты имеет tabindex="0", остальные — tabindex="-1". Это позволяет управлять фокусом внутри таблицы дат без создания множества таб-остановок.

Перемещение по датам осуществляется стрелками:

  • ArrowLeft — предыдущий день
  • ArrowRight — следующий день
  • ArrowUp — неделя назад
  • ArrowDown — неделя вперёд

При этом фокус не покидает календарную сетку, что критично для сохранения контекста.

Внутренняя логика должна синхронизировать визуально выделенную дату и фактический DOM-фокус. Расхождение этих состояний приводит к проблемам доступности.

Поведение при открытии календаря

При активации календаря через input-элемент фокус обычно должен оставаться в input, но визуальный фокус переходит в календарь. Это создаёт гибридную модель:

  • input сохраняет системный фокус
  • календарь получает “виртуальный” фокус для навигации

Некоторые реализации переводят фокус непосредственно в первый доступный день месяца. Это улучшает клавиатурную навигацию, но может ухудшать UX в формах с быстрым вводом.

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

Закрытие календаря и возврат контекста

Закрытие календаря может происходить по нескольким причинам:

  • выбор даты
  • нажатие ESC
  • клик вне области календаря
  • программное закрытие через API

Во всех сценариях необходимо обеспечить одинаковую модель восстановления:

  1. определить элемент, инициировавший открытие
  2. закрыть календарь и удалить активный overlay
  3. вернуть фокус в input

При выборе даты дополнительно важно не допустить скачка фокуса в DOM после обновления значения input, особенно если используется реактивный фреймворк, который может пересоздавать узлы.

Обработка клавиши Escape

Клавиша ESC играет роль универсального выхода из календарного режима. При её обработке необходимо:

  • остановить дальнейшую обработку события
  • закрыть календарь
  • восстановить фокус в input

При этом важно учитывать вложенные контексты: если календарь используется внутри модального окна, ESC может быть перехвачен модалкой, и порядок обработки событий должен быть согласован.

Потеря фокуса и клик вне области

Клик вне календаря инициирует закрытие через глобальный обработчик событий. В этом сценарии часто возникает проблема “прыгающего фокуса”, если клик был совершен по элементу, который сам может получать фокус.

Корректная стратегия включает:

  • проверку, находится ли target события внутри контейнера календаря
  • предотвращение закрытия при взаимодействии с input
  • отложенное восстановление фокуса после завершения event loop

Интеграция с модальными окнами

При размещении Pikaday внутри модального окна фокус-менеджмент усложняется из-за необходимости двойного ограничения:

  • внешний focus trap модального окна
  • внутренний контур календаря

В таких случаях календарь не должен разрывать внешний focus trap. Это означает:

  • Tab внутри календаря не должен выводить фокус за пределы модалки
  • закрытие календаря не должно возвращать фокус в input, если модалка остаётся открытой и управляет фокусом

Динамическое обновление DOM

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

Чтобы избежать потери контекста:

  • активная дата должна сохраняться логически, а не через DOM-ссылку
  • после рендера необходимо восстанавливать фокус на соответствующем элементе
  • используется сравнение дат, а не элементов DOM

Управление фокусом при disabled-состояниях

Если календарь или отдельные даты находятся в состоянии disabled, фокус не должен попадать на такие элементы. Это требует:

  • исключения disabled-дат из roving tabindex
  • пересчёта фокусируемого элемента при изменении месяца
  • автоматического переноса фокуса на ближайшую доступную дату

Особое внимание требуется при переходе между месяцами, где первый элемент может быть неактивным.

Поведение при множественных экземплярах

В интерфейсах с несколькими полями даты одновременно важно исключить конфликт фокуса между экземплярами Pikaday. Каждый экземпляр должен:

  • хранить собственное состояние активного input
  • не перехватывать глобальные события вне своей области
  • изолировать обработчики клавиатуры

При этом глобальные keydown-события должны маршрутизироваться к активному экземпляру, иначе возможны ситуации, когда стрелки управляют не тем календарём.

Взаимодействие с виртуальными клавиатурами

На мобильных устройствах появление виртуальной клавиатуры изменяет поведение фокуса. При открытом календаре input может оставаться активным, что приводит к одновременному отображению клавиатуры и календаря.

Типичная стратегия управления:

  • при открытии календаря временно blur input
  • при выборе даты восстановить фокус только при необходимости
  • учитывать задержки появления/скрытия клавиатуры

Синхронизация фокуса и ARIA-состояний

Фокус-менеджмент тесно связан с ARIA-атрибутами:

  • aria-selected для активной даты
  • aria-activedescendant для контейнера сетки
  • aria-expanded для input-элемента
  • role="dialog" для контейнера календаря при overlay-режиме

Несоответствие между фокусом и ARIA-состоянием приводит к некорректному восприятию компонента скринридерами.

Edge cases потери фокуса

Сложные сценарии включают:

  • уничтожение экземпляра во время активного фокуса внутри календаря
  • асинхронное обновление input через внешние библиотеки
  • повторное открытие календаря до завершения закрытия предыдущего состояния
  • переключение вкладок браузера во время взаимодействия

Во всех этих случаях необходимо принудительно синхронизировать состояние фокуса с DOM после завершения событийного цикла.

Изоляция фокуса и предотвращение “утечек”

Правильная реализация требует предотвращения ситуации, когда фокус “утекает” за пределы календаря при активной работе с ним. Это достигается:

  • ограничением tab-навигации
  • блокировкой кликов вне контейнера при необходимости
  • контролем document.activeElement на каждом keydown-событии
  • восстановлением фокуса при любых аномалиях

Такая модель делает поведение календаря предсказуемым даже в сложных SPA-архитектурах, где DOM может изменяться без перезагрузки страницы.