react-intl-number-input 0.8.6 → 0.9.0

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 CHANGED
@@ -7,6 +7,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.9.0] - 2026-10-13
11
+
12
+ ### Added
13
+
14
+ - Opt-in `controlled` mode that keeps the parent's `value` authoritative when edits are rejected. The default preserves historical optimistic editing.
15
+ - Optional `readOnly` information for custom controls and formatted `aria-valuetext`.
16
+ - Regression tests, library/demo type checking, installed npm tarball checks for CJS, ESM, types, SSR and DOM interactions, and a React 16.8–19 CI compatibility matrix.
17
+
18
+ ### Fixed
19
+
20
+ - Keep numeric state, formatted text, custom controls and accessibility attributes synchronized when values, bounds, locale or precision change.
21
+ - Avoid rescaling existing numeric state on blur after a precision change.
22
+ - Parse Han decimal and supplementary-plane digits and preserve localized caret positions.
23
+ - Preserve amounts when editing numeric prefixes or suffixes.
24
+ - Enforce `readOnly` for built-in and custom controls.
25
+ - Avoid decimal stepping accumulation within JavaScript's safe integer range.
26
+ - Supply actual input elements and consistent event methods in programmatic change callbacks.
27
+ - Build artifacts during `prepack` and validate the package before publication.
28
+ - Correct README examples, Node.js requirements and the demo's currency hint and accessible input names.
29
+
30
+ ### Changed
31
+
32
+ - Extract pure numeric helpers while retaining public callback signatures, scaled step units, implied-decimal string values, zero-on-clear behavior and existing element layout.
33
+ - Use `gh-pages` 6.1.1 to avoid the development-only `braces` vulnerability in the newer dependency chain; the demo deployment command is unchanged.
34
+ - Add `npm run release` and a GitHub Actions workflow that publishes with provenance.
35
+
36
+ ## [0.8.7] - 2026-10-01
37
+
38
+ ### Fixed
39
+
40
+ - Initialize the internal numeric value within `minValue` and `maxValue` bounds so the displayed value, accessibility attributes, and custom controls agree on the first render.
41
+ - Ignore digits in configured prefixes and suffixes when parsing edits.
42
+ - Parse locale-specific digits such as Arabic-Indic numerals.
43
+ - Prevent custom `setValue` controls from changing disabled inputs or accepting non-finite values.
44
+
45
+ ### Added
46
+
47
+ - Keyboard stepping with Arrow Up and Arrow Down; hold Shift to adjust by ten steps.
48
+
10
49
  ## [0.8.6] - 2026-09-23
11
50
 
12
51
  This is a maintainer/toolchain release. The published component API is unchanged.
package/CONTRIBUTING.md CHANGED
@@ -7,7 +7,7 @@ Thank you for your interest in contributing to `react-intl-number-input`.
7
7
  - Node.js 20+ (recommended: current Active LTS)
8
8
  - npm 10+
9
9
 
10
- These versions are required to build and test the library. Consumers of the published npm package are not constrained by this.
10
+ The package declares Node.js 20+ in `engines`. The runtime component executes in browsers; Node.js is used for installation, builds, tests and publication.
11
11
 
12
12
  ## Getting started
13
13
 
