@lp-labs/accessible-interactions
v0.1.0
Published
Framework-neutral roving focus state and explicitly scoped keyboard adapters.
Readme
@lp-labs/accessible-interactions
Framework-neutral focus movement and keyboard routing primitives. They model state and dispatch explicitly scoped input; they do not inspect or manipulate the DOM and do not guarantee that an application is accessible. Runtime and peer dependencies: none.
Install
npm install @lp-labs/accessible-interactionsNode 18+, strict TypeScript declarations, ESM and CommonJS builds.
Quickstart
import { createRovingFocus } from '@lp-labs/accessible-interactions';
const focus = createRovingFocus(['search', 'results', 'help'], {
orientation: 'vertical',
loop: true,
});
focus.setDisabled(['help']);
focus.move('ArrowDown');
if (focus.active() !== 'results') throw new Error('Unexpected focused item');Public APIs and types
createRovingFocus(items, options?)creates bounded roving-focus state.FocusOptionssupportsorientation(horizontal,vertical, orboth; defaultboth),loop(defaultfalse), andmaxItems(default 10,000).RovingFocusexposesactive,focus,move,setDisabled,replaceItems, andsnapshot.FocusSnapshotcontains independentitems,disabled, andactivevalues.FocusKeyandFocusOrientationdescribe accepted key/orientation values.createKeyboardScope(id, bindings)copies exact key-to-handler bindings.KeyboardScope.ididentifies the scope;dispatch(input)calls a matching handler only wheninput.scopematches and composition is not active.KeyboardInputis{ key, scope, repeat?, composing? }.KeyboardBindingsis a readonly mapping andKeyboardHandlerreceives a frozen input copy. The scope reportsrepeatbut leaves repeated-key policy to the handler.
The only runtime exports are createRovingFocus and createKeyboardScope. No global key listeners,
event objects, focus calls, tab-index attributes, ARIA roles, editable-target detection, or modifier
key conventions are provided. Supply those through your UI adapter and validate them in the host
application. Arrow keys move one enabled item; Home/End select first/last enabled item.
Customization
Map the UI event to this library's explicit scope and then render the returned active ID:
import { createKeyboardScope, createRovingFocus } from '@lp-labs/accessible-interactions';
const focus = createRovingFocus(['one', 'two', 'three'], { orientation: 'vertical' });
const keyboard = createKeyboardScope('results', {
ArrowDown: () => focus.move('ArrowDown'),
});
if (keyboard.dispatch({ key: 'ArrowDown', scope: 'results' })) {
if (focus.active() !== 'two') throw new Error('Keyboard adapter failed');
}Limits and errors
IDs are unique nonempty strings up to 256 UTF-16 units. maxItems is a non-negative safe integer.
Capacity and invalid numeric options throw RangeError; malformed IDs, unknown/disabled focus
targets, invalid bindings, or invalid dispatch inputs throw TypeError. Mutators validate complete
ID sets before changing state. Scope mismatch and composing input return false without dispatch.
State and snapshots are O(N) in item count; focus lookup is bounded by the 10,000-item default.
Support
If this library is useful to you, consider supporting its maintenance with a donation via PayPal.
Author
Created by Libin Prasanth. Connect on LinkedIn for updates and discussion.
