AngleDial
2 partsAn accessible circular angle / rotation picker. The root owns the angle value
in DEGREES (controlled via v-model:value or uncontrolled via
defaultValue), converts pointer presses anywhere on the dial into an angle,
snaps to snap / step, and handles the 0 / 360 seam either by wrapping
continuously (wrap) or bounding to an arc (clamp). It provides context to
AngleDialThumb, which renders the role="slider" handle on the ring and
owns keyboard interaction.
The angle convention is fixed: 0° points UP (12 o'clock) and increases
CLOCKWISE (right = 90°, down = 180°, left = 270°). Reach for it for rotation,
heading, or hue (a hue ring is min: 0, max: 360, wrap: 'wrap').
Demo
Anatomy
Import the parts and compose them. Each part forwards attributes to its underlying element.
import {
AngleDialRoot,
AngleDialThumb,
} from '@robonen/primitives/canvas/angle-dial';
<AngleDialRoot>
<AngleDialThumb />
</AngleDialRoot>API Reference
AngleDialRoot
RootAn accessible circular angle / rotation picker. The root owns the angle value in DEGREES (controlled via `v-model:value` or uncontrolled via `defaultValue`), converts pointer presses anywhere on the dial into an angle, snaps to `snap` / `step`, and handles the `0` / `360` seam either by wrapping continuously (`wrap`) or bounding to an arc (`clamp`). It provides context to `AngleDialThumb`, which renders the `role="slider"` handle on the ring and owns keyboard interaction. The angle convention is fixed: `0°` points UP (12 o'clock) and increases CLOCKWISE (right = 90°, down = 180°, left = 270°). Reach for it for rotation, heading, or hue (a hue ring is `min: 0, max: 360, wrap: 'wrap'`).
| Prop | Type | Description |
|---|---|---|
min? | number | Min angle in degrees. |
max? | number | Max angle in degrees. |
step? | number | Step granularity in degrees. |
largeStep? | number | Increment for Page keys / Shift+Arrow, in degrees. |
wrap? | AngleDialWrap | Seam behavior at the bounds. 'wrap' lets the value cross 0 / 360
continuously; 'clamp' bounds it to the arc [min, max]. |
snap? | AngleDialSnap | Snap increments in degrees — a scalar (e.g. 15) or an explicit list
(e.g. [0, 45, 90, 135, 180, 225, 270, 315]). undefined disables
snapping (only step rounding applies). |
disabled? | boolean | Disable all interaction. |
dir? | AngleDialDirection | Writing direction. When omitted it is inherited from the nearest
ConfigProvider (falling back to 'ltr'); an explicit value wins. |
defaultValue? | number | Uncontrolled initial angle in degrees. |
value? | number | null | Two-way bound value (v-model:value). |
| Event | Payload |
|---|---|
update:value | [value: number | null] |
AngleDialThumb
ThumbThe draggable handle of an `AngleDialRoot`, rendered as `role="slider"` and positioned on the ring by the current angle. It owns keyboard interaction (Arrow keys step by `step`, Shift+Arrow / Page keys by `largeStep`, Home / End jump to the bounds) and carries the ARIA value attributes. It INTENTIONALLY omits `aria-orientation`: a radial control has no single axis, so claiming `horizontal` / `vertical` would be misleading. Because a bare number is ambiguous on a circle, `aria-valuetext` strongly defaults to `` `${Math.round(deg)}°` `` (override via `valueText`). Give the thumb an `aria-label` (defaults to `'Angle'`). Exposes `value` and `point` (its fractional `{ x, y }` position on a unit circle, with `0.5,0.5` at center) as slot props for positioning.
| Prop | Type | Description |
|---|---|---|
valueText? | AngleDialValueText | Formatter producing a human-friendly aria-valuetext from the angle in
degrees. Defaults to (deg) => `${Math.round(deg)}°` — a degree suffix
disambiguates the bare number on a circular control. Return undefined to
omit aria-valuetext. |