import type { FormValidityHandle } from '../utils/formControl.js'; import type { PrimitivePropsWithRef } from '../utils/primitive.js'; /** @internal Shared context state distributed from {@link NumberField} to its parts. */ export interface NumberFieldContextValue { /** `null` is the empty field — nothing entered, nothing to submit. */ value: number | null; min: number; max: number; step: number; disabled: boolean; invalid: boolean; readOnly: boolean; required: boolean; formatOptions?: Intl.NumberFormatOptions | undefined; /** BCP-47 locale for format/parse; undefined disables formatting and rounds through String/Number instead. */ locale?: string | undefined; /** Typed text snaps into [min, max] on blur; steppers/keys clamp under both settings. */ clampBehavior: 'blur' | 'never'; /** Mouse wheel steps the value when input has focus. */ allowWheel: boolean; /** Committed value sits outside [min, max]; only reachable under clampBehavior='never', which raises aria-invalid / data-invalid. */ outOfRange: boolean; /** Clamps and publishes value-change per keystroke without settling. */ setValue: (next: number | null) => void; /** Clamps and publishes both value-change and value-commit, even when the value is unchanged. */ commitValue: (next: number | null) => void; /** Stepper by steps × step with decimal precision, clamps and commits. Empty field steps from 0. */ stepBy: (steps: number) => void; } /** Props for the {@link NumberField} root. */ export interface NumberFieldProps extends Omit, 'defaultValue' | 'onChange'> { /** * Current value, for controlled usage, where `null` is the empty field. * @default undefined — uncontrolled, tracks its own value internally. */ value?: number | null | undefined; /** * Initial value, for uncontrolled usage, clamped to `min`/`max` on mount. * @default null */ defaultValue?: number | null | undefined; /** * Fires on every keystroke with the clamped value; `null` when emptied. * @default undefined */ onValueChange?: ((value: number | null) => void) | undefined; /** * Fires once the value settles (blur, Enter, arrow, Home/End, or stepper), unlike `onValueChange`. * @default undefined */ onValueCommit?: ((value: number | null) => void) | undefined; /** * Lower bound the value is clamped to, including via increment/decrement and arrow keys. * @default -Infinity */ min?: number | undefined; /** * Upper bound the value is clamped to, including via increment/decrement and arrow keys. * @default Infinity */ max?: number | undefined; /** * Amount the step buttons and Arrow Up/Down change the value by; Page Up/Down uses 10x this. * @default 1 */ step?: number | undefined; /** * Disables the field, its input, and both step buttons. * @default false */ disabled?: boolean | undefined; /** * Marks the field as invalid, exposed as `data-invalid` on the root and `aria-invalid`/`data-invalid` on the input. * @default false */ invalid?: boolean | undefined; /** * Makes the input read-only and the step buttons inert but focusable. * @default false */ readOnly?: boolean | undefined; /** * Intl.NumberFormat options; typed text parses through the same formatter, min/max/step still operate on the numeric value. * @default undefined (displays as String(value)) */ formatOptions?: Intl.NumberFormatOptions | undefined; /** * BCP-47 locale for formatting and parsing, resolved once so server and client agree. * @default undefined — the runtime's default locale. */ locale?: string | undefined; /** * Snap typed text to [min, max]; 'never' publishes as-typed and marks aria-invalid / data-invalid. * @default 'blur' */ clampBehavior?: 'blur' | 'never' | undefined; /** * Mouse wheel steps when focused (focus-gated to avoid edits while scrolling). * @default false */ allowWheel?: boolean | undefined; /** * Id of the `
` this field submits into, for a field rendered outside it. * @default undefined — the field belongs to its nearest ancestor ``, if any. */ form?: string | undefined; /** * Form field name; hidden input submits the raw committed number, not formatted text. * @default undefined */ name?: string | undefined; /** * Marks required with aria-required and valueMissing constraint (requires name). * @default false */ required?: boolean | undefined; /** * Renders derived decrement/increment buttons flanking the input; ignored when `children` is given. * @default true */ showControls?: boolean | undefined; /** * Accessible name of the derived increment button; ignored when `children` is given. * @default 'Increment' */ incrementLabel?: string | undefined; /** * Accessible name of the derived decrement button; ignored when `children` is given. * @default 'Decrement' */ decrementLabel?: string | undefined; /** * Size rung the skin scales the field by, mirrored to `data-size`. * @default undefined — the skin's own default size. */ size?: 'md' | 'sm' | 'lg' | (string & {}) | undefined; } /** What {@link NumberField}'s forwarded ref carries: the root `
` plus the two constraint-validation methods. */ export type NumberFieldHandle = HTMLDivElement & FormValidityHandle; /** Props for {@link NumberFieldInput}, the text input driven by `NumberField`'s context. */ export interface NumberFieldInputProps extends Omit, 'value' | 'defaultValue' | 'type'> { } /** Button that increments value by one step. */ export interface NumberFieldIncrementProps extends PrimitivePropsWithRef<'button'> { /** * Accessible name; children (usually '+') don't announce clearly alone. * @default 'Increment' */ label?: string | undefined; } /** Button that decrements value by one step. */ export interface NumberFieldDecrementProps extends PrimitivePropsWithRef<'button'> { /** * Accessible name; children (usually '-') don't announce clearly alone. * @default 'Decrement' */ label?: string | undefined; }