@synerise/ds-input-number 1.2.49 → 1.2.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/CLAUDE.md +149 -0
- package/package.json +6 -5
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [1.2.50](https://github.com/Synerise/synerise-design/compare/@synerise/ds-input-number@1.2.49...@synerise/ds-input-number@1.2.50) (2026-07-23)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @synerise/ds-input-number
|
|
9
|
+
|
|
6
10
|
## [1.2.49](https://github.com/Synerise/synerise-design/compare/@synerise/ds-input-number@1.2.48...@synerise/ds-input-number@1.2.49) (2026-07-09)
|
|
7
11
|
|
|
8
12
|
**Note:** Version bump only for package @synerise/ds-input-number
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# InputNumber (`@synerise/ds-input-number`)
|
|
2
|
+
|
|
3
|
+
> Locale-aware numeric input, **DS-native** (no Ant Design). Renders a styled `<input>` with custom
|
|
4
|
+
> up/down steppers, form-field layout (label, tooltip, description, error text), autosize support,
|
|
5
|
+
> and locale-driven thousand/decimal formatting.
|
|
6
|
+
|
|
7
|
+
## Package structure
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
src/
|
|
11
|
+
InputNumber.tsx — main component (value flow, steppers, autosize wiring)
|
|
12
|
+
InputNumber.types.ts — InputNumberProps (explicit, hand-written), deprecated Props alias
|
|
13
|
+
InputNumber.styles.tsx — DS-native styled-components (InputNumberRoot, HandlerWrap, HandlerUp/Down, InputField, Addon, …)
|
|
14
|
+
index.ts — public exports
|
|
15
|
+
hooks/
|
|
16
|
+
useStepper.ts — float-safe increment/decrement, clamp, precision, press-and-hold + Shift×10
|
|
17
|
+
constants/
|
|
18
|
+
inputNumber.constants.ts — MAXIMUM_FRACTION_DIGITS (20), MAXIMUM_NUMBER_DIGITS (15), NUMBER_DELIMITER ('.'), ANGLE_UP_SVG / ANGLE_DOWN_SVG (stepper glyphs)
|
|
19
|
+
utils/
|
|
20
|
+
inputNumber.utils.ts — formatNumber, parseFormattedNumber helpers
|
|
21
|
+
__specs__/
|
|
22
|
+
InputNumber.spec.tsx — Vitest tests (component + steppers + value flow)
|
|
23
|
+
inputNumber.utils.spec.tsx — Vitest tests (utils)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
There is **no `style/` LESS** — all styling is in `InputNumber.styles.tsx`.
|
|
27
|
+
|
|
28
|
+
## Public exports
|
|
29
|
+
|
|
30
|
+
### `InputNumber` (default export)
|
|
31
|
+
|
|
32
|
+
Functional component. Not a forwardRef — no ref forwarding.
|
|
33
|
+
|
|
34
|
+
Props = explicit `InputNumberOwnProps` + `FormFieldCommonProps` + `PassthroughAttributes` (`data-*`/`aria-*`)
|
|
35
|
+
+ standard input HTML attributes via `Omit<InputHTMLAttributes<HTMLInputElement>, 'value' | 'defaultValue' | 'onChange' | 'onBlur' | 'size' | 'min' | 'max' | 'type'>` (antd-parity forwarding of `maxLength`, `onFocus`, `tabIndex`, `inputMode`, `onKeyDown`, …). The omitted keys are owned explicitly by the component (different types/behavior). Two have special wiring: `onKeyDown` and `onKeyPress` are composed with the internal arrow-stepping / numeric-filter handlers (consumer called first), and `inputMode` defaults to `'decimal'` but a consumer value overrides it:
|
|
36
|
+
|
|
37
|
+
| Prop | Type | Default | Description |
|
|
38
|
+
|------|------|---------|-------------|
|
|
39
|
+
| `value` | `number \| null` | `undefined` | Controlled value |
|
|
40
|
+
| `defaultValue` | `number \| null` | `undefined` | Uncontrolled initial value |
|
|
41
|
+
| `onChange` | `(value: number \| null) => void` | `undefined` | Called with parsed numeric value on every change |
|
|
42
|
+
| `onBlur` | `(event: FocusEvent<HTMLInputElement>) => void` | `undefined` | Native input blur, invoked **after** the on-blur min/max re-align (consumers read `event.target.value`) |
|
|
43
|
+
| `onKeyPress` | `(event: KeyboardEvent<HTMLInputElement>) => void` | `undefined` | Forwarded the input's `keypress` event (fired before the internal numeric-key filter) |
|
|
44
|
+
| `onStep` | `(value: number, info: { offset: number; type: 'up' \| 'down' }) => void` | `undefined` | Fires on a stepper interaction (up/down button or ArrowUp/ArrowDown). antd parity — fires alongside `onChange`; `offset` is the applied step magnitude (incl. Shift×10) |
|
|
45
|
+
| `min` / `max` | `number` | `undefined` | Stepper bounds (clamped on step) |
|
|
46
|
+
| `step` | `number \| string` | `1` | Stepper increment |
|
|
47
|
+
| `disabled` / `readOnly` | `boolean` | `undefined` | Disable input; steppers are not rendered |
|
|
48
|
+
| `autoFocus` | `boolean` | `undefined` | Focus the input on mount |
|
|
49
|
+
| `tabIndex` | `number` | `undefined` | Forwarded to the underlying `<input>` (focus order) |
|
|
50
|
+
| `placeholder` | `string` | `undefined` | Input placeholder |
|
|
51
|
+
| `size` | `'small' \| 'middle' \| 'large'` | `undefined` | `'large'` → 48px tall |
|
|
52
|
+
| `error` | `boolean` | `undefined` | Triggers error state (red inset border + background) |
|
|
53
|
+
| `errorText` | `ReactNode` | `undefined` | Error message shown below input; also triggers error state |
|
|
54
|
+
| `label` / `description` / `tooltip` / `tooltipConfig` | from `FormFieldCommonProps` | `undefined` | Form-field layout |
|
|
55
|
+
| `prefixel` / `suffixel` | `ReactNode` | `undefined` | Content in the left / right addon slot |
|
|
56
|
+
| `raw` | `boolean` | `undefined` | When `true`, renders bare input without FormField wrapper |
|
|
57
|
+
| `valueFormatOptions` | `NumberToFormatOptions` | `undefined` | Override formatting options (e.g. `{ maximumFractionDigits: 2 }`) |
|
|
58
|
+
| `autoResize` | `AutoResizeProp` | `undefined` | Enable autosize: `true` or `{ minWidth, maxWidth?, stretchToFit? }` |
|
|
59
|
+
| `autoResizeProps` | `Partial<Pick<AutosizeInputProps, 'placeholderIsMinWidth' \| 'wrapperClassName' \| 'wrapperStyle' \| 'extraWidth'>>` | `undefined` | Extra props passed to `AutosizeWrapper` |
|
|
60
|
+
| `style` / `className` | `CSSProperties` / `string` | `undefined` | Applied to the inner `InputNumberWrapper` |
|
|
61
|
+
|
|
62
|
+
> **Removed in the antd-removal migration (STOR-2334) — BREAKING.** The antd-only props are gone:
|
|
63
|
+
> `formatter`, `parser` (formatting is always locale-driven via `useDataFormat` + `valueFormatOptions`),
|
|
64
|
+
> `decimalSeparator` (superseded by the locale `decimalDelimiter`), `controls`, `keyboard`, `stringMode`,
|
|
65
|
+
> `bordered`, `status`, `prefix`, `onPressEnter`, and `addonBefore`/`addonAfter` (use `prefixel`/`suffixel`).
|
|
66
|
+
> Also removed: `precision` — the stepped value is now rounded to
|
|
67
|
+
> `max(decimals(step), valueFormatOptions?.maximumFractionDigits ?? 0)`, so express decimal precision
|
|
68
|
+
> via `step` and/or `valueFormatOptions` instead.
|
|
69
|
+
|
|
70
|
+
### `InputNumberProps` (type export)
|
|
71
|
+
|
|
72
|
+
The full props type. `Props` is also exported from the types file as a `@deprecated` alias but is **not**
|
|
73
|
+
re-exported from `index.ts`.
|
|
74
|
+
|
|
75
|
+
## Usage patterns
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import InputNumber from '@synerise/ds-input-number';
|
|
79
|
+
|
|
80
|
+
// Basic with form field
|
|
81
|
+
<InputNumber label="Quantity" min={1} max={100} defaultValue={10} onChange={(v) => console.log(v)} />
|
|
82
|
+
|
|
83
|
+
// Error state
|
|
84
|
+
<InputNumber label="Price" error errorText="Must be positive" value={price} onChange={setPrice} />
|
|
85
|
+
|
|
86
|
+
// With prefix/suffix
|
|
87
|
+
<InputNumber prefixel="$" suffixel="USD" min={0} step={0.01} />
|
|
88
|
+
|
|
89
|
+
// Raw (no form field wrapper)
|
|
90
|
+
<InputNumber raw value={qty} onChange={setQty} />
|
|
91
|
+
|
|
92
|
+
// Autosize
|
|
93
|
+
<InputNumber autoResize={{ minWidth: '60px', maxWidth: '200px', stretchToFit: true }} value={val} onChange={setVal} />
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Styling
|
|
97
|
+
|
|
98
|
+
All styling lives in `InputNumber.styles.tsx` as DS-native styled-components driven by transient
|
|
99
|
+
`$`-props and `:focus-within` (no antd LESS, no `.ant-*`/`.ds-*` styling selectors). The
|
|
100
|
+
`ant-input-number-*` and `ds-input-number-*` class names are kept on the elements as hooks only
|
|
101
|
+
(ui-tests / interim external CSS). Theme tokens used: `grey-700` (text), `grey-300` (idle border),
|
|
102
|
+
`blue-600` (focus border), `red-600`/`red-050` (error), `grey-500` (placeholder), `grey-050` (addon).
|
|
103
|
+
|
|
104
|
+
> **Deep import**: `InputNumber.styles.tsx` imports `autoresizeConfObjToCss` from
|
|
105
|
+
> `@synerise/ds-input/dist/Input.styles` — a fragile internal path (pre-existing; flagged as an
|
|
106
|
+
> eslint warning until ds-input exposes it from the root).
|
|
107
|
+
|
|
108
|
+
## Key dependencies
|
|
109
|
+
|
|
110
|
+
- `@synerise/ds-form-field` — `FormField` wrapper providing label, tooltip, description, error layout
|
|
111
|
+
- `@synerise/ds-input` — `AutosizeWrapper` + `AutoResizeProp` type
|
|
112
|
+
- `@synerise/ds-core` — `useDataFormat` hook (locale-aware number formatting), `NumberToFormatOptions`, `ThemeProps`
|
|
113
|
+
- `@synerise/ds-utils` — `useResizeObserver` (for `stretchToFit` autosize) + `PassthroughAttributes` type
|
|
114
|
+
- `uuid` — generates stable `id` for label `htmlFor` association
|
|
115
|
+
|
|
116
|
+
## Implementation notes
|
|
117
|
+
|
|
118
|
+
- **Steppers (`useStepper`)**: float-safe arithmetic (integer scaling, so `0.1 + 0.2 === 0.3`), clamp to
|
|
119
|
+
`min`/`max`, then round to `max(decimals(step), valueFormatOptions?.maximumFractionDigits ?? 0)` — so a
|
|
120
|
+
fractional `step` (e.g. `0.01`) keeps its decimals without any extra config, and `maximumFractionDigits`
|
|
121
|
+
raises the rounding floor. Mouse press-and-hold auto-repeats (600ms delay → 200ms interval);
|
|
122
|
+
`Shift` multiplies the step by ten; ArrowUp/ArrowDown step from the keyboard (a consumer `onKeyDown`
|
|
123
|
+
is invoked first, then the stepping runs). Steppers are not rendered when `disabled` or `readOnly`.
|
|
124
|
+
- **Value flow**: a controlled `<input type="text">`. Typed input is parsed locale→number string first
|
|
125
|
+
(`parseFormattedNumber`) then re-formatted for display (`formatNumber`) — preserving a lone `-`, a
|
|
126
|
+
trailing decimal and trailing zeros while typing. `onChange` always emits `number | null` (emits `0`,
|
|
127
|
+
not `null`). `aria-valuenow` carries the raw numeric value.
|
|
128
|
+
- **On-blur min/max re-align (antd parity)**: typing is never clamped (rc-input-number's
|
|
129
|
+
`userTyping=true` path), so a value can exceed `min`/`max` mid-edit. On blur, an out-of-range value is
|
|
130
|
+
re-aligned to the nearest bound (`> max ⇒ max`, `< min ⇒ min`) — mirroring rc-input-number's
|
|
131
|
+
`flushInputValue(false)` → `triggerValueUpdate(…, userTyping=false)` → `getRangeValue`. When it clamps
|
|
132
|
+
it updates the display and fires `onChange(clamped)`; an in-range value is left untouched (no
|
|
133
|
+
`onChange`). Empty / `NaN` values are not clamped. Steppers clamp independently in `useStepper`.
|
|
134
|
+
- **Numeric key filter**: lives on the input's `onKeyPress` — the native `keypress` event does not fire
|
|
135
|
+
for Backspace / arrows / Delete, so editing keys are never blocked. A consumer-supplied `onKeyPress`
|
|
136
|
+
prop is invoked first (so it can inspect / `preventDefault`), then the numeric filter runs.
|
|
137
|
+
- **Locale-aware formatting**: `useDataFormat()` returns `formatValue`, `thousandDelimiter`,
|
|
138
|
+
`decimalDelimiter` based on the active `DataFormatNotationType` (`'EU'` or `'US'`).
|
|
139
|
+
- **Controlled/uncontrolled hybrid**: keeps `localValue`/`displayValue` state; `value` prop changes sync
|
|
140
|
+
via `useEffect` (only when `value !== localValue`).
|
|
141
|
+
- **Error state**: `error={true}` or a non-empty `errorText` adds the `error` class to the root and the
|
|
142
|
+
red inset border + `red-050` background via `$error`.
|
|
143
|
+
- **`raw` mode**: skips the `FormField` wrapper — `label`/`description`/`errorText`/`tooltip` have no effect.
|
|
144
|
+
- **`autoResize` + `stretchToFit`**: `useResizeObserver` on a wrapper ref dynamically sets `max-width`;
|
|
145
|
+
`AUTOSIZE_EXTRA_WIDTH = 45` accommodates the stepper buttons.
|
|
146
|
+
- **Maximum digits**: `parseFormattedNumber` truncates to `MAXIMUM_NUMBER_DIGITS = 15` to avoid
|
|
147
|
+
floating-point precision issues near `Number.MAX_SAFE_INTEGER`.
|
|
148
|
+
- **Uses Vitest** for testing.
|
|
149
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synerise/ds-input-number",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.50",
|
|
4
4
|
"description": "Input-Number UI Component for the Synerise Design System",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"repository": "Synerise/synerise-design",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
"/dist",
|
|
19
19
|
"CHANGELOG.md",
|
|
20
|
+
"CLAUDE.md",
|
|
20
21
|
"README.md",
|
|
21
22
|
"package.json",
|
|
22
23
|
"LICENSE.md"
|
|
@@ -38,9 +39,9 @@
|
|
|
38
39
|
"sideEffects": false,
|
|
39
40
|
"types": "dist/index.d.ts",
|
|
40
41
|
"dependencies": {
|
|
41
|
-
"@synerise/ds-form-field": "^1.3.
|
|
42
|
-
"@synerise/ds-input": "^1.7.
|
|
43
|
-
"@synerise/ds-utils": "^1.10.
|
|
42
|
+
"@synerise/ds-form-field": "^1.3.24",
|
|
43
|
+
"@synerise/ds-input": "^1.7.13",
|
|
44
|
+
"@synerise/ds-utils": "^1.10.2",
|
|
44
45
|
"uuid": "^8.3.2"
|
|
45
46
|
},
|
|
46
47
|
"peerDependencies": {
|
|
@@ -49,5 +50,5 @@
|
|
|
49
50
|
"styled-components": "^5.3.3",
|
|
50
51
|
"vitest": "4"
|
|
51
52
|
},
|
|
52
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "d0a43cc43d8528a36f105aceea52ab470edb71d9"
|
|
53
54
|
}
|