first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -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.