first commit
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
# `<InputNumber>`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## 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-GB `1,234.5`, de-DE `1.234,5`, fr-FR `1 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` (not `0` or `NaN`).
|
||||
- Invalid input on blur reverts to the previously valid formatted value rather than corrupting state.
|
||||
- Controlled OR uncontrolled — pass `value` (controlled) or `defaultValue` (uncontrolled).
|
||||
- `displayOptions: Intl.NumberFormatOptions` lets the rest-state format render percent, currency, scientific, etc.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```tsx
|
||||
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`):
|
||||
|
||||
```tsx
|
||||
<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:
|
||||
|
||||
```tsx
|
||||
<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. `null` renders empty.
|
||||
- `defaultValue?: number | null` — uncontrolled initial.
|
||||
- `onValueChange?: (value: number | null) => void` — fires on blur.
|
||||
- `locale?: string` — defaults to `useReactUiLocale().locale`.
|
||||
- `displayOptions?: Intl.NumberFormatOptions`.
|
||||
- All other `<input>` HTML attributes are forwarded EXCEPT `value`, `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.NumberFormat` instance is cached per `(locale, displayOptions)` via `useMemo`. The separator probe (`formatToParts(12345.6)`) is also cached per locale at module level.
|
||||
- `onValueChange` fires only on blur to avoid emitting `null` for every transient unparseable keystroke. Forms that need keystroke-level validation can subscribe to `onChange` on the underlying `<input>` directly via the spread (we do not own `onChange` once the user has supplied one).
|
||||
- The component uses `type="text"` with `inputMode="decimal"` rather than `type="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 `value` while 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.
|
||||
Reference in New Issue
Block a user