first commit
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
# `<XYPad>` — 2-axis touch surface
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`<XYPad>` is an accessible 2-axis touch surface that emits normalised
|
||||
`{ x, y }` coordinates as the user drags a pointer or steps with the
|
||||
arrow keys. The Y axis follows musical-software convention — the top
|
||||
of the pad is `y = 1` and the bottom is `y = 0`. Both axes are
|
||||
normalised to `[0, 1]` regardless of the pad's pixel size, so the
|
||||
caller can map them onto any parameter range with a simple scale.
|
||||
|
||||
Common applications include morphing two modulation sources, mapping
|
||||
filter cutoff against resonance, controlling pan + send, or any pair
|
||||
of correlated parameters that benefit from a single gestural surface.
|
||||
|
||||
## Features
|
||||
|
||||
- Pointer-drag emission via Pointer Events with capture for off-pad drags
|
||||
- Keyboard navigation — Arrows step by `step`, PageUp/PageDown by `step * 4`,
|
||||
Home/End jump the X axis to the extremes
|
||||
- Full Tab focusability with `role="application"`
|
||||
- Clamping to `[0, 1]` on every emission, including out-of-bounds drags
|
||||
- `disabled` removes the element from the Tab order and silences events
|
||||
- CSS-variable themed background and cursor
|
||||
- `data-x` / `data-y` deterministic attributes for E2E inspection
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```tsx
|
||||
import { useState } from "react";
|
||||
import { XYPad } from "@suaveplan/react-control-surface";
|
||||
|
||||
function Pad() {
|
||||
const [v, setV] = useState({ x: 0.5, y: 0.5 });
|
||||
return <XYPad value={v} onChange={setV} label="Morph" />;
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```tsx
|
||||
import { useState } from "react";
|
||||
import { XYPad } from "@suaveplan/react-control-surface";
|
||||
|
||||
function FilterPad({
|
||||
onCutoff,
|
||||
onResonance,
|
||||
}: {
|
||||
onCutoff: (hz: number) => void;
|
||||
onResonance: (q: number) => void;
|
||||
}) {
|
||||
const [v, setV] = useState({ x: 0.5, y: 0.5 });
|
||||
return (
|
||||
<XYPad
|
||||
value={v}
|
||||
onChange={(next) => {
|
||||
setV(next);
|
||||
onCutoff(40 + next.x * 19960);
|
||||
onResonance(0.5 + next.y * 9.5);
|
||||
}}
|
||||
step={0.01}
|
||||
width={320}
|
||||
height={240}
|
||||
label="Filter morph"
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| ----------- | ---------------------------------------- | ------- | ------------------------------------------ |
|
||||
| `value` | `{ x: number; y: number }` | req | Current normalised value, both in `[0, 1]`.|
|
||||
| `onChange` | `(next: XYValue) => void` | req | Called for every drag and keyboard step. |
|
||||
| `step` | `number` | `0.05` | Step size for arrow keys. |
|
||||
| `width` | `number` | `200` | Render width in CSS pixels. |
|
||||
| `height` | `number` | `200` | Render height in CSS pixels. |
|
||||
| `disabled` | `boolean` | `false` | When `true`, ignores all input. |
|
||||
| `label` | `string` | — | Visible + accessible label. |
|
||||
| `ariaLabel` | `string` | derived | Override the accessible label. |
|
||||
| `className` | `string` | — | Appended to `rcs-xy-pad`. |
|
||||
|
||||
The exported `pointerToValue(rect, clientX, clientY)` helper performs
|
||||
the pure rect-to-`[0, 1]` conversion (including Y-inversion and
|
||||
clamping) and is exported for re-use in custom pads.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Pointer events are used uniformly across mouse + touch + pen. On
|
||||
`pointerdown` the pad calls `setPointerCapture` so subsequent drags
|
||||
outside the bounding rect continue to fire `pointermove` events. The
|
||||
`draggingRef` flag avoids spurious moves before the user has pressed.
|
||||
|
||||
Keyboard handling is exhaustive: every recognised key calls
|
||||
`preventDefault` and emits a clamped value; unrecognised keys do
|
||||
nothing (the event bubbles for the consumer to handle). The
|
||||
`aria-valuetext` attribute is updated on every render with the
|
||||
current `x`/`y` truncated to two decimals, providing screen-reader
|
||||
support without a polling cycle.
|
||||
Reference in New Issue
Block a user