import type { PrimitivePropsWithRef } from '../utils/primitive.cjs'; /** * The payload `onValueChange` receives, carrying both ends of the change * plus what drove it. A dial's `onValueCommit` fires only when a pointer drag * ends — a key press is already a discrete commit and fires none. */ export interface AngleSliderValueChangeDetail { /** The angle the change settled on, in degrees, already folded into `[0, 360)`. */ value: number; /** The angle held before the change, or `null` when there was none. */ previousValue: number | null; /** * What drove the change: `'keyboard'` for arrow/Page/Home/End keys, * `'pointer'` for a drag frame, `'programmatic'` when the application set it. */ reason: 'keyboard' | 'pointer' | 'programmatic'; } /** @internal Shared state distributed from {@link AngleSlider} to its parts via context. Not part of the public API. */ export interface AngleSliderContextValue { value: number; step: number; disabled: boolean; readOnly: boolean; dragging: boolean; } /** Props for the {@link AngleSlider} root. */ export interface AngleSliderProps extends Omit, 'onChange'> { /** * Current angle in degrees, for controlled usage. Pair with `onValueChange`. * Values outside `[0, 360)` are folded into it on the next change — 450 and * 90 are the same direction, and the widget only ever reports one of them. * @default undefined — uncontrolled, tracks its own value internally. * @min 0 * @max 360 */ value?: number | undefined; /** * Initial angle in degrees, for uncontrolled usage, seeding the first render. * @default 0 — pointing straight up, since 0° is at 12 o'clock. * @min 0 * @max 360 */ defaultValue?: number | undefined; /** * Fires on every change — each arrow key, and each frame of a drag that * crosses a `step` boundary — carrying the new angle, the angle held before * it, and the reason the change happened. A change that resolves to the * angle already held fires nothing. * @default undefined — no-op if omitted. */ onValueChange?: ((detail: AngleSliderValueChangeDetail) => void) | undefined; /** * Fires once with the committed angle when a drag ends (pointer release or * cancel) — the callback to hang an expensive effect on, since a drag emits * `onValueChange` per step crossed but this exactly once. Keyboard changes * do not fire it, being discrete commits already reported by `onValueChange`. * @default undefined — no-op if omitted. */ onValueCommit?: ((value: number) => void) | undefined; /** * Increment in degrees that both pointer and keyboard input are rounded to. * Shift+arrow always moves 10° and PageUp/PageDown always move 45°, * regardless of this value. `0` makes pointer input continuous, while the * arrows keep falling back to 1° so every angle stays reachable by keyboard. * @default 1 — every whole degree is reachable. */ step?: number | undefined; /** * Blocks pointer and keyboard interaction and takes the root out of the tab * order (it stays programmatically focusable, at `tabIndex={-1}`). * @default false */ disabled?: boolean | undefined; /** * Displays the current angle but blocks changes, while staying focusable and * in the tab order — unlike `disabled`, a read-only dial can still be reached * and read by a keyboard user. * @default false */ readOnly?: boolean | undefined; /** * Restrict the value to the angles declared by the rendered `AngleSliderMark` * parts, discovered from the DOM at interaction time so one added or removed * later just works. With marks present, dragging snaps to the nearest one, * arrow keys move one mark, Shift+arrow/PageUp/PageDown move three, and * Home/End jump to the first/last mark; with none rendered, falls back to * the `step` grid rather than becoming unchangeable. * @default false */ restrictToMarks?: boolean | undefined; /** * Formats the angle for `aria-valuetext`, which is what a screen reader * announces in place of the bare `aria-valuenow`. Supply it to localise the * announcement, or to announce something that is not degrees at all — a * compass bearing, a clock position. Returning an empty string is treated as * no override. * @default (angle) => `${angle}°` */ formatValue?: ((angle: number) => string) | undefined; /** * Form field name. With it the current angle is submitted under that name * through a visually-hidden input; without it the dial submits nothing. * @default undefined */ name?: string | undefined; /** * Id of the `
` the dial submits into, forwarded to the hidden input, * so a dial can live outside the form it belongs to. * @default undefined */ form?: string | undefined; /** * Ticks rendered around the dial, one derived `AngleSliderMark` part each. * Marks are discovered from the DOM at interaction time (same mechanism * `restrictToMarks` always used), so a derived mark works with it exactly * as a hand-composed one does. Ignored when `children` is given. * @default undefined — no marks are rendered. */ marks?: readonly AngleSliderMarkData[] | undefined; } /** * One entry of {@link AngleSliderProps.marks} — a tick at an angle, with an * optional label. Named `Data`, not `AngleSliderMark`, because that name is * the `AngleSliderMark` part component's own. */ export interface AngleSliderMarkData { /** The angle in degrees this tick sits at. */ value: number; /** Text rendered inside the derived mark. */ label?: string | undefined; } /** Props for the {@link AngleSliderThumb}, the handle that points at the current angle. */ export interface AngleSliderThumbProps extends PrimitivePropsWithRef<'div'> { } /** Props for {@link AngleSliderMarks}, the container for the tick marks around the dial. */ export interface AngleSliderMarksProps extends PrimitivePropsWithRef<'div'> { } /** Props for a single {@link AngleSliderMark} tick. */ export interface AngleSliderMarkProps extends PrimitivePropsWithRef<'div'> { /** * The angle in degrees this tick sits at. Published back as * `--skdx-angle-slider-mark-angle` for positioning, and read by the root when * `restrictToMarks` is set. */ value: number; }