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

SkateJS — это легковесная библиотека для создания веб-компонентов, основанных на стандартных возможностях браузера. Одной из важных задач при работе с компонентами является управление фокусом: правильная установка и отслеживание фокуса повышает доступность, удобство взаимодействия и контроль над состояниями элементов интерфейса.

Основы фокуса в веб-компонентах

Фокус в браузере относится к элементу, который готов принимать пользовательский ввод с клавиатуры. В контексте SkateJS фокус можно контролировать на уровне компонента через методы жизненного цикла и свойства, такие как ref, а также через стандартные DOM-методы focus() и blur().

Каждый компонент SkateJS может содержать shadow DOM, что создаёт дополнительный слой изоляции. Для управления фокусом внутри shadow DOM важно понимать концепцию delegated focus: при использовании delegatesFocus: true в shadow root, вызов focus() на host-компоненте передаёт фокус внутрь shadow DOM.

import { define, Component, h } from 'skatejs';

class FocusInput extends Component {
  static get props() {
    return {
      autofocus: Boolean
    };
  }

  connectedCallback() {
    super.connectedCallback();
    if (this.autofocus) {
      this.focusInput();
    }
  }

  focusInput() {
    this.shadowRoot.querySelector('input')?.focus();
  }

  render() {
    return <input type="text" />;
  }
}

define('focus-input', FocusInput);

В этом примере при установке свойства autofocus компонент автоматически устанавливает фокус на <input> внутри shadow DOM.

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

SkateJS позволяет использовать свойства компонента для динамического контроля фокуса. Создание специализированного метода, например focusInput(), обеспечивает удобный интерфейс для внешнего кода.

  • focus() — устанавливает фокус на конкретный элемент.
  • blur() — снимает фокус.
  • hasFocus — можно реализовать через getter, который проверяет document.activeElement внутри shadow DOM.

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

get hasFocus() {
  return this.shadowRoot.activeElement === this.shadowRoot.querySelector('input');
}

Такой подход позволяет реагировать на изменения состояния фокуса и интегрировать компонент с другими элементами интерфейса.

Обработка событий фокуса

SkateJS поддерживает стандартные DOM-события focus и blur, а также их делегированные версии focusin и focusout. Для компонентов с shadow DOM события focusin и focusout удобнее использовать, так как они всплывают.

connectedCallback() {
  super.connectedCallback();
  this.addEventListener('focusin', () => {
    this.setAttribute('focused', '');
  });
  this.addEventListener('focusout', () => {
    this.removeAttribute('focused');
  });
}

Использование атрибута focused позволяет менять стили компонента через CSS без необходимости напрямую манипулировать DOM.

:host([focused]) input {
  border-color: blue;
}

Программная навигация фокуса

Для сложных компонентов важно уметь управлять последовательностью фокуса. SkateJS не накладывает ограничений на использование tabindex, поэтому можно реализовать кастомное поведение:

focusNext() {
  const inputs = Array.from(this.shadowRoot.querySelectorAll('input'));
  const index = inputs.indexOf(this.shadowRoot.activeElement);
  if (index >= 0 && index < inputs.length - 1) {
    inputs[index + 1].focus();
  }
}

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

Доступность и фокус

Для компонентов, которые взаимодействуют с пользователем, необходимо учитывать ARIA-атрибуты. Атрибуты aria-activedescendant, aria-labelledby и role помогают экранным читалкам правильно интерпретировать состояние фокуса.

Пример использования ARIA для кастомного элемента с фокусом:

render() {
  return (
    <div role="textbox" aria-label="Введите текст" tabindex="0">
      <input type="text" />
    </div>
  );
}

Здесь tabindex="0" делает контейнер фокусируемым, а role и aria-label обеспечивают корректное взаимодействие с ассистивными технологиями.

Взаимодействие с внешними событиями

Фокус в SkateJS можно синхронизировать с глобальными событиями, такими как keydown или click. Например, при нажатии на кнопку вне компонента можно автоматически фокусировать внутренний input:

document.querySelector('#activate').addEventListener('click', () => {
  document.querySelector('focus-input').focusInput();
});

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

Итоги по управлению фокусом

  • Shadow DOM требует явного управления фокусом через методы или делегирование.
  • События focusin/focusout предпочтительнее для отслеживания внутри компонентов.
  • Программная навигация и ARIA-атрибуты обеспечивают доступность и удобство.
  • Создание методов типа focusInput() и геттеров состояния фокуса позволяет компонентам быть предсказуемыми и интегрируемыми в интерфейс.

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