@@ -33,6 +33,9 @@ Open [http://localhost:3001](http://localhost:3001). The demo lives in `example/
33
33
  | --- | --- |
34
34
  | `npm start` | esbuild dev server for the demo |
35
35
  | `npm test` | Run unit tests with Jest |
36
+ | `npm run typecheck` | Check library and demo types |
37
+ | `npm run check` | Tests, type checking, library builds and installed-tarball validation |
38
+ | `npm run check:package` | Validate the built npm tarball: CJS, ESM, types, SSR and DOM interactions |
36
39
  | `npm run build:lib` | Compile `src/` to CommonJS, ESM, and types in `dist/` |
37
40
  | `npm run build` | Build the demo bundle in `example/dist` |
38
41
  | `npm run publish-demo` | Build and deploy the demo to GitHub Pages |
@@ -52,10 +55,10 @@ The npm package ships `dist/`, `README.md`, `LICENSE`, `CHANGELOG.md`, and `CONT
52
55
  Run the full test suite before opening a pull request:
53
56
 
54
57
  ```bash
55
- npm test
58
+ npm run check
56
59
  ```
57
60
 
58
- Tests live alongside the source in `src/index.test.tsx`.
61
+ Tests live alongside the source in `src/index.test.tsx` and `src/regressions.test.tsx`. CI also checks the installed package with React 16.8, 17, 18 and 19, and runs the full checks on Node.js 20 and 24.
59
62
 
60
63
  ## Building the library
61
64
 
@@ -73,36 +76,58 @@ This generates:
73
76
 
74
77
  ## Publishing to npm
75
78
 
76
- ### Prerequisites
79
+ ### Ownership & Accounts
77
80
 
78
- - npm account with publish access to `react-intl-number-input`
79
- - logged in: `npm login`
81
+ The package is published under the npm account **`wipmob`** (`nandorip@live.com`).
80
82
 
81
- ### Release checklist
83
+ > **Tip (Co-Maintainer):** To allow publishing from the `nandorip` account as well, add it via npm CLI or web interface:
84
+ > ```bash
85
+ > npm owner add nandorip react-intl-number-input
86
+ > ```
87
+ > Or manage collaborators at [npmjs.com/package/react-intl-number-input/access](https://www.npmjs.com/package/react-intl-number-input/access).
82
88
 
83
- 1. Update `CHANGELOG.md` with the new version and changes.
84
- 2. Bump the version in `package.json` following [semver](https://semver.org/):
89
+ ### Method 1: Automated Release via GitHub Actions (Recommended)
90
+
91
+ This repository includes an automated workflow ([`.github/workflows/publish.yml`](.github/workflows/publish.yml)) with npm provenance attestations. This avoids local 2FA prompts and terminal issues.
92
+
93
+ 1. **One-time setup:**
94
+ - In your npm account (**`wipmob`**), generate an **Automation Token** or **Granular Access Token** with *Read and write* permissions for `react-intl-number-input`.
95
+ - In GitHub, go to **Settings > Secrets and variables > Actions** and add `NPM_TOKEN` with your token.
96
+ 2. **Publishing a release:**
97
+ - Create a new Release on GitHub (e.g. `v0.9.0`), or trigger the workflow manually under **Actions > Publish to npm > Run workflow**.
98
+ - GitHub Actions will run all checks, build, verify the tarball, and publish with `--provenance` automatically.
99
+
100
+ ### Method 2: Local CLI Release (`npm run release`)
101
+
102
+ A release helper script is available to validate requirements, run the test suite, and publish:
103
+
104
+ 1. **Verify login:**
85
105
  ```bash
86
- npm version patch # 0.7.0 → 0.7.1
87
- npm version minor # 0.7.0 → 0.8.0
88
- npm version major # 0.7.0 → 1.0.0
106
+ npm whoami # Should return 'wipmob' (or 'nandorip' if added as owner)
89
107
  ```
90
- 3. Publish:
108
+ If not logged in: `npm login`.
109
+ 2. **Bump version (if not already done):**
91
110
  ```bash
92
- npm publish
111
+ npm version patch # or minor / major
112
+ ```
113
+ 3. **Execute release:**
114
+ ```bash
115
+ npm run release
116
+ ```
117
+ If your account requires 2FA and you are in a non-interactive shell, pass the OTP directly:
118
+ ```bash
119
+ npm run release -- --otp=123456
93
120
  ```
94
121
 
95
- The `prepublishOnly` script runs automatically before publish:
96
-
97
- ```
98
- npm test → npm run build:lib
99
- ```
100
-
101
- Do not commit `dist/` — it is built during publish and excluded via `.gitignore`.
122
+ The script automatically:
123
+ - Confirms the active npm user.
124
+ - Runs `npm run check` (Jest tests, TypeScript, library builds, tarball verification).
125
+ - Runs `npm publish`.
126
+ - Creates and pushes the git tag `v<version>`.
102
127
 
103
128
  ### Deploying the demo
104
129
 
105
- To update the live demo on GitHub Pages:
130
+ To update the live demo on GitHub Pages independently:
106
131
 
107
132
  ```bash
108
133
  npm run publish-demo
@@ -112,9 +137,9 @@ npm run publish-demo
112
137
 
113
138
  1. Fork the repository and create a feature branch.
114
139
  2. Make your changes with tests where applicable.
115
- 3. Run `npm test` and ensure it passes.
140
+ 3. Run `npm run check` and ensure it passes.
116
141
  4. Open a pull request with a clear description of the change.
117
142
 
118
143
  ## Reporting issues
119
144
 
120
- Open an issue at [github.com/nandorip/react-intl-number-input/issues](https://github.com/nandorip/react-intl-number-input/issues).
145
+ Open an issue at [github.com/nandorip/react-intl-number-input/issues](https://github.com/nandorip/react-intl-number-input/issues).
package/README.md CHANGED
@@ -1,12 +1,13 @@
1
1
  # react-intl-number-input
2
2
 
3
- A React component for masked and formatted number input with `Intl.NumberFormat` locale support.
3
+ A React number input with implied decimal places and locale formatting through `Intl.NumberFormat`. For money-style entry, typing `1234` with two decimal places produces `12.34`.
4
4
 
5
- [Demo](https://nandorip.github.io/react-intl-number-input/example/dist/)
5
+ [Interactive demo](https://nandorip.github.io/react-intl-number-input/)
6
6
 
7
7
  ## Requirements
8
8
 
9
9
  - React: `>=16.8.0 <20`
10
+ - Node.js: `>=20` for installation and local development (declared in the npm package's `engines` field). The component runs in the browser.
10
11
 
11
12
  ## Install
12
13
 
@@ -55,7 +56,7 @@ function App() {
55
56
  console.log('Numeric:', val, 'Masked:', masked);
56
57
  };
57
58
 
58
- return <IntlNumberInput value={value} onChange={handleChange} locale="pt-BR" />;
59
+ return <IntlNumberInput controlled value={value} onChange={handleChange} locale="pt-BR" />;
59
60
  }
60
61
  ```
61
62
 
@@ -80,7 +81,8 @@ ReactDOM.render(<App />, document.getElementById('root'));
80
81
 
81
82
  | Name | Type | Default | Description |
82
83
  | --- | --- | :---: | --- |
83
- | value | `number` \| `string` | `0` | Controlled numeric value |
84
+ | value | `number` \| `string` | `0` | Initial/synchronized value; numbers use numeric units, strings use implied-decimal parsing |
85
+ | controlled | `boolean` | `false` | Keep `value` authoritative, including when the parent rejects an edit |
84
86
  | locale | `string` | `'en-US'` | BCP 47 language tag ([Intl locales](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#locale_identification_and_negotiation)) |
85
87
  | prefix | `string` | `''` | Prefix shown in the masked value |
86
88
  | suffix | `string` | `''` | Suffix shown in the masked value |
@@ -93,7 +95,7 @@ ReactDOM.render(<App />, document.getElementById('root'));
93
95
  | maxValue | `number` | — | Maximum allowed value |
94
96
  | showStepButtons | `boolean` | `false` | Renders built-in +/- step buttons (ignored when `renderControls` is set) |
95
97
  | renderControls | `function` | — | `(props: ControlsRenderProps) => ReactNode` — custom stepper UI |
96
- | step | `number` | `1` | Step increment in display units (e.g. `1` with `precision={2}` adds `0.01`) |
98
+ | step | `number` | `1` | Step in scaled digit units: the increment is `step / 10 ** precision` (e.g. `1` with `precision={2}` adds `0.01`) |
97
99
  | inputMode | `'numeric'` \| `'decimal'` | auto | Overrides mobile keyboard mode |
98
100
  | className | `string` | — | Input CSS class |
99
101
  | style | `object` | — | Input inline styles |
@@ -106,13 +108,37 @@ ReactDOM.render(<App />, document.getElementById('root'));
106
108
 
107
109
  The component also accepts standard `<input>` attributes such as `ref`, `onFocus`, `onKeyDown`, `aria-*`, `data-*`, and `autoComplete`. Custom `onChange` and `onBlur` callbacks receive the clamped numeric value and the formatted masked string.
108
110
 
111
+ ### Value modes and compatibility
112
+
113
+ By default, the component preserves its historical behavior: `value` initializes the field and a changed `value` updates it, while local edits remain visible if the parent leaves `value` unchanged. Without `value`, the component manages its own numeric state.
114
+
115
+ For strict controlled behavior, use `controlled` and update `value` from `onChange`. Rejected edits emit the proposed numeric value and mask, but the input and custom controls retain the parent's value:
116
+
117
+ ```jsx
118
+ <IntlNumberInput
119
+ controlled
120
+ value={value}
121
+ onChange={(_event, nextValue) => setValue(nextValue)}
122
+ />
123
+ ```
124
+
125
+ The default remains compatible with existing integrations, including those that use a fixed `value` as an initial amount. `controlled` requires no changes to callback signatures.
126
+
127
+ String values use the same implied-decimal parser as typing: with `precision={2}`, `value="1234"` means `12.34`, while `value={1234}` means `1,234.00`. Prefer numbers for programmatic values. Changing precision reformats existing internal numeric state without rescaling it; rounding for display does not discard hidden fraction digits.
128
+
129
+ Typing forwards the original React change event. Buttons and custom setters deliver a programmatic change event whose `target` and `currentTarget` are the actual input element, containing the proposed mask during the callback. Use the second and third callback arguments for numeric and formatted values consistently across both kinds of changes. These programmatic callbacks do not dispatch an additional DOM change event.
130
+
109
131
  ## Accessibility (a11y)
110
132
 
111
- The component is built with accessibility in mind:
112
- - Uses `role="spinbutton"` to identify as a numeric input.
113
- - Provides `aria-valuenow`, `aria-valuemin`, and `aria-valuemax` attributes.
114
- - Support for `forwardRef` allows linking labels and managing focus programmatically.
115
- - Built-in step buttons have descriptive `aria-label` attributes.
133
+ The component is built with accessibility in mind:
134
+ - Uses `role="spinbutton"` to identify as a numeric input.
135
+ - Provides `aria-valuenow`, `aria-valuemin`, and `aria-valuemax` attributes.
136
+ - Provides the formatted value as `aria-valuetext`, including any prefix and suffix.
137
+ - Supports Arrow Up and Arrow Down to adjust by one step; Shift plus an arrow adjusts by ten steps. Your `onKeyDown` handler runs first and can prevent the adjustment.
138
+ - Support for `forwardRef` allows linking labels and managing focus programmatically.
139
+ - Built-in step buttons have descriptive `aria-label` attributes.
140
+
141
+ Associate each input with a visible `<label htmlFor="...">`, `aria-labelledby`, or `aria-label`. `readOnly` prevents changes from typing, arrow keys, built-in buttons and custom setters; custom controls also receive `readOnly`.
116
142
 
117
143
  ## Examples
118
144
 
@@ -120,32 +146,32 @@ The component is built with accessibility in mind:
120
146
 
121
147
  ```javascript
122
148
  // maskedValue: 1,234,567.89
123
- <IntlNumberInput />
149
+ <IntlNumberInput value={1234567.89} />
124
150
  ```
125
151
 
126
152
  ```javascript
127
153
  // maskedValue: 12,345.6789
128
- <IntlNumberInput precision={4} />
154
+ <IntlNumberInput value={12345.6789} precision={4} />
129
155
  ```
130
156
 
131
157
  ```javascript
132
158
  // maskedValue: $1,234,567.89
133
- <IntlNumberInput prefix="$" />
159
+ <IntlNumberInput value={1234567.89} prefix="$" />
134
160
  ```
135
161
 
136
162
  ```javascript
137
163
  // maskedValue: 1,234%
138
- <IntlNumberInput suffix="%" precision={0} />
164
+ <IntlNumberInput value={1234} suffix="%" precision={0} />
139
165
  ```
140
166
 
141
167
  ```javascript
142
168
  // maskedValue: 12.50% — type digits only; decimal is implied by precision
143
- <IntlNumberInput suffix="%" precision={2} />
169
+ <IntlNumberInput value={12.5} suffix="%" precision={2} />
144
170
  ```
145
171
 
146
172
  ```javascript
147
173
  // maskedValue: R$ 1.234.567,89
148
- <IntlNumberInput locale="pt-BR" prefix="R$ " precision={2} />
174
+ <IntlNumberInput value={1234567.89} locale="pt-BR" prefix="R$ " precision={2} />
149
175
  ```
150
176
 
151
177
  ```javascript
@@ -171,12 +197,12 @@ The component is built with accessibility in mind:
171
197
  minValue={0}
172
198
  maxValue={100}
173
199
  onChange={(event, value) => console.log(value)}
174
- renderControls={({ increment, decrement, setValue, value, formattedValue, min, max, disabled }) => (
200
+ renderControls={({ increment, decrement, setValue, value, formattedValue, min, max, disabled, readOnly }) => (
175
201
  <div className="amount-controls">
176
- <button type="button" onClick={() => decrement()} disabled={disabled}>-</button>
202
+ <button type="button" onClick={() => decrement()} disabled={disabled || readOnly}>-</button>
177
203
  <span>{formattedValue}</span>
178
- <button type="button" onClick={() => increment()} disabled={disabled}>+</button>
179
- <button type="button" onClick={() => setValue(max ?? value)} disabled={disabled}>
204
+ <button type="button" onClick={() => increment()} disabled={disabled || readOnly}>+</button>
205
+ <button type="button" onClick={() => setValue(max ?? value)} disabled={disabled || readOnly}>
180
206
  Max
181
207
  </button>
182
208
  </div>
@@ -211,12 +237,18 @@ function FocusExample() {
211
237
 
212
238
  Users type digits; the component applies locale formatting and optional prefix/suffix. For `precision={2}`, typing `1234` becomes `12.34`. Negative values are supported when `-` is present in the input.
213
239
 
240
+ ASCII digits and the locale's digits are accepted, including Arabic-Indic, Han decimal and supplementary-plane digits. Edits confined to the configured prefix or suffix preserve the amount and restore the affix. Prefixes and suffixes are display adornments; a `%` suffix does not multiply or divide the numeric value.
241
+
242
+ For compatibility, clearing the input produces zero (or the nearest allowed bound). Empty and missing values are not represented separately: `required` cannot detect a missing amount and `placeholder` is normally hidden. Validate absence separately in your form if needed. Bounds apply immediately; typing an intermediate value below the minimum may therefore clamp it before typing is complete.
243
+
244
+ The numeric API uses JavaScript `number`. Precision controls formatting, not arbitrary-precision arithmetic. Stepping avoids floating-point accumulation when the scaled operands fit safe integers; very large values or high precision retain the limitations of JavaScript numbers.
245
+
214
246
  ## Contributing
215
247
 
216
248
  See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, testing, and publishing instructions.
217
249
 
218
- Local development requires Node.js 20+. The published package has no Node.js version constraint for consumers.
250
+ Before publishing, run `npm run check`. It verifies tests, the demo's types, both builds and an installed npm tarball, including its type declarations and runtime entry points.
219
251
 
220
252
  ## Changelog
221
253
 
222
- See [CHANGELOG.md](CHANGELOG.md) for release history.
254
+ See [CHANGELOG.md](CHANGELOG.md) for release history.
@@ -0,0 +1,34 @@
1
+ import React from 'react';
2
+ export interface ControlsRenderProps {
3
+ increment: (multiplier?: number) => void;
4
+ decrement: (multiplier?: number) => void;
5
+ setValue: (value: number) => void;
6
+ value: number;
7
+ min?: number;
8
+ max?: number;
9
+ disabled: boolean;
10
+ readOnly?: boolean;
11
+ step: number;
12
+ precision: number;
13
+ formattedValue: string;
14
+ }
15
+ type IntlNumberInputOwnProps = {
16
+ locale?: string;
17
+ prefix?: string;
18
+ suffix?: string;
19
+ precision?: number;
20
+ value?: number | string;
21
+ /** Opt in to keeping value authoritative when the parent rejects an edit. */
22
+ controlled?: boolean;
23
+ showStepButtons?: boolean;
24
+ step?: number;
25
+ minValue?: number;
26
+ maxValue?: number;
27
+ inputMode?: 'numeric' | 'decimal';
28
+ onChange?: (event: React.ChangeEvent<HTMLInputElement>, value: number, maskedValue: string) => void;
29
+ onBlur?: (event: React.FocusEvent<HTMLInputElement>, value: number, maskedValue: string) => void;
30
+ renderControls?: (props: ControlsRenderProps) => React.ReactNode;
31
+ };
32
+ export type IntlNumberInputProps = IntlNumberInputOwnProps & Omit<React.ComponentPropsWithoutRef<'input'>, keyof IntlNumberInputOwnProps | 'value' | 'type' | 'defaultValue' | 'children'>;
33
+ declare const _default: React.NamedExoticComponent<IntlNumberInputOwnProps & Omit<Omit<React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "ref">, "children" | "defaultValue" | "type" | keyof IntlNumberInputOwnProps> & React.RefAttributes<HTMLInputElement>>;
34
+ export default _default;