@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.
Files changed (3) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/CLAUDE.md +149 -0
  3. 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.49",
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.23",
42
- "@synerise/ds-input": "^1.7.12",
43
- "@synerise/ds-utils": "^1.10.1",
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": "5c90008871be36fb52553a2ed3a633acf5db5b3b"
53
+ "gitHead": "d0a43cc43d8528a36f105aceea52ab470edb71d9"
53
54
  }