Files
skills/docs/ui/react/react-audio/src/a11y/keyboard-bindings/keyboard-bindings.md
2026-05-29 08:20:56 +01:00

3.7 KiB
Raw Blame History

a11y/keyboard-bindings

(no submodules)

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

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

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.