SkateJS — это библиотека для создания веб-компонентов, основанная на стандартах Web Components. Она предоставляет удобные средства для декларативного определения компонентов, их состояния и событий. Одним из ключевых аспектов интерактивных компонентов является клавиатурная навигация, которая обеспечивает доступность (accessibility) и улучшает взаимодействие пользователя с приложением без мыши.
SkateJS позволяет управлять фокусом элементов через стандартные HTML-атрибуты и события. Основные моменты:
tabindex — определяет порядок
обхода элементов при навигации с помощью клавиши Tab.
tabindex="0" делает элемент фокусируемым в стандартном
порядке.tabindex="-1" делает элемент программно фокусируемым,
но исключает его из последовательности Tab.autofocus — автоматически
устанавливает фокус на элемент при загрузке страницы или монтировании
компонента.
В SkateJS свойства можно задать как через HTML-разметку компонента, так и через реактивное состояние компонента.
import { define, props, Component } from 'skatejs';
class MyButton extends Component {
static props = {
disabled: props.boolean
}
render() {
return (
<button tabindex="0" disabled={this.disabled}>
Нажми меня
</button>
);
}
}
define('my-button', MyButton);
Для полноценной клавиатурной навигации важна обработка событий
keydown,
keyup и
keypress. SkateJS предоставляет удобный
способ привязки обработчиков через метод connectedCallback
или декларативно в render с использованием JSX.
Пример обработки стрелок для перемещения между элементами списка:
class MenuItem extends Component {
handleKeyDown = (event) => {
switch (event.key) {
case 'ArrowDown':
this.focusNext();
event.preventDefault();
break;
case 'ArrowUp':
this.focusPrevious();
event.preventDefault();
break;
case 'Enter':
this.activate();
break;
}
}
focusNext() {
const next = this.nextElementSibling;
if (next) next.focus();
}
focusPrevious() {
const prev = this.previousElementSibling;
if (prev) prev.focus();
}
activate() {
this.dispatchEvent(new CustomEvent('item-activated', { bubbles: true }));
}
render() {
return (
<div tabindex="0" onKeyD own={this.handleKeyDown}>
{this.textContent}
</div>
);
}
}
define('menu-item', MenuItem);
Для поддержки доступности необходимо использовать атрибуты ARIA, которые сообщают экранным читателям и другим вспомогательным технологиям о роли и состоянии элементов.
role="menu" и
role="menuitem" — для создания
навигационных меню.aria-selected — указывает, выбран ли
элемент в списке.aria-disabled — обозначает, что
элемент недоступен для взаимодействия.Пример списка с поддержкой ARIA:
class Menu extends Component {
render() {
return (
<div role="menu">
<menu-item role="menuitem">Пункт 1</menu-item>
<menu-item role="menuitem">Пункт 2</menu-item>
<menu-item role="menuitem">Пункт 3</menu-item>
</div>
);
}
}
define('my-menu', Menu);
SkateJS позволяет реализовать циклическую навигацию внутри компонента: при достижении последнего элемента стрелкой вниз фокус переходит к первому, и наоборот. Это удобно для выпадающих меню и панелей инструментов.
focusNext() {
const items = Array.from(this.parentNode.querySelectorAll('[role="menuitem"]'));
const index = items.indexOf(this);
const nextIndex = (index + 1) % items.length;
items[nextIndex].focus();
}
focusPrevious() {
const items = Array.from(this.parentNode.querySelectorAll('[role="menuitem"]'));
const index = items.indexOf(this);
const prevIndex = (index - 1 + items.length) % items.length;
items[prevIndex].focus();
}
Клавиатурная навигация тесно связана с реактивным состоянием компонента. SkateJS позволяет хранить активный элемент или индекс в свойствах и обновлять DOM при изменении состояния.
class TabPanel extends Component {
static props = {
activeIndex: props.number
}
handleKeyDown = (event) => {
if (event.key === 'ArrowRight') {
this.activeIndex = (this.activeIndex + 1) % this.children.length;
} else if (event.key === 'ArrowLeft') {
this.activeIndex = (this.activeIndex - 1 + this.children.length) % this.children.length;
}
}
render() {
return (
<div onKeyD own={this.handleKeyDown}>
{Array.from(this.children).map((child, i) =>
<div tabindex={i === this.activeIndex ? "0" : "-1"}>{child.textContent}</div>
)}
</div>
);
}
}
define('tab-panel', TabPanel);
tabindex только там, где
это необходимо. Избыточные tabindex могут нарушить естественную
последовательность.Эффективная клавиатурная навигация в SkateJS обеспечивает доступность и плавное взаимодействие пользователей с компонентами, интегрируя события, фокус и ARIA в единый механизм управления интерфейсом.