3.8 KiB
<InputNumber>
(no submodules)
Purpose
Locale-aware numeric input. Parses incoming text using the active locale's decimal and grouping separators, accepts locale-specific digit glyphs (٣٫١٤ in Arabic-Indic), and emits a canonical number | null through onValueChange. Switches to a raw editable buffer on focus to avoid cursor jumps caused by re-formatting mid-keystroke; renders the locale-formatted value on blur. The plain <Input> is unaffected — <InputNumber> is a sibling primitive consumers opt into per call site, so existing form code is not disturbed.
Features
- Per-locale separator detection via
Intl.NumberFormat.formatToParts(12345.6)— handles en-GB1,234.5, de-DE1.234,5, fr-FR1 234,5, ar-AE Arabic-Indic glyphs. - Digit-folding for non-Latin numbering systems — Eastern Arabic, Devanagari, Bengali, etc.
- Focus / blur buffer split — raw Latin during edit, locale formatted at rest.
- Empty input emits
null(not0orNaN). - Invalid input on blur reverts to the previously valid formatted value rather than corrupting state.
- Controlled OR uncontrolled — pass
value(controlled) ordefaultValue(uncontrolled). displayOptions: Intl.NumberFormatOptionslets the rest-state format render percent, currency, scientific, etc.
Usage: Basic Example
import { InputNumber } from "@suaveplan/react-ui";
const [amount, setAmount] = useState<number | null>(null);
<InputNumber value={amount} onValueChange={setAmount} />
Usage: Advanced Example
Currency-style display (rest-state shows 1,234.50 while focused buffer shows 1234.5):
<InputNumber
value={price}
onValueChange={setPrice}
displayOptions={{
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}}
inputMode="decimal"
aria-label="Unit price"
/>
Bind directly to a form library's controlled value, with locale override for a multi-currency entry form:
<InputNumber
locale="de-DE"
value={field.value}
onValueChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
API Reference
<InputNumber value? defaultValue? onValueChange? locale? displayOptions? {...rest}>
value?: number | null— controlled.nullrenders empty.defaultValue?: number | null— uncontrolled initial.onValueChange?: (value: number | null) => void— fires on blur.locale?: string— defaults touseReactUiLocale().locale.displayOptions?: Intl.NumberFormatOptions.- All other
<input>HTML attributes are forwarded EXCEPTvalue,defaultValue,onChange,type(the component owns those).
parseLocaleNumber(text, locale): number | null
Standalone utility for parsing a locale-formatted numeric string. Used internally by the component; exported for consumers needing the same parse logic outside the component.
Implementation Notes
- The
Intl.NumberFormatinstance is cached per(locale, displayOptions)viauseMemo. The separator probe (formatToParts(12345.6)) is also cached per locale at module level. onValueChangefires only on blur to avoid emittingnullfor every transient unparseable keystroke. Forms that need keystroke-level validation can subscribe toonChangeon the underlying<input>directly via the spread (we do not ownonChangeonce the user has supplied one).- The component uses
type="text"withinputMode="decimal"rather thantype="number".type="number"rejects locale-specific separators in Chrome and triggers spinner UI consumers usually do not want. - When a parent re-renders with an externally-changed
valuewhile the input is NOT focused, the displayed value re-formats. While focused, the focused-buffer text wins so the user is not interrupted mid-edit.