first commit
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
# `a11y/keyboard-bindings`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Pure-value table mapping keyboard keys to parameter-control actions
|
||||
(`fine-decrement`, `coarse-increment`, `page-decrement`, `min`,
|
||||
`max`, `enter-value`). Phase-2 components — knobs, sliders, the
|
||||
parameter inspector — consume this table directly so the keymap is
|
||||
declared in exactly one place and easy to audit for accessibility
|
||||
compliance.
|
||||
|
||||
The module is React-free; it exports a frozen array, two factor
|
||||
constants (`KEY_MODIFIER_FACTOR_SHIFT` = 10, `KEY_MODIFIER_FACTOR_ALT`
|
||||
= 0.1), and a helper `matchKeyBinding(event)` that resolves a
|
||||
`KeyboardEvent`-shaped value into `{ binding, compositeFactor }` or
|
||||
`undefined` for a no-match.
|
||||
|
||||
## Features
|
||||
|
||||
- `PARAMETER_KEY_BINDINGS` — frozen `ReadonlyArray<KeyBinding>`.
|
||||
- `KEY_MODIFIER_FACTOR_SHIFT`, `KEY_MODIFIER_FACTOR_ALT` — composable
|
||||
multipliers.
|
||||
- `matchKeyBinding(event)` — pure function, no DOM imports.
|
||||
- Standard a11y keymap (arrows, page-up/down, home, end, enter).
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { matchKeyBinding } from "@suaveplan/react-audio/a11y";
|
||||
|
||||
function onKey(event: KeyboardEvent) {
|
||||
const match = matchKeyBinding(event);
|
||||
if (match === undefined) return;
|
||||
console.log(match.binding.action, match.compositeFactor);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```tsx
|
||||
import {
|
||||
matchKeyBinding,
|
||||
PARAMETER_KEY_BINDINGS,
|
||||
} from "@suaveplan/react-audio/a11y";
|
||||
|
||||
function Knob({
|
||||
value,
|
||||
min,
|
||||
max,
|
||||
step,
|
||||
onChange,
|
||||
}: {
|
||||
value: number;
|
||||
min: number;
|
||||
max: number;
|
||||
step: number;
|
||||
onChange: (next: number) => void;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
role="slider"
|
||||
aria-valuemin={min}
|
||||
aria-valuemax={max}
|
||||
aria-valuenow={value}
|
||||
tabIndex={0}
|
||||
onKeyDown={(event) => {
|
||||
const match = matchKeyBinding(event);
|
||||
if (match === undefined) return;
|
||||
event.preventDefault();
|
||||
const delta = step * match.compositeFactor;
|
||||
switch (match.binding.action) {
|
||||
case "fine-decrement":
|
||||
case "coarse-decrement":
|
||||
case "page-decrement":
|
||||
onChange(Math.max(min, value - delta));
|
||||
return;
|
||||
case "fine-increment":
|
||||
case "coarse-increment":
|
||||
case "page-increment":
|
||||
onChange(Math.min(max, value + delta));
|
||||
return;
|
||||
case "min":
|
||||
onChange(min);
|
||||
return;
|
||||
case "max":
|
||||
onChange(max);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `KeyBinding` — `{ key, action, factor, modifiers? }`.
|
||||
- `PARAMETER_KEY_BINDINGS: ReadonlyArray<KeyBinding>`.
|
||||
- `KEY_MODIFIER_FACTOR_SHIFT: 10`.
|
||||
- `KEY_MODIFIER_FACTOR_ALT: 0.1`.
|
||||
- `matchKeyBinding(event): { binding, compositeFactor } | undefined`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The keymap conventions (`←` fine, `↓` coarse, `Shift` ×10, `Alt`
|
||||
×0.1) match the design.md accessibility section so the upstream
|
||||
behaviour carries forward. The `matchKeyBinding` helper takes an
|
||||
event-shaped object rather than a `KeyboardEvent` so it is callable
|
||||
from test harnesses with plain object literals — and zero DOM types
|
||||
are imported into this file. The factor multiplication
|
||||
order is `binding.factor * shift * alt`; with both modifiers pressed
|
||||
the net effect is `factor × 10 × 0.1 = factor`, which is the
|
||||
intended "shift cancels alt" semantics.
|
||||
Reference in New Issue
Block a user